@elurjs/core 3.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/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/lib/async.cjs +16 -0
- package/dist/lib/async.js +123 -0
- package/dist/lib/component.cjs +2 -0
- package/dist/lib/component.js +66 -0
- package/dist/lib/context.cjs +2 -0
- package/dist/lib/context.js +52 -0
- package/dist/lib/devtools.cjs +59 -0
- package/dist/lib/devtools.js +419 -0
- package/dist/lib/elur/async.cjs +16 -0
- package/dist/lib/elur/async.cjs.map +1 -0
- package/dist/lib/elur/async.d.cts +32 -0
- package/dist/lib/elur/async.d.ts +32 -0
- package/dist/lib/elur/async.js +123 -0
- package/dist/lib/elur/async.js.map +1 -0
- package/dist/lib/elur/component.cjs +2 -0
- package/dist/lib/elur/component.cjs.map +1 -0
- package/dist/lib/elur/component.d.cts +17 -0
- package/dist/lib/elur/component.d.ts +17 -0
- package/dist/lib/elur/component.js +66 -0
- package/dist/lib/elur/component.js.map +1 -0
- package/dist/lib/elur/context.cjs +2 -0
- package/dist/lib/elur/context.cjs.map +1 -0
- package/dist/lib/elur/context.d.cts +29 -0
- package/dist/lib/elur/context.d.ts +29 -0
- package/dist/lib/elur/context.js +52 -0
- package/dist/lib/elur/context.js.map +1 -0
- package/dist/lib/elur/devtools.cjs +59 -0
- package/dist/lib/elur/devtools.cjs.map +1 -0
- package/dist/lib/elur/devtools.d.cts +24 -0
- package/dist/lib/elur/devtools.d.ts +24 -0
- package/dist/lib/elur/devtools.js +419 -0
- package/dist/lib/elur/devtools.js.map +1 -0
- package/dist/lib/elur/form.cjs +2 -0
- package/dist/lib/elur/form.cjs.map +1 -0
- package/dist/lib/elur/form.d.cts +283 -0
- package/dist/lib/elur/form.d.ts +283 -0
- package/dist/lib/elur/form.js +315 -0
- package/dist/lib/elur/form.js.map +1 -0
- package/dist/lib/elur/hydrate/index.cjs +2 -0
- package/dist/lib/elur/hydrate/index.cjs.map +1 -0
- package/dist/lib/elur/hydrate/index.d.cts +13 -0
- package/dist/lib/elur/hydrate/index.d.ts +13 -0
- package/dist/lib/elur/hydrate/index.js +410 -0
- package/dist/lib/elur/hydrate/index.js.map +1 -0
- package/dist/lib/elur/index.d.cts +19 -0
- package/dist/lib/elur/index.d.ts +19 -0
- package/dist/lib/elur/lifecycle.cjs +2 -0
- package/dist/lib/elur/lifecycle.cjs.map +1 -0
- package/dist/lib/elur/lifecycle.d.cts +45 -0
- package/dist/lib/elur/lifecycle.d.ts +45 -0
- package/dist/lib/elur/lifecycle.js +39 -0
- package/dist/lib/elur/lifecycle.js.map +1 -0
- package/dist/lib/elur/plugins.cjs +2 -0
- package/dist/lib/elur/plugins.cjs.map +1 -0
- package/dist/lib/elur/plugins.d.cts +48 -0
- package/dist/lib/elur/plugins.d.ts +48 -0
- package/dist/lib/elur/plugins.js +59 -0
- package/dist/lib/elur/plugins.js.map +1 -0
- package/dist/lib/elur/reactivity.cjs +2 -0
- package/dist/lib/elur/reactivity.cjs.map +1 -0
- package/dist/lib/elur/reactivity.d.cts +60 -0
- package/dist/lib/elur/reactivity.d.ts +60 -0
- package/dist/lib/elur/reactivity.js +188 -0
- package/dist/lib/elur/reactivity.js.map +1 -0
- package/dist/lib/elur/router.cjs +12 -0
- package/dist/lib/elur/router.cjs.map +1 -0
- package/dist/lib/elur/router.d.cts +137 -0
- package/dist/lib/elur/router.d.ts +137 -0
- package/dist/lib/elur/router.js +513 -0
- package/dist/lib/elur/router.js.map +1 -0
- package/dist/lib/elur/server/index.cjs +2 -0
- package/dist/lib/elur/server/index.cjs.map +1 -0
- package/dist/lib/elur/server/index.d.cts +44 -0
- package/dist/lib/elur/server/index.d.ts +44 -0
- package/dist/lib/elur/server/index.js +307 -0
- package/dist/lib/elur/server/index.js.map +1 -0
- package/dist/lib/elur/store.cjs +2 -0
- package/dist/lib/elur/store.cjs.map +1 -0
- package/dist/lib/elur/store.d.cts +137 -0
- package/dist/lib/elur/store.d.ts +137 -0
- package/dist/lib/elur/store.js +143 -0
- package/dist/lib/elur/store.js.map +1 -0
- package/dist/lib/elur/template/bindings.cjs +2 -0
- package/dist/lib/elur/template/bindings.cjs.map +1 -0
- package/dist/lib/elur/template/bindings.d.cts +40 -0
- package/dist/lib/elur/template/bindings.d.ts +40 -0
- package/dist/lib/elur/template/bindings.js +214 -0
- package/dist/lib/elur/template/bindings.js.map +1 -0
- package/dist/lib/elur/template/dom-write.cjs +2 -0
- package/dist/lib/elur/template/dom-write.cjs.map +1 -0
- package/dist/lib/elur/template/dom-write.d.cts +5 -0
- package/dist/lib/elur/template/dom-write.d.ts +5 -0
- package/dist/lib/elur/template/dom-write.js +16 -0
- package/dist/lib/elur/template/dom-write.js.map +1 -0
- package/dist/lib/elur/template/error-boundary.cjs +2 -0
- package/dist/lib/elur/template/error-boundary.cjs.map +1 -0
- package/dist/lib/elur/template/error-boundary.d.cts +7 -0
- package/dist/lib/elur/template/error-boundary.d.ts +7 -0
- package/dist/lib/elur/template/error-boundary.js +90 -0
- package/dist/lib/elur/template/error-boundary.js.map +1 -0
- package/dist/lib/elur/template/html.cjs +2 -0
- package/dist/lib/elur/template/html.cjs.map +1 -0
- package/dist/lib/elur/template/html.d.cts +8 -0
- package/dist/lib/elur/template/html.d.ts +8 -0
- package/dist/lib/elur/template/html.js +104 -0
- package/dist/lib/elur/template/html.js.map +1 -0
- package/dist/lib/elur/template/index.cjs +1 -0
- package/dist/lib/elur/template/index.d.cts +15 -0
- package/dist/lib/elur/template/index.d.ts +15 -0
- package/dist/lib/elur/template/index.js +13 -0
- package/dist/lib/elur/template/keyed-diff.cjs +2 -0
- package/dist/lib/elur/template/keyed-diff.cjs.map +1 -0
- package/dist/lib/elur/template/keyed-diff.d.cts +32 -0
- package/dist/lib/elur/template/keyed-diff.d.ts +32 -0
- package/dist/lib/elur/template/keyed-diff.js +100 -0
- package/dist/lib/elur/template/keyed-diff.js.map +1 -0
- package/dist/lib/elur/template/keyed.cjs +2 -0
- package/dist/lib/elur/template/keyed.cjs.map +1 -0
- package/dist/lib/elur/template/keyed.d.cts +27 -0
- package/dist/lib/elur/template/keyed.d.ts +27 -0
- package/dist/lib/elur/template/keyed.js +52 -0
- package/dist/lib/elur/template/keyed.js.map +1 -0
- package/dist/lib/elur/template/mount-helpers.cjs +2 -0
- package/dist/lib/elur/template/mount-helpers.cjs.map +1 -0
- package/dist/lib/elur/template/mount-helpers.d.cts +29 -0
- package/dist/lib/elur/template/mount-helpers.d.ts +29 -0
- package/dist/lib/elur/template/mount-helpers.js +132 -0
- package/dist/lib/elur/template/mount-helpers.js.map +1 -0
- package/dist/lib/elur/template/node-binding.cjs +2 -0
- package/dist/lib/elur/template/node-binding.cjs.map +1 -0
- package/dist/lib/elur/template/node-binding.d.cts +6 -0
- package/dist/lib/elur/template/node-binding.d.ts +6 -0
- package/dist/lib/elur/template/node-binding.js +95 -0
- package/dist/lib/elur/template/node-binding.js.map +1 -0
- package/dist/lib/elur/template/portal.cjs +2 -0
- package/dist/lib/elur/template/portal.cjs.map +1 -0
- package/dist/lib/elur/template/portal.d.cts +19 -0
- package/dist/lib/elur/template/portal.d.ts +19 -0
- package/dist/lib/elur/template/portal.js +49 -0
- package/dist/lib/elur/template/portal.js.map +1 -0
- package/dist/lib/elur/template/raw.cjs +2 -0
- package/dist/lib/elur/template/raw.cjs.map +1 -0
- package/dist/lib/elur/template/raw.d.cts +9 -0
- package/dist/lib/elur/template/raw.d.ts +9 -0
- package/dist/lib/elur/template/raw.js +29 -0
- package/dist/lib/elur/template/raw.js.map +1 -0
- package/dist/lib/elur/template/sanitize.cjs +2 -0
- package/dist/lib/elur/template/sanitize.cjs.map +1 -0
- package/dist/lib/elur/template/sanitize.d.cts +16 -0
- package/dist/lib/elur/template/sanitize.d.ts +16 -0
- package/dist/lib/elur/template/sanitize.js +29 -0
- package/dist/lib/elur/template/sanitize.js.map +1 -0
- package/dist/lib/elur/template/transitions.cjs +2 -0
- package/dist/lib/elur/template/transitions.cjs.map +1 -0
- package/dist/lib/elur/template/transitions.d.cts +43 -0
- package/dist/lib/elur/template/transitions.d.ts +43 -0
- package/dist/lib/elur/template/transitions.js +96 -0
- package/dist/lib/elur/template/transitions.js.map +1 -0
- package/dist/lib/elur/template/types.cjs +2 -0
- package/dist/lib/elur/template/types.cjs.map +1 -0
- package/dist/lib/elur/template/types.d.cts +114 -0
- package/dist/lib/elur/template/types.d.ts +114 -0
- package/dist/lib/elur/template/types.js +23 -0
- package/dist/lib/elur/template/types.js.map +1 -0
- package/dist/lib/elur.cjs +1 -0
- package/dist/lib/elur.js +18 -0
- package/dist/lib/form.cjs +2 -0
- package/dist/lib/form.js +315 -0
- package/dist/lib/hydrate.cjs +2 -0
- package/dist/lib/hydrate.js +410 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.cts +2 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +18 -0
- package/dist/lib/lifecycle.cjs +2 -0
- package/dist/lib/lifecycle.js +39 -0
- package/dist/lib/plugins.cjs +2 -0
- package/dist/lib/plugins.js +59 -0
- package/dist/lib/router.cjs +12 -0
- package/dist/lib/router.js +513 -0
- package/dist/lib/server.cjs +2 -0
- package/dist/lib/server.js +307 -0
- package/dist/lib/signals.cjs +2 -0
- package/dist/lib/signals.js +188 -0
- package/dist/lib/store.cjs +2 -0
- package/dist/lib/store.js +143 -0
- package/dist/lib/template.cjs +1 -0
- package/dist/lib/template.js +13 -0
- package/package.json +216 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { Signal } from "./reactivity.js";
|
|
2
|
+
/**
|
|
3
|
+
* A validator function. Return an error string when invalid, or `null` /
|
|
4
|
+
* `undefined` when valid.
|
|
5
|
+
*
|
|
6
|
+
* `allValues` is provided when the validator runs inside `createForm`, enabling
|
|
7
|
+
* cross-field validation (e.g., "confirm password matches password").
|
|
8
|
+
*
|
|
9
|
+
* **Validators must be pure.** They are invoked reactively whenever the field
|
|
10
|
+
* value changes — potentially many times per keystroke in forms with
|
|
11
|
+
* cross-field rules — and may run inside computed signals that assume
|
|
12
|
+
* deterministic output. Do not perform I/O, mutate external state, or rely on
|
|
13
|
+
* `Date.now()` / `Math.random()` inside a validator. For asynchronous checks
|
|
14
|
+
* (uniqueness, server-side rules), submit the form and inject the result via
|
|
15
|
+
* {@link FormState.setErrors}.
|
|
16
|
+
*/
|
|
17
|
+
export type Validator<T, AllValues = unknown> = (value: T, allValues?: AllValues) => string | null | undefined;
|
|
18
|
+
export declare function required(message?: string): Validator<unknown>;
|
|
19
|
+
export declare function minLength(n: number, message?: string): Validator<string>;
|
|
20
|
+
export declare function maxLength(n: number, message?: string): Validator<string>;
|
|
21
|
+
export declare function pattern(regex: RegExp, message?: string): Validator<string>;
|
|
22
|
+
export declare function email(message?: string): Validator<string>;
|
|
23
|
+
export declare function min(n: number, message?: string): Validator<number>;
|
|
24
|
+
export declare function max(n: number, message?: string): Validator<number>;
|
|
25
|
+
/** Creates a typed custom validator compatible with `elurField` and `createForm`. */
|
|
26
|
+
export declare function createValidator<T, AllValues = unknown>(fn: (value: T, allValues?: AllValues) => string | null | undefined): Validator<T, AllValues>;
|
|
27
|
+
/** All built-in validators grouped as a namespace. Extensible via `extendValidators`. */
|
|
28
|
+
export declare const validators: {
|
|
29
|
+
readonly required: typeof required;
|
|
30
|
+
readonly minLength: typeof minLength;
|
|
31
|
+
readonly maxLength: typeof maxLength;
|
|
32
|
+
readonly email: typeof email;
|
|
33
|
+
readonly pattern: typeof pattern;
|
|
34
|
+
readonly min: typeof min;
|
|
35
|
+
readonly max: typeof max;
|
|
36
|
+
};
|
|
37
|
+
/** Shape of the built-in `validators` namespace. Used as the base type for `extendValidators`. */
|
|
38
|
+
export type ValidatorsBase = typeof validators;
|
|
39
|
+
/**
|
|
40
|
+
* Merges custom validator factories into the built-in namespace.
|
|
41
|
+
* Returns a new object — the original is never mutated.
|
|
42
|
+
*/
|
|
43
|
+
export declare function extendValidators<E extends Record<string, (...args: any[]) => Validator<any>>>(base: ValidatorsBase, extensions: E): ValidatorsBase & E;
|
|
44
|
+
/**
|
|
45
|
+
* Controls when validation errors become visible.
|
|
46
|
+
* - `"blur"` — after the field loses focus (default)
|
|
47
|
+
* - `"input"` — as soon as the user types
|
|
48
|
+
* - `"submit"` — only after the first submit attempt
|
|
49
|
+
*/
|
|
50
|
+
export type ValidateOn = "blur" | "input" | "submit";
|
|
51
|
+
/** Public state of a single form field. */
|
|
52
|
+
export interface FieldState<T> {
|
|
53
|
+
/** Current value — read/write signal. */
|
|
54
|
+
value: Signal<T>;
|
|
55
|
+
/** Error visible según validateOn. Para UI. */
|
|
56
|
+
readonly error: Signal<string | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Error real, ignorando validateOn/touched/dirty.
|
|
59
|
+
* Para lógica de habilitación de botones, validez global, etc.
|
|
60
|
+
*/
|
|
61
|
+
readonly rawError: Signal<string | null>;
|
|
62
|
+
/** True after the input has lost focus at least once. */
|
|
63
|
+
touched: Signal<boolean>;
|
|
64
|
+
/** True after the user has typed at least once. */
|
|
65
|
+
dirty: Signal<boolean>;
|
|
66
|
+
/** Attach to `@input` on any `<input>`, `<select>`, `<textarea>`. */
|
|
67
|
+
readonly onInput: (e: Event) => void;
|
|
68
|
+
/** Attach to `@blur`. */
|
|
69
|
+
readonly onBlur: () => void;
|
|
70
|
+
/** Reset to initial value and clear touched/dirty/error state. */
|
|
71
|
+
reset(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Set the field value programmatically.
|
|
74
|
+
*
|
|
75
|
+
* @param value — new value for the field.
|
|
76
|
+
* @param options — control whether to touch/dirty the field and whether to force
|
|
77
|
+
* validation visibility.
|
|
78
|
+
*/
|
|
79
|
+
setValue(value: T, options?: {
|
|
80
|
+
shouldTouch?: boolean;
|
|
81
|
+
shouldDirty?: boolean;
|
|
82
|
+
shouldValidate?: boolean;
|
|
83
|
+
}): void;
|
|
84
|
+
/**
|
|
85
|
+
* @internal — inject an external error message (server / schema validator).
|
|
86
|
+
* The error clears automatically when the user next edits the field.
|
|
87
|
+
*/
|
|
88
|
+
_setExternalError(msg: string | null): void;
|
|
89
|
+
/** @internal — force error visibility (e.g., on submit). */
|
|
90
|
+
_forceVisible(): void;
|
|
91
|
+
/** @internal — update the stored initial value so a later reset() returns here. */
|
|
92
|
+
_setInitialValue(value: T): void;
|
|
93
|
+
/** @internal — dispose computed signals. */
|
|
94
|
+
_dispose(): void;
|
|
95
|
+
}
|
|
96
|
+
/** Creates a standalone reactive form field with optional validators. */
|
|
97
|
+
export declare function elurField<T, AllValues = unknown>(initialValue: T, fieldValidators?: Validator<T, AllValues>[], validateOn?: ValidateOn, getAllValues?: () => AllValues): FieldState<T>;
|
|
98
|
+
/** Public state of a field array (dynamic list of field groups). */
|
|
99
|
+
export interface FieldArrayState<T extends Record<string, unknown>> {
|
|
100
|
+
/** Reactive list of field group states. */
|
|
101
|
+
readonly fields: Signal<Array<{
|
|
102
|
+
[K in keyof T]: FieldState<T[K]>;
|
|
103
|
+
}>>;
|
|
104
|
+
/** Appends a new item to the end of the array. */
|
|
105
|
+
append(value: T): void;
|
|
106
|
+
/** Removes the item at the given index. */
|
|
107
|
+
remove(index: number): void;
|
|
108
|
+
/**
|
|
109
|
+
* Moves an item from `from` to `to` index.
|
|
110
|
+
* Items between the two positions shift to fill the gap.
|
|
111
|
+
*/
|
|
112
|
+
move(from: number, to: number): void;
|
|
113
|
+
/** Replaces the item at the given index with new values. */
|
|
114
|
+
replace(index: number, value: T): void;
|
|
115
|
+
/** Number of items in the array. Reactive. */
|
|
116
|
+
readonly length: Signal<number>;
|
|
117
|
+
/** Replaces the whole array with a new list of items. */
|
|
118
|
+
setValues(items: T[]): void;
|
|
119
|
+
/** Patches existing items and appends any extras without touching untouched items. */
|
|
120
|
+
patchValues(items: Partial<T>[]): void;
|
|
121
|
+
/** Resets the array to its initial value, optionally updating that initial value. */
|
|
122
|
+
reset(items?: T[]): void;
|
|
123
|
+
/** @internal */
|
|
124
|
+
_dispose(): void;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Creates a reactive array of field groups for dynamic list forms.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const items = elurFieldArray([{ name: "" }], {
|
|
131
|
+
* name: [required()],
|
|
132
|
+
* });
|
|
133
|
+
* items.append({ name: "nuevo" });
|
|
134
|
+
* items.remove(0);
|
|
135
|
+
*/
|
|
136
|
+
export declare function elurFieldArray<T extends Record<string, unknown>>(initialItems: T[], fieldValidators?: {
|
|
137
|
+
[K in keyof T]?: Validator<T[K], unknown>[];
|
|
138
|
+
}, validateOn?: ValidateOn): FieldArrayState<T>;
|
|
139
|
+
/** Field-name map that supports top-level keys and dot-path nested keys. */
|
|
140
|
+
export type FormFields<T extends Record<string, unknown>> = {
|
|
141
|
+
[K in keyof T]: FieldState<T[K]>;
|
|
142
|
+
} & Record<string, FieldState<unknown>>;
|
|
143
|
+
/** Map of field-name → error message for external validation results. */
|
|
144
|
+
export type FieldErrors<T extends Record<string, unknown>> = {
|
|
145
|
+
[K in keyof T]?: string | null;
|
|
146
|
+
} & Record<string, string | null | undefined>;
|
|
147
|
+
/** Validators map supporting both top-level and dot-path keys. */
|
|
148
|
+
export type FormValidators<T extends Record<string, unknown>> = {
|
|
149
|
+
[K in keyof T]?: Validator<T[K], T>[];
|
|
150
|
+
} & Record<string, Validator<any, any>[] | undefined>;
|
|
151
|
+
export type DeepPartial<T> = T extends object ? {
|
|
152
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
153
|
+
} : T;
|
|
154
|
+
export interface FormState<T extends Record<string, unknown>> {
|
|
155
|
+
/** Individual field states — access value, error, event handlers. */
|
|
156
|
+
fields: FormFields<T>;
|
|
157
|
+
/** Computed snapshot of all current field values. */
|
|
158
|
+
readonly values: Signal<T>;
|
|
159
|
+
/** Computed map of all currently visible field errors. */
|
|
160
|
+
readonly errors: Signal<FieldErrors<T>>;
|
|
161
|
+
/**
|
|
162
|
+
* True when no field has a *visible* error.
|
|
163
|
+
*
|
|
164
|
+
* Visibility follows `validateOn`, so this signal reflects what the user
|
|
165
|
+
* currently sees in the UI — not the underlying validity of the data.
|
|
166
|
+
* Use it to drive error summaries, banners, or any UI that should only
|
|
167
|
+
* react to errors the user has been shown.
|
|
168
|
+
*
|
|
169
|
+
* For enabling or disabling submit buttons, use {@link canSubmit} instead.
|
|
170
|
+
*/
|
|
171
|
+
readonly valid: Signal<boolean>;
|
|
172
|
+
/**
|
|
173
|
+
* True when every per-field validator passes against the current values,
|
|
174
|
+
* regardless of `touched`, `dirty`, or `validateOn`.
|
|
175
|
+
*
|
|
176
|
+
* This is the signal to bind to submit buttons:
|
|
177
|
+
*
|
|
178
|
+
* ```ts
|
|
179
|
+
* <button disabled=${() => !form.canSubmit.value || form.isSubmitting.value}>
|
|
180
|
+
* Save
|
|
181
|
+
* </button>
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* Unlike {@link valid}, `canSubmit` does not depend on whether errors are
|
|
185
|
+
* currently visible — a pristine form with empty required fields starts
|
|
186
|
+
* as `false` and flips to `true` the moment all validators pass.
|
|
187
|
+
*
|
|
188
|
+
* External errors injected via {@link setErrors} also flip `canSubmit` to
|
|
189
|
+
* `false` until the user edits the affected field.
|
|
190
|
+
*
|
|
191
|
+
* **Note:** `canSubmit` does not execute `options.validate` (schema-level
|
|
192
|
+
* validators such as Zod). Schema validation runs only on submit, by design,
|
|
193
|
+
* to keep per-keystroke cost predictable. If you need a schema rule to
|
|
194
|
+
* affect `canSubmit` reactively, express it as a per-field validator in
|
|
195
|
+
* `options.validators` instead.
|
|
196
|
+
*/
|
|
197
|
+
readonly canSubmit: Signal<boolean>;
|
|
198
|
+
/** True when at least one field has been modified. */
|
|
199
|
+
readonly dirty: Signal<boolean>;
|
|
200
|
+
/** True when at least one field has been touched (lost focus). */
|
|
201
|
+
readonly touched: Signal<boolean>;
|
|
202
|
+
/** True while the submit callback is executing (async-safe). */
|
|
203
|
+
readonly isSubmitting: Signal<boolean>;
|
|
204
|
+
/** Number of times the form has been submitted (including failed validations). */
|
|
205
|
+
readonly submitCount: Signal<number>;
|
|
206
|
+
/**
|
|
207
|
+
* Wraps a submit callback. Returned handler:
|
|
208
|
+
* 1. Calls `e.preventDefault()`
|
|
209
|
+
* 2. Increments `submitCount` and marks all fields as visible
|
|
210
|
+
* 3. Runs `options.validate` if provided (Zod, etc.)
|
|
211
|
+
* 4. Only calls `fn(values)` if all validations pass
|
|
212
|
+
* 5. Manages `isSubmitting` across async callbacks
|
|
213
|
+
*/
|
|
214
|
+
handleSubmit(fn: (values: T) => void | Promise<void>): (e: Event) => void;
|
|
215
|
+
/**
|
|
216
|
+
* Reset all fields to their initial values. If `newInitialValues` is provided,
|
|
217
|
+
* it becomes the new baseline and subsequent reset() calls will use it.
|
|
218
|
+
*/
|
|
219
|
+
reset(newInitialValues?: T): void;
|
|
220
|
+
/**
|
|
221
|
+
* Set a single field value by its path (top-level or nested dot-path).
|
|
222
|
+
*/
|
|
223
|
+
setValue(path: string, value: unknown, options?: {
|
|
224
|
+
shouldTouch?: boolean;
|
|
225
|
+
shouldDirty?: boolean;
|
|
226
|
+
shouldValidate?: boolean;
|
|
227
|
+
}): void;
|
|
228
|
+
/**
|
|
229
|
+
* Set multiple field values at once.
|
|
230
|
+
*
|
|
231
|
+
* @param values — partial object with top-level and/or nested values.
|
|
232
|
+
* @param options — control whether to preserve existing touched/dirty/error state.
|
|
233
|
+
*/
|
|
234
|
+
setValues(values: DeepPartial<T>, options?: {
|
|
235
|
+
keepDirty?: boolean;
|
|
236
|
+
keepTouched?: boolean;
|
|
237
|
+
keepErrors?: boolean;
|
|
238
|
+
}): void;
|
|
239
|
+
/**
|
|
240
|
+
* Inject external errors (e.g., from a server response) into specific fields.
|
|
241
|
+
* Each field's error clears automatically the next time the user edits it.
|
|
242
|
+
*/
|
|
243
|
+
setErrors(errors: FieldErrors<T>): void;
|
|
244
|
+
/**
|
|
245
|
+
* Disposes all internal computed signals.
|
|
246
|
+
* Call in `onUnmount` when the form lives inside a component.
|
|
247
|
+
*/
|
|
248
|
+
dispose(): void;
|
|
249
|
+
}
|
|
250
|
+
export interface FormOptions<T extends Record<string, unknown>> {
|
|
251
|
+
/** Per-field validators. Each validator receives `(value, allValues?)`. */
|
|
252
|
+
validators?: FormValidators<T>;
|
|
253
|
+
/**
|
|
254
|
+
* Controls when validation errors become visible.
|
|
255
|
+
* - `"blur"` — after the field loses focus (default)
|
|
256
|
+
* - `"input"` — as soon as the user types
|
|
257
|
+
* - `"submit"` — only after the first submit attempt
|
|
258
|
+
*/
|
|
259
|
+
validateOn?: ValidateOn;
|
|
260
|
+
/**
|
|
261
|
+
* Optional schema-level validator — runs on submit after built-in validators pass.
|
|
262
|
+
* Return `null` / `undefined` if valid, or a field→error map if not.
|
|
263
|
+
* String arrays are accepted (first element shown per field).
|
|
264
|
+
*
|
|
265
|
+
* @example Zod interop
|
|
266
|
+
* ```typescript
|
|
267
|
+
* validate(values) {
|
|
268
|
+
* const r = schema.safeParse(values);
|
|
269
|
+
* if (r.success) return null;
|
|
270
|
+
* return Object.fromEntries(
|
|
271
|
+
* Object.entries(r.error.flatten().fieldErrors)
|
|
272
|
+
* .map(([k, v]) => [k, v?.[0]])
|
|
273
|
+
* );
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
validate?: (values: T) => Record<string, string | string[] | null | undefined> | null | undefined;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates a managed form with reactive fields, built-in validation,
|
|
281
|
+
* schema-level validation (Zod/Valibot/Yup/custom), and submit handling.
|
|
282
|
+
*/
|
|
283
|
+
export declare function createForm<T extends Record<string, unknown>>(initialValues: T, options?: FormOptions<T>): FormState<T>;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { Signal } from "./reactivity.js";
|
|
2
|
+
/**
|
|
3
|
+
* A validator function. Return an error string when invalid, or `null` /
|
|
4
|
+
* `undefined` when valid.
|
|
5
|
+
*
|
|
6
|
+
* `allValues` is provided when the validator runs inside `createForm`, enabling
|
|
7
|
+
* cross-field validation (e.g., "confirm password matches password").
|
|
8
|
+
*
|
|
9
|
+
* **Validators must be pure.** They are invoked reactively whenever the field
|
|
10
|
+
* value changes — potentially many times per keystroke in forms with
|
|
11
|
+
* cross-field rules — and may run inside computed signals that assume
|
|
12
|
+
* deterministic output. Do not perform I/O, mutate external state, or rely on
|
|
13
|
+
* `Date.now()` / `Math.random()` inside a validator. For asynchronous checks
|
|
14
|
+
* (uniqueness, server-side rules), submit the form and inject the result via
|
|
15
|
+
* {@link FormState.setErrors}.
|
|
16
|
+
*/
|
|
17
|
+
export type Validator<T, AllValues = unknown> = (value: T, allValues?: AllValues) => string | null | undefined;
|
|
18
|
+
export declare function required(message?: string): Validator<unknown>;
|
|
19
|
+
export declare function minLength(n: number, message?: string): Validator<string>;
|
|
20
|
+
export declare function maxLength(n: number, message?: string): Validator<string>;
|
|
21
|
+
export declare function pattern(regex: RegExp, message?: string): Validator<string>;
|
|
22
|
+
export declare function email(message?: string): Validator<string>;
|
|
23
|
+
export declare function min(n: number, message?: string): Validator<number>;
|
|
24
|
+
export declare function max(n: number, message?: string): Validator<number>;
|
|
25
|
+
/** Creates a typed custom validator compatible with `elurField` and `createForm`. */
|
|
26
|
+
export declare function createValidator<T, AllValues = unknown>(fn: (value: T, allValues?: AllValues) => string | null | undefined): Validator<T, AllValues>;
|
|
27
|
+
/** All built-in validators grouped as a namespace. Extensible via `extendValidators`. */
|
|
28
|
+
export declare const validators: {
|
|
29
|
+
readonly required: typeof required;
|
|
30
|
+
readonly minLength: typeof minLength;
|
|
31
|
+
readonly maxLength: typeof maxLength;
|
|
32
|
+
readonly email: typeof email;
|
|
33
|
+
readonly pattern: typeof pattern;
|
|
34
|
+
readonly min: typeof min;
|
|
35
|
+
readonly max: typeof max;
|
|
36
|
+
};
|
|
37
|
+
/** Shape of the built-in `validators` namespace. Used as the base type for `extendValidators`. */
|
|
38
|
+
export type ValidatorsBase = typeof validators;
|
|
39
|
+
/**
|
|
40
|
+
* Merges custom validator factories into the built-in namespace.
|
|
41
|
+
* Returns a new object — the original is never mutated.
|
|
42
|
+
*/
|
|
43
|
+
export declare function extendValidators<E extends Record<string, (...args: any[]) => Validator<any>>>(base: ValidatorsBase, extensions: E): ValidatorsBase & E;
|
|
44
|
+
/**
|
|
45
|
+
* Controls when validation errors become visible.
|
|
46
|
+
* - `"blur"` — after the field loses focus (default)
|
|
47
|
+
* - `"input"` — as soon as the user types
|
|
48
|
+
* - `"submit"` — only after the first submit attempt
|
|
49
|
+
*/
|
|
50
|
+
export type ValidateOn = "blur" | "input" | "submit";
|
|
51
|
+
/** Public state of a single form field. */
|
|
52
|
+
export interface FieldState<T> {
|
|
53
|
+
/** Current value — read/write signal. */
|
|
54
|
+
value: Signal<T>;
|
|
55
|
+
/** Error visible según validateOn. Para UI. */
|
|
56
|
+
readonly error: Signal<string | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Error real, ignorando validateOn/touched/dirty.
|
|
59
|
+
* Para lógica de habilitación de botones, validez global, etc.
|
|
60
|
+
*/
|
|
61
|
+
readonly rawError: Signal<string | null>;
|
|
62
|
+
/** True after the input has lost focus at least once. */
|
|
63
|
+
touched: Signal<boolean>;
|
|
64
|
+
/** True after the user has typed at least once. */
|
|
65
|
+
dirty: Signal<boolean>;
|
|
66
|
+
/** Attach to `@input` on any `<input>`, `<select>`, `<textarea>`. */
|
|
67
|
+
readonly onInput: (e: Event) => void;
|
|
68
|
+
/** Attach to `@blur`. */
|
|
69
|
+
readonly onBlur: () => void;
|
|
70
|
+
/** Reset to initial value and clear touched/dirty/error state. */
|
|
71
|
+
reset(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Set the field value programmatically.
|
|
74
|
+
*
|
|
75
|
+
* @param value — new value for the field.
|
|
76
|
+
* @param options — control whether to touch/dirty the field and whether to force
|
|
77
|
+
* validation visibility.
|
|
78
|
+
*/
|
|
79
|
+
setValue(value: T, options?: {
|
|
80
|
+
shouldTouch?: boolean;
|
|
81
|
+
shouldDirty?: boolean;
|
|
82
|
+
shouldValidate?: boolean;
|
|
83
|
+
}): void;
|
|
84
|
+
/**
|
|
85
|
+
* @internal — inject an external error message (server / schema validator).
|
|
86
|
+
* The error clears automatically when the user next edits the field.
|
|
87
|
+
*/
|
|
88
|
+
_setExternalError(msg: string | null): void;
|
|
89
|
+
/** @internal — force error visibility (e.g., on submit). */
|
|
90
|
+
_forceVisible(): void;
|
|
91
|
+
/** @internal — update the stored initial value so a later reset() returns here. */
|
|
92
|
+
_setInitialValue(value: T): void;
|
|
93
|
+
/** @internal — dispose computed signals. */
|
|
94
|
+
_dispose(): void;
|
|
95
|
+
}
|
|
96
|
+
/** Creates a standalone reactive form field with optional validators. */
|
|
97
|
+
export declare function elurField<T, AllValues = unknown>(initialValue: T, fieldValidators?: Validator<T, AllValues>[], validateOn?: ValidateOn, getAllValues?: () => AllValues): FieldState<T>;
|
|
98
|
+
/** Public state of a field array (dynamic list of field groups). */
|
|
99
|
+
export interface FieldArrayState<T extends Record<string, unknown>> {
|
|
100
|
+
/** Reactive list of field group states. */
|
|
101
|
+
readonly fields: Signal<Array<{
|
|
102
|
+
[K in keyof T]: FieldState<T[K]>;
|
|
103
|
+
}>>;
|
|
104
|
+
/** Appends a new item to the end of the array. */
|
|
105
|
+
append(value: T): void;
|
|
106
|
+
/** Removes the item at the given index. */
|
|
107
|
+
remove(index: number): void;
|
|
108
|
+
/**
|
|
109
|
+
* Moves an item from `from` to `to` index.
|
|
110
|
+
* Items between the two positions shift to fill the gap.
|
|
111
|
+
*/
|
|
112
|
+
move(from: number, to: number): void;
|
|
113
|
+
/** Replaces the item at the given index with new values. */
|
|
114
|
+
replace(index: number, value: T): void;
|
|
115
|
+
/** Number of items in the array. Reactive. */
|
|
116
|
+
readonly length: Signal<number>;
|
|
117
|
+
/** Replaces the whole array with a new list of items. */
|
|
118
|
+
setValues(items: T[]): void;
|
|
119
|
+
/** Patches existing items and appends any extras without touching untouched items. */
|
|
120
|
+
patchValues(items: Partial<T>[]): void;
|
|
121
|
+
/** Resets the array to its initial value, optionally updating that initial value. */
|
|
122
|
+
reset(items?: T[]): void;
|
|
123
|
+
/** @internal */
|
|
124
|
+
_dispose(): void;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Creates a reactive array of field groups for dynamic list forms.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* const items = elurFieldArray([{ name: "" }], {
|
|
131
|
+
* name: [required()],
|
|
132
|
+
* });
|
|
133
|
+
* items.append({ name: "nuevo" });
|
|
134
|
+
* items.remove(0);
|
|
135
|
+
*/
|
|
136
|
+
export declare function elurFieldArray<T extends Record<string, unknown>>(initialItems: T[], fieldValidators?: {
|
|
137
|
+
[K in keyof T]?: Validator<T[K], unknown>[];
|
|
138
|
+
}, validateOn?: ValidateOn): FieldArrayState<T>;
|
|
139
|
+
/** Field-name map that supports top-level keys and dot-path nested keys. */
|
|
140
|
+
export type FormFields<T extends Record<string, unknown>> = {
|
|
141
|
+
[K in keyof T]: FieldState<T[K]>;
|
|
142
|
+
} & Record<string, FieldState<unknown>>;
|
|
143
|
+
/** Map of field-name → error message for external validation results. */
|
|
144
|
+
export type FieldErrors<T extends Record<string, unknown>> = {
|
|
145
|
+
[K in keyof T]?: string | null;
|
|
146
|
+
} & Record<string, string | null | undefined>;
|
|
147
|
+
/** Validators map supporting both top-level and dot-path keys. */
|
|
148
|
+
export type FormValidators<T extends Record<string, unknown>> = {
|
|
149
|
+
[K in keyof T]?: Validator<T[K], T>[];
|
|
150
|
+
} & Record<string, Validator<any, any>[] | undefined>;
|
|
151
|
+
export type DeepPartial<T> = T extends object ? {
|
|
152
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
153
|
+
} : T;
|
|
154
|
+
export interface FormState<T extends Record<string, unknown>> {
|
|
155
|
+
/** Individual field states — access value, error, event handlers. */
|
|
156
|
+
fields: FormFields<T>;
|
|
157
|
+
/** Computed snapshot of all current field values. */
|
|
158
|
+
readonly values: Signal<T>;
|
|
159
|
+
/** Computed map of all currently visible field errors. */
|
|
160
|
+
readonly errors: Signal<FieldErrors<T>>;
|
|
161
|
+
/**
|
|
162
|
+
* True when no field has a *visible* error.
|
|
163
|
+
*
|
|
164
|
+
* Visibility follows `validateOn`, so this signal reflects what the user
|
|
165
|
+
* currently sees in the UI — not the underlying validity of the data.
|
|
166
|
+
* Use it to drive error summaries, banners, or any UI that should only
|
|
167
|
+
* react to errors the user has been shown.
|
|
168
|
+
*
|
|
169
|
+
* For enabling or disabling submit buttons, use {@link canSubmit} instead.
|
|
170
|
+
*/
|
|
171
|
+
readonly valid: Signal<boolean>;
|
|
172
|
+
/**
|
|
173
|
+
* True when every per-field validator passes against the current values,
|
|
174
|
+
* regardless of `touched`, `dirty`, or `validateOn`.
|
|
175
|
+
*
|
|
176
|
+
* This is the signal to bind to submit buttons:
|
|
177
|
+
*
|
|
178
|
+
* ```ts
|
|
179
|
+
* <button disabled=${() => !form.canSubmit.value || form.isSubmitting.value}>
|
|
180
|
+
* Save
|
|
181
|
+
* </button>
|
|
182
|
+
* ```
|
|
183
|
+
*
|
|
184
|
+
* Unlike {@link valid}, `canSubmit` does not depend on whether errors are
|
|
185
|
+
* currently visible — a pristine form with empty required fields starts
|
|
186
|
+
* as `false` and flips to `true` the moment all validators pass.
|
|
187
|
+
*
|
|
188
|
+
* External errors injected via {@link setErrors} also flip `canSubmit` to
|
|
189
|
+
* `false` until the user edits the affected field.
|
|
190
|
+
*
|
|
191
|
+
* **Note:** `canSubmit` does not execute `options.validate` (schema-level
|
|
192
|
+
* validators such as Zod). Schema validation runs only on submit, by design,
|
|
193
|
+
* to keep per-keystroke cost predictable. If you need a schema rule to
|
|
194
|
+
* affect `canSubmit` reactively, express it as a per-field validator in
|
|
195
|
+
* `options.validators` instead.
|
|
196
|
+
*/
|
|
197
|
+
readonly canSubmit: Signal<boolean>;
|
|
198
|
+
/** True when at least one field has been modified. */
|
|
199
|
+
readonly dirty: Signal<boolean>;
|
|
200
|
+
/** True when at least one field has been touched (lost focus). */
|
|
201
|
+
readonly touched: Signal<boolean>;
|
|
202
|
+
/** True while the submit callback is executing (async-safe). */
|
|
203
|
+
readonly isSubmitting: Signal<boolean>;
|
|
204
|
+
/** Number of times the form has been submitted (including failed validations). */
|
|
205
|
+
readonly submitCount: Signal<number>;
|
|
206
|
+
/**
|
|
207
|
+
* Wraps a submit callback. Returned handler:
|
|
208
|
+
* 1. Calls `e.preventDefault()`
|
|
209
|
+
* 2. Increments `submitCount` and marks all fields as visible
|
|
210
|
+
* 3. Runs `options.validate` if provided (Zod, etc.)
|
|
211
|
+
* 4. Only calls `fn(values)` if all validations pass
|
|
212
|
+
* 5. Manages `isSubmitting` across async callbacks
|
|
213
|
+
*/
|
|
214
|
+
handleSubmit(fn: (values: T) => void | Promise<void>): (e: Event) => void;
|
|
215
|
+
/**
|
|
216
|
+
* Reset all fields to their initial values. If `newInitialValues` is provided,
|
|
217
|
+
* it becomes the new baseline and subsequent reset() calls will use it.
|
|
218
|
+
*/
|
|
219
|
+
reset(newInitialValues?: T): void;
|
|
220
|
+
/**
|
|
221
|
+
* Set a single field value by its path (top-level or nested dot-path).
|
|
222
|
+
*/
|
|
223
|
+
setValue(path: string, value: unknown, options?: {
|
|
224
|
+
shouldTouch?: boolean;
|
|
225
|
+
shouldDirty?: boolean;
|
|
226
|
+
shouldValidate?: boolean;
|
|
227
|
+
}): void;
|
|
228
|
+
/**
|
|
229
|
+
* Set multiple field values at once.
|
|
230
|
+
*
|
|
231
|
+
* @param values — partial object with top-level and/or nested values.
|
|
232
|
+
* @param options — control whether to preserve existing touched/dirty/error state.
|
|
233
|
+
*/
|
|
234
|
+
setValues(values: DeepPartial<T>, options?: {
|
|
235
|
+
keepDirty?: boolean;
|
|
236
|
+
keepTouched?: boolean;
|
|
237
|
+
keepErrors?: boolean;
|
|
238
|
+
}): void;
|
|
239
|
+
/**
|
|
240
|
+
* Inject external errors (e.g., from a server response) into specific fields.
|
|
241
|
+
* Each field's error clears automatically the next time the user edits it.
|
|
242
|
+
*/
|
|
243
|
+
setErrors(errors: FieldErrors<T>): void;
|
|
244
|
+
/**
|
|
245
|
+
* Disposes all internal computed signals.
|
|
246
|
+
* Call in `onUnmount` when the form lives inside a component.
|
|
247
|
+
*/
|
|
248
|
+
dispose(): void;
|
|
249
|
+
}
|
|
250
|
+
export interface FormOptions<T extends Record<string, unknown>> {
|
|
251
|
+
/** Per-field validators. Each validator receives `(value, allValues?)`. */
|
|
252
|
+
validators?: FormValidators<T>;
|
|
253
|
+
/**
|
|
254
|
+
* Controls when validation errors become visible.
|
|
255
|
+
* - `"blur"` — after the field loses focus (default)
|
|
256
|
+
* - `"input"` — as soon as the user types
|
|
257
|
+
* - `"submit"` — only after the first submit attempt
|
|
258
|
+
*/
|
|
259
|
+
validateOn?: ValidateOn;
|
|
260
|
+
/**
|
|
261
|
+
* Optional schema-level validator — runs on submit after built-in validators pass.
|
|
262
|
+
* Return `null` / `undefined` if valid, or a field→error map if not.
|
|
263
|
+
* String arrays are accepted (first element shown per field).
|
|
264
|
+
*
|
|
265
|
+
* @example Zod interop
|
|
266
|
+
* ```typescript
|
|
267
|
+
* validate(values) {
|
|
268
|
+
* const r = schema.safeParse(values);
|
|
269
|
+
* if (r.success) return null;
|
|
270
|
+
* return Object.fromEntries(
|
|
271
|
+
* Object.entries(r.error.flatten().fieldErrors)
|
|
272
|
+
* .map(([k, v]) => [k, v?.[0]])
|
|
273
|
+
* );
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
validate?: (values: T) => Record<string, string | string[] | null | undefined> | null | undefined;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates a managed form with reactive fields, built-in validation,
|
|
281
|
+
* schema-level validation (Zod/Valibot/Yup/custom), and submit handling.
|
|
282
|
+
*/
|
|
283
|
+
export declare function createForm<T extends Record<string, unknown>>(initialValues: T, options?: FormOptions<T>): FormState<T>;
|