form-father 0.2.11 → 0.4.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,28 @@
1
+ import { type ValidatorFieldElement, type ValidatorResult } from './validators';
2
+ type MaybePromise<T> = T | Promise<T>;
3
+ export type SchemaParseResult = {
4
+ success: true;
5
+ data?: unknown;
6
+ } | {
7
+ success: false;
8
+ error?: unknown;
9
+ issues?: Array<{
10
+ message?: string;
11
+ }>;
12
+ };
13
+ export type SchemaLike = {
14
+ safeParse?: (value: unknown) => MaybePromise<SchemaParseResult>;
15
+ parse?: (value: unknown) => MaybePromise<unknown>;
16
+ } | ((value: unknown) => MaybePromise<boolean | ValidatorResult | void>);
17
+ export type AdapterValidator = (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean | ValidatorResult>;
18
+ export interface SchemaValidatorOptions {
19
+ message?: string | ((error: unknown) => string);
20
+ mapValue?: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => unknown;
21
+ }
22
+ export interface RegisterSchemaValidatorOptions extends SchemaValidatorOptions {
23
+ override?: boolean;
24
+ }
25
+ export declare function createSchemaValidator(schema: SchemaLike, options?: SchemaValidatorOptions): AdapterValidator;
26
+ export declare function registerSchemaValidator(name: string, schema: SchemaLike, defaultMessage: string, options?: RegisterSchemaValidatorOptions): void;
27
+ export declare function createFieldValidator(predicate: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean>, message?: string): AdapterValidator;
28
+ export {};
@@ -1,14 +1,50 @@
1
1
  export * from './validators';
2
+ export * from './adapters';
2
3
  export { serializeToFormData, isEmailValid, isUrlValid, parseCommonResponseProperties, closest, isPhoneValid, blockScrollBody, unblockScrollBody, serializeFormToJSON, } from './helpers';
3
4
  export type ValidationRule = string | {
4
5
  rule: string;
5
6
  params?: any;
6
7
  };
8
+ export type ValidationTrigger = 'submit' | 'input' | 'blur' | 'change';
9
+ export type ValidationErrorSource = 'client' | 'server' | 'manual';
10
+ export type FieldReference = string | FormFieldElement;
11
+ export declare const FORM_ERROR_FIELD = "_form";
12
+ export interface ValidationError {
13
+ field: string;
14
+ rule: string;
15
+ message: string;
16
+ source: ValidationErrorSource;
17
+ }
18
+ export interface SubmitResult {
19
+ success: boolean;
20
+ response?: Response;
21
+ responseBody?: ResponseBody;
22
+ error?: unknown;
23
+ }
24
+ export interface FormResetOptions {
25
+ clearErrors?: boolean;
26
+ }
27
+ export interface ValidationIssue {
28
+ field?: string;
29
+ rule?: string;
30
+ message: string;
31
+ source?: ValidationErrorSource;
32
+ }
33
+ export type ValidationIssueInput = string | ValidationIssue | ErrorResponse | Record<string, string> | Array<string | ValidationIssue | ErrorResponse>;
34
+ export interface FormValidatorContext {
35
+ form: Form;
36
+ formElement: HTMLFormElement;
37
+ values: Record<string, any>;
38
+ errors: ValidationError[];
39
+ }
40
+ export type FormValidatorResult = boolean | string | void | ValidationIssue | Array<string | ValidationIssue>;
41
+ export type FormValidator = (context: FormValidatorContext) => FormValidatorResult | Promise<FormValidatorResult>;
7
42
  export type ValidationSchema = Record<string, {
8
43
  rules: ValidationRule[];
9
44
  selector?: string;
10
45
  messages?: Record<string, string>;
11
46
  }>;
47
+ export type FormFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
12
48
  /** Параметры формы. */
13
49
  export interface FormOptions {
14
50
  /**
@@ -24,6 +60,20 @@ export interface FormOptions {
24
60
  * @param formInstance - Инстанс формы.
25
61
  */
26
62
  onAfterValidate?: (isValid: boolean, formInstance: Form) => void;
63
+ /**
64
+ * Функция обратного вызова. Запускается после неуспешной клиентской валидации.
65
+ *
66
+ * @param errors - Список ошибок валидации.
67
+ * @param formInstance - Инстанс формы.
68
+ */
69
+ onValidationError?: (errors: ValidationError[], formInstance: Form) => void;
70
+ /**
71
+ * Функция обратного вызова. Запускается перед отправкой уже валидной формы. Если вернуть `false`, отправка будет
72
+ * отменена.
73
+ *
74
+ * @param formInstance - Инстанс формы.
75
+ */
76
+ onBeforeSubmit?: (formInstance: Form) => void | boolean | Promise<void | boolean>;
27
77
  /**
28
78
  * Функция обратного вызова. Запускается, когда форма отправляется.
29
79
  *
@@ -51,10 +101,19 @@ export interface FormOptions {
51
101
  * @param formInstance - Инстанс формы.
52
102
  */
53
103
  onResponseUnsuccess?: (responseBody: ResponseBody, formInstance: Form) => void;
104
+ /**
105
+ * Функция обратного вызова. Запускается при исключении во время отправки формы.
106
+ *
107
+ * @param error - Ошибка отправки.
108
+ * @param formInstance - Инстанс формы.
109
+ */
110
+ onSubmitError?: (error: unknown, formInstance: Form) => void;
54
111
  /** Нужно ли показывать loader в кнопке. По умолчанию `true`. */
55
112
  showLoaderButton?: boolean;
56
113
  /** Нужно ли проскроливать до первого по порядку элемента с ошибкой. По умолчанию `true`. */
57
114
  scrollToFirstErroredInput?: boolean;
115
+ /** Нужно ли переводить фокус в первое ошибочное поле. По умолчанию `false`. */
116
+ focusFirstErroredInput?: boolean;
58
117
  /** Кастомный тип ошибки. */
59
118
  customTypeError?: any;
60
119
  /** Цвет лоадера. */
@@ -68,10 +127,27 @@ export interface FormOptions {
68
127
  * `.input-primary`.
69
128
  */
70
129
  inputWrapperSelector?: string;
130
+ /**
131
+ * События, на которых нужно запускать валидацию поля. `submit` всегда обрабатывается отдельно. По умолчанию
132
+ * `submit`.
133
+ */
134
+ validateOn?: ValidationTrigger | ValidationTrigger[];
135
+ /** События, на которых нужно повторно проверять уже ошибочные поля. По умолчанию `input` и `change`. */
136
+ revalidateOn?: ValidationTrigger | ValidationTrigger[];
137
+ /** Задержка live-валидации в миллисекундах. По умолчанию `0`. */
138
+ validationDebounce?: number;
139
+ /** Атрибут с CSS-селектором контейнера для ошибки поля. По умолчанию `data-error-container`. */
140
+ errorContainerAttribute?: string;
141
+ /** Атрибут, куда пишется состояние поля: `validating`, `valid` или `invalid`. */
142
+ validationStateAttribute?: string;
143
+ /** Следить за динамически добавленными/удалёнными полями и submit-кнопками. По умолчанию `false`. */
144
+ observeMutations?: boolean;
71
145
  /** Функция для обёртки отправляемых данных. */
72
146
  wrapData?: (data: Record<string, any>) => Record<string, any>;
73
147
  /** Схема валидации: поле → массив правил + override-сообщения */
74
148
  validationSchema?: ValidationSchema;
149
+ /** Валидаторы всей формы: cross-field, conditional required, date ranges и другие проверки над `getValues()`. */
150
+ formValidators?: FormValidator | FormValidator[];
75
151
  }
76
152
  export interface ErrorResponse {
77
153
  name: string;
@@ -98,6 +174,12 @@ export default class Form {
98
174
  private inputs;
99
175
  private _onSubmitHandler?;
100
176
  private _onSubmitClickHandler?;
177
+ private fieldEventHandlers;
178
+ private liveValidationTimers;
179
+ private fieldValidationTokens;
180
+ private fieldValidationTokenCounter;
181
+ private mutationObserver?;
182
+ private errors;
101
183
  private static get defaultParams();
102
184
  private static set defaultParams(value);
103
185
  static get defaultValidationSchema(): ValidationSchema;
@@ -110,6 +192,32 @@ export default class Form {
110
192
  */
111
193
  constructor($el: HTMLElement, options?: FormOptions);
112
194
  getOptions(): FormOptions;
195
+ /**
196
+ * Инициализирует сразу несколько форм по CSS-селектору. Если форма уже инициализирована, её настройки обновятся.
197
+ *
198
+ * @param selector - CSS-селектор форм. По умолчанию `form[data-form-father]`.
199
+ * @param options - Общие параметры для найденных форм.
200
+ */
201
+ static initAll(selector?: string, options?: FormOptions): Form[];
202
+ /** Обновляет настройки конкретного инстанса формы без пересоздания обработчиков вручную. */
203
+ updateOptions(options: Partial<FormOptions>): this;
204
+ /** Возвращает текущий список ошибок формы. */
205
+ getErrors(): ValidationError[];
206
+ /** Возвращает значения формы обычным объектом. */
207
+ getValues(): Record<string, any>;
208
+ private normalizeValueList;
209
+ /** Проставляет значения полей по `name` и уведомляет UI через `input`/`change` события. */
210
+ setValues(values: Record<string, any>): this;
211
+ /** Снимает все клиентские и серверные ошибки с формы, не меняя значения полей. */
212
+ clearErrors(): this;
213
+ private isErrorResponse;
214
+ private normalizeValidationIssues;
215
+ private applyValidationIssue;
216
+ /** Показывает несколько ошибок формы или полей: из backend map/array, `ErrorResponse[]` или form validators. */
217
+ setErrors(issues: ValidationIssueInput, source?: ValidationErrorSource): this;
218
+ private validateFormValidators;
219
+ /** Сбрасывает форму через native `reset()` и опционально очищает ошибки. */
220
+ reset(options?: FormResetOptions): this;
113
221
  /**
114
222
  * Обновляет параметры по умолчанию для настроек формы. Метод объединяет переданные параметры с уже существующими
115
223
  * параметрами по умолчанию.
@@ -118,8 +226,17 @@ export default class Form {
118
226
  * свойства, которые необходимо обновить; остальные сохранятся без изменений.
119
227
  */
120
228
  static setDefaultParams(params: Partial<FormOptions>): void;
229
+ private refreshControls;
121
230
  private findSubmitElements;
231
+ private bindSubmitHandlers;
122
232
  private initialization;
233
+ private handleSubmitEvent;
234
+ private normalizeTriggers;
235
+ private clearFieldValidationEvents;
236
+ private bindFieldValidationEvents;
237
+ private scheduleFieldValidation;
238
+ private setupMutationObserver;
239
+ private getErrorContainer;
123
240
  /**
124
241
  * Показывает ошибку для поля ввода.
125
242
  *
@@ -143,17 +260,49 @@ export default class Form {
143
260
  * @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
144
261
  */
145
262
  private scrollToFirstErroredInput;
263
+ private focusFirstErroredInput;
264
+ private getValidationSchema;
265
+ private getAllFields;
266
+ private getFieldKey;
267
+ private hasFieldError;
268
+ private clearFieldErrorRecord;
269
+ private setErrorRecord;
270
+ private getValidationStateAttribute;
271
+ private setFieldValidationState;
272
+ private startFieldValidation;
273
+ private isLatestFieldValidation;
274
+ private finishFieldValidation;
275
+ private clearFieldValidationState;
276
+ private setValidationError;
277
+ private resolveField;
278
+ private parseRuleString;
279
+ private parseValidationAttribute;
280
+ private getRuleName;
281
+ private getRuleIdentity;
282
+ private collectRules;
283
+ private getRuleMessage;
284
+ private fieldMatchesSchemaDefinition;
285
+ private getFieldValidationConfig;
286
+ private getValidationFields;
287
+ private validateInput;
288
+ private runFieldValidation;
289
+ private getRadioGroup;
290
+ /** Показывает ошибку конкретного поля по имени или DOM-элементу. */
291
+ showFieldError(field: FieldReference, message: string, source?: ValidationErrorSource, rule?: string): boolean;
292
+ /** Проверяет одно поле по имени или DOM-элементу. */
293
+ validateField(field: FieldReference): Promise<boolean>;
146
294
  /**
147
295
  * Проверяет все поля ввода.
148
296
  *
149
- * Порядок для КАЖДОГО `<input>`:
297
+ * Порядок для каждого поля:
150
298
  *
151
299
  * 1. `required` — если атрибут `required`
152
300
  * 2. правила из `validationSchema`
153
- * 3. правила из `data-custom-validate`
301
+ * 3. правила из `data-validate`
302
+ * 4. правила из `data-custom-validate`
154
303
  *
155
304
  * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
156
- * валидации, поэтому валидационный цикл больше не проверяет их наличие.
305
+ * валидации.
157
306
  */
158
307
  validate($block?: HTMLElement): Promise<boolean>;
159
308
  /** Блокирует кнопку отправки данных. */
@@ -197,5 +346,5 @@ export default class Form {
197
346
  private parseResponseBody;
198
347
  private handleUnsuccessfulResponse;
199
348
  private sendData;
200
- private submit;
349
+ submit(): Promise<SubmitResult | undefined>;
201
350
  }
@@ -1,9 +1,10 @@
1
1
  export type ValidatorEffectCtx = {
2
2
  value: string;
3
- $input: HTMLInputElement | HTMLTextAreaElement;
3
+ $input: ValidatorFieldElement;
4
4
  $form: HTMLElement;
5
5
  params?: any;
6
6
  };
7
+ export type ValidatorFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
7
8
  export type ValidatorResult = {
8
9
  valid: boolean;
9
10
  /** Сообщение об ошибке, если нужно переопределить дефолт */
@@ -13,7 +14,7 @@ export type ValidatorResult = {
13
14
  /** Остановить дальнейшие правила для поля (даже если valid=true). Полезно, если валидатор полностью «ведёт» поле. */
14
15
  stopOthers?: boolean;
15
16
  };
16
- type ValidatorFn = (value: string, $input: HTMLInputElement | HTMLTextAreaElement, $form: HTMLElement, params?: any) => boolean | Promise<boolean> | ValidatorResult | Promise<ValidatorResult>;
17
+ export type ValidatorFn = (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => boolean | ValidatorResult | Promise<boolean | ValidatorResult>;
17
18
  interface Validator {
18
19
  fn: ValidatorFn;
19
20
  defaultMessage: string;
@@ -0,0 +1,225 @@
1
+ # Form Father
2
+
3
+ [![npm version](https://img.shields.io/npm/v/form-father)](https://www.npmjs.com/package/form-father)
4
+ [![npm downloads](https://img.shields.io/npm/dm/form-father)](https://www.npmjs.com/package/form-father)
5
+
6
+ [🇷🇺 Документация на русском](https://github.com/Poliklot/form-father/blob/master/README.md)
7
+
8
+ **Form Father** is a library for handling forms in pure JavaScript, providing convenient validation and form submission
9
+ with TypeScript support.
10
+
11
+ ## Installation
12
+
13
+ Install the library via npm:
14
+
15
+ ```bash
16
+ npm install form-father
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```javascript
22
+ import Form, { isUrlValid, serializeToFormData } from 'form-father';
23
+
24
+ Form.setDefaultParams({
25
+ showLoaderButton: false,
26
+ scrollToFirstErroredInput: false,
27
+ logging: true,
28
+ });
29
+
30
+ const formElement = document.querySelector('#myForm');
31
+ const options = {
32
+ onSubmit: formInstance => {
33
+ // Actions on form submission
34
+ },
35
+ onResponse: (responseBody, formInstance) => {
36
+ // Actions on receiving server response
37
+ },
38
+ // Other options...
39
+ };
40
+
41
+ const form = new Form(formElement, options);
42
+ const simpleForm = new Form(document.querySelector('#simpleForm')); // options are optional
43
+
44
+ const forms = Form.initAll('form[data-form-father]', {
45
+ inputWrapperSelector: '.field',
46
+ validateOn: ['blur', 'change'],
47
+ revalidateOn: ['input', 'change'],
48
+ });
49
+ ```
50
+
51
+ ```ts
52
+ import Form, {
53
+ FORM_ERROR_FIELD,
54
+ type FormOptions,
55
+ type ValidationSchema,
56
+ type ResponseBody,
57
+ type ValidationError,
58
+ type SubmitResult,
59
+ type FormResetOptions,
60
+ type FormValidator,
61
+ type ValidationIssue,
62
+ } from 'form-father';
63
+ ```
64
+
65
+ ## Options
66
+
67
+ - **onSubmit**: Callback function executed on form submission.
68
+ - **onBeforeSubmit**: Runs before sending an already valid form; returning `false` cancels the request.
69
+ - **onSubmitError**: Runs when form submission throws.
70
+ - **onResponse**: Callback function executed when a server response is received.
71
+ - **onResponseSuccess**: Callback function executed for a successful HTTP response with `success: true`.
72
+ - **onResponseUnsuccess**: Callback function executed for unsuccessful HTTP responses, `success !== true`, or invalid JSON.
73
+ - **onValidationError**: Runs when client-side validation fails.
74
+ - **showLoaderButton**: Whether to display a loader in the submit button. Defaults to `true`.
75
+ - **scrollToFirstErroredInput**: Whether to scroll to the first input with an error. Defaults to `true`.
76
+ - **focusFirstErroredInput**: Whether to focus the first invalid field. Defaults to `false`.
77
+ - **customTypeError**: Custom error type.
78
+ - **loaderColor**: Color of the loader in the submit button.
79
+ - **logging**: Specifies whether to log data to the console. Defaults to false.
80
+ - **validateOn**: Field live-validation events: `submit`, `input`, `blur`, `change`.
81
+ - **revalidateOn**: Events used to re-check already invalid fields. Defaults to `input` and `change`.
82
+ - **validationDebounce**: Live-validation delay in milliseconds.
83
+ - **errorContainerAttribute**: Attribute containing the CSS selector for a custom error container. Defaults to
84
+ `data-error-container`.
85
+ - **validationStateAttribute**: Field state attribute: `validating`, `valid`, or `invalid`. Defaults to
86
+ `data-form-father-state`.
87
+ - **observeMutations**: Watches dynamically added fields and submit buttons.
88
+ - **formValidators**: A form validator or array of validators for cross-field rules.
89
+
90
+ ## Form Submission
91
+
92
+ - Submit elements are `button[type="submit"]`, `button` without `type`, `input[type="submit"]`, and
93
+ `input[type="image"]`.
94
+ - `submit()` is public and returns `Promise<SubmitResult | undefined>`.
95
+ - For `method="GET"` and `method="HEAD"`, data is appended to the `action` query string and no request body is sent.
96
+ - `wrapData` applies to every supported `enctype`: `application/x-www-form-urlencoded`, `multipart/form-data`,
97
+ `text/plain`, and `application/json`.
98
+ - `onResponseSuccess` runs only for a successful HTTP response with `success: true`.
99
+ - `onResponseUnsuccess` runs for unsuccessful HTTP responses, `success !== true`, or invalid JSON responses.
100
+
101
+ ## Methods
102
+
103
+ - **Form.initAll(selector, options)**: Initializes all forms matching a selector and reuses existing instances.
104
+ - **updateOptions(options)**: Updates one form instance and rebinds live validation.
105
+ - **validate()**: Validates the whole form.
106
+ - **validateField(field)**: Validates one field by name or DOM element.
107
+ - **showFieldError(field, message, source)**: Shows a field error manually.
108
+ - **setErrors(errors, source)**: Applies backend/form-level errors from an object, array, or `ErrorResponse[]`.
109
+ - **getErrors()**: Returns current errors as `{ field, rule, message, source }`.
110
+ - **getValues()**: Returns form values as a plain object.
111
+ - **setValues(values)**: Fills fields by `name` and dispatches `input`/`change`.
112
+ - **clearErrors()**: Clears errors without changing field values.
113
+ - **reset(options)**: Calls native `form.reset()` and clears errors by default.
114
+ - **clearInputs()**: Clears all input fields in the form.
115
+ - **setDefaultParams(params):** The setDefaultParams method is used to set default values for all instances of the form. These parameters can be overridden when initializing a specific form.
116
+ - **destroy()**: Removes event listeners and runtime artifacts.
117
+
118
+ ## Validation
119
+
120
+ ```html
121
+ <input
122
+ name="email"
123
+ type="email"
124
+ data-validate="required|email"
125
+ data-error-required="Email is required"
126
+ data-error-email="Enter a valid email"
127
+ data-error-container="#email-error"
128
+ />
129
+ <small id="email-error" hidden></small>
130
+ ```
131
+
132
+ - `data-validate` accepts rules separated by `|`, spaces, or commas.
133
+ - `data-error-rule-name` overrides a message for one rule.
134
+ - `data-error-container` points to a custom error container without requiring wrapper markup.
135
+ - During async validation, fields receive `aria-busy="true"` and a `validating` state attribute; stale validator
136
+ responses are ignored.
137
+ - `data-custom-validate` is still supported for backward compatibility.
138
+
139
+ Final rule order:
140
+
141
+ ```text
142
+ required → schema.rules → data-validate → data-custom-validate
143
+ ```
144
+
145
+ ### Cross-field and form-level validation
146
+
147
+ ```ts
148
+ const form = new Form($form, {
149
+ formValidators: [
150
+ ({ values }) =>
151
+ values.password === values.passwordConfirm
152
+ ? true
153
+ : {
154
+ field: 'passwordConfirm',
155
+ rule: 'same-as-password',
156
+ message: 'Passwords do not match',
157
+ },
158
+ ({ values }) => (values.start <= values.end ? true : { field: 'end', message: 'End date must be after start' }),
159
+ ],
160
+ });
161
+ ```
162
+
163
+ Return a string or an issue without `field` to show a form-level error. In `getErrors()`, it uses
164
+ `FORM_ERROR_FIELD` (`"_form"`).
165
+
166
+ ```ts
167
+ form.setErrors({
168
+ email: 'Email is already used',
169
+ [FORM_ERROR_FIELD]: 'Please check the form',
170
+ });
171
+ ```
172
+
173
+ ## Schema Adapters
174
+
175
+ Form Father has no schema-library runtime dependency, but can wrap Zod/Valibot/Yup-like schemas with `safeParse()` or
176
+ `parse()`.
177
+
178
+ ```ts
179
+ import { registerSchemaValidator } from 'form-father';
180
+
181
+ registerSchemaValidator('company-email', z.string().email(), 'Use a company email');
182
+ ```
183
+
184
+ For simple checks, use `createFieldValidator(predicate, message)`.
185
+
186
+ ## Demos and Recipes
187
+
188
+ - `demos/index.html` is a static login/callback/search/upload demo after `npm run build`.
189
+ - `npm run demos` builds the package and starts the Vite demo server.
190
+ - `docs/recipes/README.md` contains short API, data-attribute, adapter, and server-error recipes.
191
+
192
+ ## Helpers
193
+
194
+ The library provides a number of utility functions:
195
+
196
+ - **serializeToFormData($element)**: Serializes form data into a `FormData` object.
197
+ - **isEmailValid(value)**: Checks if a string is a valid email address.
198
+ - **isUrlValid(value)**: Checks an `http(s)` URL, domain, IP, or `localhost`; the scheme may be omitted.
199
+ - **isPhoneValid(value)**: Checks if a string is a valid phone number.
200
+ - **closest($el, selector)**: Finds the closest parent element matching the given selector.
201
+ - **blockScrollBody()**: Blocks page scrolling.
202
+ - **unblockScrollBody()**: Unblocks page scrolling.
203
+ - **parseCommonResponseProperties(responseBody)**: Processes common properties from the server response.
204
+ - **serializeFormToJSON(form)**: Serializes form data into a plain object.
205
+
206
+ ## Build and Development
207
+
208
+ **Rollup** is used for building the project. Main commands:
209
+
210
+ - **npm run build**: Builds the project.
211
+ - **npm run watch**: Builds the project in watch mode.
212
+ - **npm run demos**: Runs a demo version using Vite.
213
+
214
+ ## Contributing
215
+
216
+ We welcome your suggestions and improvements! Please create [issues](https://github.com/poliklot/form-father/issues) and
217
+ submit [pull requests](https://github.com/poliklot/form-father/pulls).
218
+
219
+ ## License
220
+
221
+ [MIT](LICENSE)
222
+
223
+ ---
224
+
225
+ © 2024 Poliklot
@@ -0,0 +1,115 @@
1
+ # Form Father Recipes
2
+
3
+ ## Init several forms
4
+
5
+ ```ts
6
+ import Form from 'form-father';
7
+
8
+ const forms = Form.initAll('form[data-form-father]', {
9
+ inputWrapperSelector: '.field',
10
+ validateOn: ['blur', 'change'],
11
+ revalidateOn: ['input', 'change'],
12
+ });
13
+ ```
14
+
15
+ ## HTML-only rules
16
+
17
+ ```html
18
+ <input
19
+ name="email"
20
+ type="email"
21
+ data-validate="required|email"
22
+ data-error-required="Email is required"
23
+ data-error-email="Enter a valid email"
24
+ data-error-container="#email-error"
25
+ />
26
+ <small id="email-error" hidden></small>
27
+ ```
28
+
29
+ ## Programmatic field API
30
+
31
+ ```ts
32
+ const form = new Form(document.querySelector('form')!);
33
+
34
+ await form.validateField('email');
35
+ form.showFieldError('email', 'This email is already used', 'server');
36
+ console.log(form.getErrors());
37
+ ```
38
+
39
+ ## State helpers
40
+
41
+ ```ts
42
+ form.setValues({
43
+ email: 'user@example.com',
44
+ interests: ['docs', 'api'],
45
+ });
46
+
47
+ console.log(form.getValues());
48
+ form.clearErrors();
49
+ form.reset();
50
+ ```
51
+
52
+ ## Cross-field validation
53
+
54
+ ```ts
55
+ const form = new Form(document.querySelector('form')!, {
56
+ formValidators: ({ values }) =>
57
+ values.password === values.passwordConfirm
58
+ ? true
59
+ : {
60
+ field: 'passwordConfirm',
61
+ rule: 'same-as-password',
62
+ message: 'Passwords do not match',
63
+ },
64
+ });
65
+ ```
66
+
67
+ ## Applying backend errors
68
+
69
+ ```ts
70
+ import { FORM_ERROR_FIELD } from 'form-father';
71
+
72
+ form.setErrors({
73
+ email: 'Email is already registered',
74
+ [FORM_ERROR_FIELD]: 'Please check the form',
75
+ });
76
+ ```
77
+
78
+ ## Async validation state
79
+
80
+ ```css
81
+ [data-form-father-state="validating"] {
82
+ opacity: 0.7;
83
+ }
84
+
85
+ [data-form-father-state="invalid"] {
86
+ border-color: #b3261e;
87
+ }
88
+ ```
89
+
90
+ Async validators are race-safe: if an older validation finishes after a newer one started, Form Father ignores the stale
91
+ result and keeps the latest field state.
92
+
93
+ ## Zod-like schema adapter
94
+
95
+ ```ts
96
+ import { registerSchemaValidator } from 'form-father';
97
+
98
+ registerSchemaValidator('email-domain', z.string().email().endsWith('@company.com'), 'Use a company email');
99
+ ```
100
+
101
+ The adapter only expects a `safeParse(value)` or `parse(value)` method, so it can be used with Zod-like libraries without
102
+ adding runtime dependencies to Form Father.
103
+
104
+ ## Server response errors
105
+
106
+ ```json
107
+ {
108
+ "success": false,
109
+ "error": true,
110
+ "error-msg": "Please check the form",
111
+ "errors": [{ "name": "email", "error-msg": "Email is already registered" }]
112
+ }
113
+ ```
114
+
115
+ Field errors are shown through the same rendering path as client-side validation errors.
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "form-father",
3
- "version": "0.2.11",
3
+ "version": "0.4.0",
4
4
  "description": "Form Father: Библиотека для обработки форм",
5
5
  "type": "module",
6
- "files": ["dist", "README.md", "LICENSE", "RESPONSE_API.md"],
6
+ "files": ["dist", "README.md", "LICENSE", "RESPONSE_API.md", "CHANGELOG.md", "docs", "demos"],
7
7
  "exports": {
8
8
  ".": {
9
9
  "import": "./dist/index.js",