@madojs/mado 0.12.0 → 0.13.1
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 +15 -8
- package/CHANGELOG.md +66 -0
- package/README.md +25 -3
- package/dist/src/component.d.ts +3 -1
- package/dist/src/component.d.ts.map +1 -0
- package/dist/src/component.js +65 -30
- package/dist/src/component.js.map +1 -1
- package/dist/src/context.d.ts +13 -32
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +73 -48
- package/dist/src/context.js.map +1 -1
- package/dist/src/css.d.ts +1 -0
- package/dist/src/css.d.ts.map +1 -0
- package/dist/src/css.js.map +1 -1
- package/dist/src/devtools-hook.d.ts +14 -0
- package/dist/src/devtools-hook.d.ts.map +1 -0
- package/dist/src/devtools-hook.js +27 -0
- package/dist/src/devtools-hook.js.map +1 -0
- package/dist/src/devtools.d.ts +24 -22
- package/dist/src/devtools.d.ts.map +1 -0
- package/dist/src/devtools.js +230 -58
- package/dist/src/devtools.js.map +1 -1
- package/dist/src/diagnostics.d.ts +12 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +68 -8
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/each.d.ts +1 -5
- package/dist/src/each.d.ts.map +1 -0
- package/dist/src/each.js +0 -8
- package/dist/src/each.js.map +1 -1
- package/dist/src/forms.d.ts +27 -96
- package/dist/src/forms.d.ts.map +1 -0
- package/dist/src/forms.js +184 -394
- package/dist/src/forms.js.map +1 -1
- package/dist/src/head.d.ts +1 -0
- package/dist/src/head.d.ts.map +1 -0
- package/dist/src/head.js +2 -1
- package/dist/src/head.js.map +1 -1
- package/dist/src/html/bindings.d.ts +1 -0
- package/dist/src/html/bindings.d.ts.map +1 -0
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/html/parser.d.ts +3 -4
- package/dist/src/html/parser.d.ts.map +1 -0
- package/dist/src/html/parser.js +63 -14
- package/dist/src/html/parser.js.map +1 -1
- package/dist/src/html/template-types.d.ts +1 -0
- package/dist/src/html/template-types.d.ts.map +1 -0
- package/dist/src/html/template-types.js.map +1 -1
- package/dist/src/html/template.d.ts +5 -1
- package/dist/src/html/template.d.ts.map +1 -0
- package/dist/src/html/template.js +78 -3
- package/dist/src/html/template.js.map +1 -1
- package/dist/src/index.d.ts +7 -8
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/json.d.ts +3 -0
- package/dist/src/json.d.ts.map +1 -0
- package/dist/src/json.js +14 -0
- package/dist/src/json.js.map +1 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.d.ts.map +1 -0
- package/dist/src/lifecycle.js +3 -2
- package/dist/src/lifecycle.js.map +1 -1
- package/dist/src/page.d.ts +1 -0
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/page.js.map +1 -1
- package/dist/src/persisted.d.ts +6 -1
- package/dist/src/persisted.d.ts.map +1 -0
- package/dist/src/persisted.js +17 -11
- package/dist/src/persisted.js.map +1 -1
- package/dist/src/resource.d.ts +2 -1
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +126 -49
- package/dist/src/resource.js.map +1 -1
- package/dist/src/router/base.d.ts +1 -0
- package/dist/src/router/base.d.ts.map +1 -0
- package/dist/src/router/base.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -0
- package/dist/src/router/manifest.d.ts.map +1 -0
- package/dist/src/router/manifest.js +89 -148
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +1 -0
- package/dist/src/router/match.d.ts.map +1 -0
- package/dist/src/router/match.js +19 -5
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.d.ts +1 -0
- package/dist/src/router/navigation.d.ts.map +1 -0
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/signal.d.ts +1 -0
- package/dist/src/signal.d.ts.map +1 -0
- package/dist/src/signal.js +46 -17
- package/dist/src/signal.js.map +1 -1
- package/dist/src/static-runtime.d.ts +1 -0
- package/dist/src/static-runtime.d.ts.map +1 -0
- package/dist/src/static-runtime.js.map +1 -1
- package/dist/src/vite/index.d.ts +1 -0
- package/dist/src/vite/index.d.ts.map +1 -0
- package/dist/src/vite/index.js +4 -3
- package/dist/src/vite/index.js.map +1 -1
- package/docs/architecture/v1-roadmap.md +19 -0
- package/docs/en/10-pages-and-components.md +9 -1
- package/docs/en/13-data.md +8 -6
- package/docs/en/14-forms.md +66 -219
- package/docs/en/15-static-snapshots.md +4 -4
- package/docs/en/16-app-architecture.md +3 -2
- package/docs/en/20-deployment.md +21 -8
- package/docs/en/21-error-handling.md +9 -6
- package/docs/en/24-devtools-and-diagnostics.md +45 -0
- package/docs/en/30-api-freeze-map.md +8 -9
- package/docs/en/32-v1-stability.md +2 -2
- package/docs/en/33-migration-0.12-0.13.md +56 -0
- package/docs/en/40-llm-guide.md +7 -9
- package/docs/en/41-for-backenders.md +13 -9
- package/docs/en/42-why-mado.md +2 -2
- package/docs/en/README.md +2 -0
- package/docs/recipes/nginx/nginx.conf +2 -2
- package/llms.txt +25 -7
- package/package.json +37 -16
- package/scripts/clean.mjs +8 -0
- package/scripts/cli/generate.mjs +11 -14
- package/scripts/cli/help.mjs +2 -0
- package/scripts/cli/index.mjs +14 -6
- package/scripts/cli/init.mjs +13 -18
- package/scripts/cli/release.mjs +22 -19
- package/scripts/cli/run.mjs +5 -3
- package/scripts/logger.mjs +117 -0
- package/scripts/output-guard.mjs +128 -0
- package/scripts/preview.mjs +11 -19
- package/scripts/static/browser.mjs +58 -8
- package/scripts/static/discover.mjs +4 -1
- package/scripts/static.mjs +21 -7
- package/starters/default/package.json +4 -4
- package/starters/default/tsconfig.node.json +13 -0
- package/starters/default/vite.config.ts +0 -3
- package/starters/modular/README.md +4 -4
- package/starters/modular/package.json +4 -4
- package/starters/modular/src/layouts/app-shell.layout.ts +1 -2
- package/starters/modular/src/modules/auth/login.page.ts +5 -12
- package/starters/modular/src/modules/billing/pages/invoice-detail.page.ts +3 -4
- package/starters/modular/src/modules/billing/pages/invoices-list.page.ts +1 -1
- package/starters/modular/src/shared/styles/reset.css +21 -1
- package/starters/modular/tsconfig.json +10 -1
- package/starters/modular/tsconfig.node.json +13 -0
- package/starters/modular/vite.config.ts +6 -5
- package/dist/src/lazy.d.ts +0 -38
- package/dist/src/lazy.js +0 -73
- package/dist/src/lazy.js.map +0 -1
- package/scripts/bake.mjs +0 -5
- package/scripts/docs-lint.mjs +0 -170
- package/scripts/package-smoke.mjs +0 -135
- package/scripts/release-notes.mjs +0 -66
- package/scripts/size-budget.mjs +0 -95
- package/starters/modular/src/shared/ui/x-button.component.ts +0 -49
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../../src/diagnostics.ts"],"names":[],"mappings":"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","sourcesContent":["import { emitDevtools } from \"./devtools-hook.js\";\n\n/**\n * Internal dev diagnostics.\n *\n * Warnings are intentionally best-effort: they never throw and they are printed\n * once per code so noisy app renders do not flood the console.\n */\n\nexport type DiagnosticLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface DiagnosticRecord {\n timestamp: number;\n level: DiagnosticLevel;\n scope: string;\n code: string;\n message: string;\n data?: unknown;\n}\n\nconst seen = new Set<string>();\nconst LEVELS: Record<DiagnosticLevel | \"silent\", number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: 50,\n};\n\nexport function reportDiagnostic(\n level: DiagnosticLevel,\n scope: string,\n code: string,\n message: string,\n data?: unknown,\n): void {\n if (LEVELS[level] < LEVELS[configuredLevel()]) return;\n const record: DiagnosticRecord = {\n timestamp: Date.now(),\n level,\n scope,\n code,\n message,\n ...(data === undefined ? {} : { data }),\n };\n try {\n const method = level === \"debug\" ? \"debug\" : level;\n const colour = level === \"error\"\n ? \"color:#d33;font-weight:600\"\n : level === \"warn\"\n ? \"color:#b26a00;font-weight:600\"\n : level === \"info\"\n ? \"color:#087ea4\"\n : \"color:#777\";\n const args: unknown[] = [\n `%c[mado:${scope}:${code}]%c ${message}`,\n colour,\n \"color:inherit;font-weight:normal\",\n ];\n if (data !== undefined) args.push(data);\n console[method](...args);\n } catch {\n /* diagnostics must never affect application behaviour */\n }\n if (typeof __MADO_DEVTOOLS__ === \"undefined\" || __MADO_DEVTOOLS__) {\n emitDevtools(`diagnostic:${level}`, undefined, record);\n }\n dispatchDiagnostic(record);\n}\n\nexport function reportError(\n scope: string,\n code: string,\n message: string,\n error: unknown,\n): void {\n reportDiagnostic(\"error\", scope, code, message, error);\n}\n\nexport function warnOnce(code: string, message: string): void {\n if (seen.has(code)) return;\n seen.add(code);\n try {\n reportDiagnostic(\"warn\", \"runtime\", code, message);\n } catch {\n /* noop */\n }\n}\n\n/** @internal */\nexport const _testHooks = {\n resetWarnings(): void {\n seen.clear();\n },\n seenWarnings(): string[] {\n return [...seen];\n },\n};\n\nfunction configuredLevel(): DiagnosticLevel | \"silent\" {\n try {\n const value = localStorage.getItem(\"mado:log-level\") ?? \"warn\";\n if (Object.hasOwn(LEVELS, value)) return value as DiagnosticLevel | \"silent\";\n } catch {\n /* localStorage is unavailable in Node, workers and locked-down pages */\n }\n return \"warn\";\n}\n\nfunction dispatchDiagnostic(record: DiagnosticRecord): void {\n try {\n globalThis.dispatchEvent?.(\n new CustomEvent(\"mado:diagnostic\", { detail: record }),\n );\n } catch {\n /* EventTarget/CustomEvent may not exist in non-browser runtimes. */\n }\n}\n"]}
|
package/dist/src/each.d.ts
CHANGED
|
@@ -32,8 +32,4 @@ export interface EachResult<T = unknown> {
|
|
|
32
32
|
}
|
|
33
33
|
export declare const isEachResult: (v: unknown) => v is EachResult;
|
|
34
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;
|
|
35
|
+
//# sourceMappingURL=each.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/each.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtC;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,cAAc,CAAC;CAC7D;AAED,eAAO,MAAM,YAAY,MAAO,OAAO,KAAG,CAAC,IAAI,UAGT,CAAC;AAEvC,wBAAgB,IAAI,CAAC,CAAC,EACpB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,EAC1C,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,cAAc,GACjD,UAAU,CAAC,CAAC,CAAC,CAEf"}
|
package/dist/src/each.js
CHANGED
|
@@ -18,18 +18,10 @@
|
|
|
18
18
|
* Template usage:
|
|
19
19
|
* html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.text}</li>`)}</ul>`
|
|
20
20
|
*/
|
|
21
|
-
import { html } from "./html/template.js";
|
|
22
21
|
export const isEachResult = (v) => typeof v === "object" &&
|
|
23
22
|
v !== null &&
|
|
24
23
|
v._madoEach === true;
|
|
25
24
|
export function each(items, keyOf, render) {
|
|
26
25
|
return { _madoEach: true, items, keyOf, render };
|
|
27
26
|
}
|
|
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
27
|
//# sourceMappingURL=each.js.map
|
package/dist/src/each.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.js","sourceRoot":"","sources":["../../src/each.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"each.js","sourceRoot":"","sources":["../../src/each.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAiBH,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","sourcesContent":["/**\n * Keyed list rendering.\n *\n * each() returns a special EachResult descriptor. The html binder recognizes\n * it by the `_madoEach` flag and applies keyed reconciliation: on every\n * update it reuses DOM nodes for the same keys and moves them with\n * insertBefore instead of recreating everything.\n *\n * Key differences from a naive implementation:\n * - state lives in the parent's ChildState, not globally by renderFn, so two\n * each() calls with the same render function do not interfere;\n * - DOM nodes are actually reused, so input focus, animations,\n * IntersectionObserver state and .scrollTop survive reorder.\n *\n * API:\n * each(items, item => item.id, item => html`<li>${item.name}</li>`)\n *\n * Template usage:\n * html`<ul>${() => each(items(), t => t.id, t => html`<li>${t.text}</li>`)}</ul>`\n */\n\nimport type { TemplateResult } from \"./html/template-types.js\";\n\nexport type EachKey = string | number;\n\n/**\n * Marker recognised by the html binder.\n * Do not set `_madoEach` yourself — use each().\n */\nexport interface EachResult<T = unknown> {\n readonly _madoEach: true;\n readonly items: readonly T[];\n readonly keyOf: (item: T, index: number) => EachKey;\n readonly render: (item: T, index: number) => TemplateResult;\n}\n\nexport const isEachResult = (v: unknown): v is EachResult =>\n typeof v === \"object\" &&\n v !== null &&\n (v as EachResult)._madoEach === true;\n\nexport function each<T>(\n items: readonly T[],\n keyOf: (item: T, index: number) => EachKey,\n render: (item: T, index: number) => TemplateResult,\n): EachResult<T> {\n return { _madoEach: true, items, keyOf, render };\n}\n"]}
|
package/dist/src/forms.d.ts
CHANGED
|
@@ -1,105 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* const f = useForm({
|
|
6
|
-
* email: { required: true, type: 'email' },
|
|
7
|
-
* age: { min: 18 },
|
|
8
|
-
* });
|
|
9
|
-
*
|
|
10
|
-
* html`
|
|
11
|
-
* <form @submit=${f.onSubmit(async values => { await save(values) })}>
|
|
12
|
-
* <input name="email" .value=${() => f.values().email ?? ''}
|
|
13
|
-
* @input=${f.onInput} />
|
|
14
|
-
* ${() => f.errors().email ? html`<err>${f.errors().email}</err>` : null}
|
|
15
|
-
*
|
|
16
|
-
* <input name="age" type="number" .value=${() => f.values().age ?? ''}
|
|
17
|
-
* @input=${f.onInput} />
|
|
18
|
-
*
|
|
19
|
-
* <button ?disabled=${() => !f.isValid() || f.submitting()}>Save</button>
|
|
20
|
-
* </form>
|
|
21
|
-
* `;
|
|
22
|
-
*
|
|
23
|
-
* What's inside:
|
|
24
|
-
* - validation rules mirror common HTML constraints
|
|
25
|
-
* (required, min, max, pattern, type=email/url/number, etc.);
|
|
26
|
-
* - custom rules via a validate(values) function returning
|
|
27
|
-
* { field: 'msg' } or null;
|
|
28
|
-
* - all fields are automatically "touched" on blur — errors
|
|
29
|
-
* are shown only after user interaction;
|
|
30
|
-
* - submit() is aborted if the form is invalid.
|
|
31
|
-
*
|
|
32
|
-
* No Yup/Zod/Formik dependencies. If you really want them —
|
|
33
|
-
* pass your own validate(values) and wire it to anything.
|
|
2
|
+
* Typed form state over the browser's native form and constraint-validation
|
|
3
|
+
* model. HTML owns required/type/min/max/pattern; Mado owns reactive state,
|
|
4
|
+
* touched/dirty flags, async validation cancellation and submit state.
|
|
34
5
|
*/
|
|
35
|
-
import { type
|
|
36
|
-
export type FormValue =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export type FormValues = Record<string,
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/** Field declaration. Rules intentionally match common HTML constraints. */
|
|
44
|
-
export interface FieldSchema {
|
|
45
|
-
required?: boolean;
|
|
46
|
-
type?: "text" | "email" | "url" | "number" | "tel" | "password";
|
|
47
|
-
min?: number;
|
|
48
|
-
max?: number;
|
|
49
|
-
minLength?: number;
|
|
50
|
-
maxLength?: number;
|
|
51
|
-
pattern?: string;
|
|
52
|
-
/** Default value */
|
|
53
|
-
default?: FormValue;
|
|
54
|
-
/** Synchronous custom validator for this field. */
|
|
55
|
-
validate?: FieldValidator;
|
|
56
|
-
/** Asynchronous custom validator for this field. */
|
|
57
|
-
validateAsync?: AsyncFieldValidator;
|
|
6
|
+
import { type Computed, type Signal } from "./signal.js";
|
|
7
|
+
export type FormValue = unknown;
|
|
8
|
+
export type FormValues = Record<string, unknown>;
|
|
9
|
+
export type FormErrors<V extends FormValues = FormValues> = Partial<Record<string, string>>;
|
|
10
|
+
export type FormTouched<V extends FormValues = FormValues> = Partial<Record<string, boolean>>;
|
|
11
|
+
export interface FormValidationContext {
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
form: HTMLFormElement | null;
|
|
58
14
|
}
|
|
59
|
-
export type
|
|
15
|
+
export type FormValidator<V extends FormValues> = (values: Readonly<V>, context: FormValidationContext) => FormErrors<V> | null | Promise<FormErrors<V> | null>;
|
|
60
16
|
export interface UseFormOptions<V extends FormValues> {
|
|
61
|
-
|
|
62
|
-
validate?:
|
|
63
|
-
/** Async custom validator, usually for server-backed uniqueness checks. */
|
|
64
|
-
validateAsync?: (values: V) => Promise<FormErrors | null>;
|
|
65
|
-
}
|
|
66
|
-
export interface FieldArrayApi<T extends FormValue = FormValue> {
|
|
67
|
-
/** Current array value. */
|
|
68
|
-
items: () => T[];
|
|
69
|
-
/** Field name helper: `items.0.title`. */
|
|
70
|
-
path(index: number, field?: string): string;
|
|
71
|
-
append(value: T): void;
|
|
72
|
-
prepend(value: T): void;
|
|
73
|
-
insert(index: number, value: T): void;
|
|
74
|
-
remove(index: number): void;
|
|
75
|
-
move(from: number, to: number): void;
|
|
76
|
-
replace(items: readonly T[]): void;
|
|
77
|
-
set(index: number, value: T): void;
|
|
17
|
+
initial: V;
|
|
18
|
+
validate?: FormValidator<V>;
|
|
78
19
|
}
|
|
79
20
|
export interface FormApi<V extends FormValues> {
|
|
80
21
|
values: Signal<V>;
|
|
81
|
-
errors:
|
|
82
|
-
touched: Signal<
|
|
22
|
+
errors: Computed<FormErrors<V>>;
|
|
23
|
+
touched: Signal<FormTouched<V>>;
|
|
24
|
+
dirty: Computed<boolean>;
|
|
83
25
|
submitting: Signal<boolean>;
|
|
84
|
-
validating:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
onSubmit: (handler: (values: V) => void | Promise<void>) => (e: Event) => void;
|
|
93
|
-
/** Set a field value programmatically. */
|
|
94
|
-
setField<K extends keyof V & string>(name: K, value: V[K]): void;
|
|
95
|
-
setField(name: string, value: FormValue): void;
|
|
96
|
-
/** Run async validators for a single field path. */
|
|
97
|
-
validateField(name: string): Promise<boolean>;
|
|
98
|
-
/** Run all sync + async validators and update errors. */
|
|
99
|
-
validate(): Promise<boolean>;
|
|
100
|
-
/** Helper for dynamic arrays stored under one form field. */
|
|
101
|
-
array<T extends FormValue = FormValue>(name: keyof V & string): FieldArrayApi<T>;
|
|
102
|
-
/** Reset to defaults. */
|
|
103
|
-
reset(): void;
|
|
26
|
+
validating: Signal<boolean>;
|
|
27
|
+
isValid: Computed<boolean>;
|
|
28
|
+
onInput(event: Event): void;
|
|
29
|
+
onBlur(event: Event): void;
|
|
30
|
+
onSubmit(handler: (values: Readonly<V>, event: SubmitEvent) => void | Promise<void>): (event: SubmitEvent) => void;
|
|
31
|
+
setField<K extends Extract<keyof V, string>>(name: K, value: V[K]): void;
|
|
32
|
+
reset(nextInitial?: V): void;
|
|
33
|
+
validate(form?: HTMLFormElement | null): Promise<boolean>;
|
|
104
34
|
}
|
|
105
|
-
export declare function useForm<V extends FormValues>(
|
|
35
|
+
export declare function useForm<V extends FormValues>(options: UseFormOptions<V>): FormApi<V>;
|
|
36
|
+
//# sourceMappingURL=forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["../../src/forms.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAG3E,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC;AAChC,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,OAAO,CACjE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACvB,CAAC;AACF,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,OAAO,CAClE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CACxB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,UAAU,IAAI,CAChD,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,qBAAqB,KAC3B,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAE1D,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,UAAU;IAClD,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,UAAU;IAC3C,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAClB,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAChC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC3B,QAAQ,CACN,OAAO,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACzE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACzE,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAID,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,EAC1C,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC,CAuJZ"}
|