form-father 0.7.1 → 0.8.2

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.
@@ -0,0 +1,270 @@
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
+ type FormValidatorPredicate,
63
+ type ErrorSummaryOptions,
64
+ } from 'form-father';
65
+ ```
66
+
67
+ ## Options
68
+
69
+ - **onSubmit**: Callback function executed on form submission.
70
+ - **onBeforeSubmit**: Runs before sending an already valid form; returning `false` cancels the request.
71
+ - **onSubmitError**: Runs when form submission throws.
72
+ - **onResponse**: Callback function executed when a server response is received.
73
+ - **onResponseSuccess**: Callback function executed for a successful HTTP response with `success: true`.
74
+ - **onResponseUnsuccess**: Callback function executed for unsuccessful HTTP responses, `success !== true`, or invalid JSON.
75
+ - **onValidationError**: Runs when client-side validation fails.
76
+ - **showLoaderButton**: Whether to display a loader in the submit button. Defaults to `true`.
77
+ - **scrollToFirstErroredInput**: Whether to scroll to the first input with an error. Defaults to `true`.
78
+ - **focusFirstErroredInput**: Whether to focus the first invalid field. Defaults to `false`.
79
+ - **customTypeError**: Custom error type.
80
+ - **loaderColor**: Color of the loader in the submit button.
81
+ - **logging**: Specifies whether to log data to the console. Defaults to false.
82
+ - **validateOn**: Field live-validation events: `submit`, `input`, `blur`, `change`.
83
+ - **revalidateOn**: Events used to re-check already invalid fields. Defaults to `input` and `change`.
84
+ - **validationDebounce**: Live-validation delay in milliseconds.
85
+ - **errorContainerAttribute**: Attribute containing the CSS selector for a custom error container. Defaults to
86
+ `data-error-container`.
87
+ - **validationStateAttribute**: Field state attribute: `validating`, `valid`, or `invalid`. Defaults to
88
+ `data-form-father-state`.
89
+ - **ariaDescribeErrors**: Links inline error text to the field with `aria-describedby`. Defaults to `true`.
90
+ - **errorIdPrefix**: Prefix for automatically generated error element ids. Defaults to `form-father-error`.
91
+ - **errorSummary**: Form error summary: `true` for the default renderer or `{ selector, title, focus, render }`.
92
+ - **observeMutations**: Watches dynamically added fields and submit buttons.
93
+ - **formValidators**: A form validator or array of validators for cross-field rules.
94
+
95
+ ## Form Submission
96
+
97
+ - Submit elements are `button[type="submit"]`, `button` without `type`, `input[type="submit"]`, and
98
+ `input[type="image"]`.
99
+ - `submit()` is public and returns `Promise<SubmitResult | undefined>`.
100
+ - For `method="GET"` and `method="HEAD"`, data is appended to the `action` query string and no request body is sent.
101
+ - `wrapData` applies to every supported `enctype`: `application/x-www-form-urlencoded`, `multipart/form-data`,
102
+ `text/plain`, and `application/json`.
103
+ - `onResponseSuccess` runs only for a successful HTTP response with `success: true`.
104
+ - `onResponseUnsuccess` runs for unsuccessful HTTP responses, `success !== true`, or invalid JSON responses.
105
+
106
+ ## Methods
107
+
108
+ - **Form.initAll(selector, options)**: Initializes all forms matching a selector and reuses existing instances.
109
+ - **updateOptions(options)**: Updates one form instance and rebinds live validation.
110
+ - **validate()**: Validates the whole form.
111
+ - **validateField(field)**: Validates one field by name or DOM element.
112
+ - **showFieldError(field, message, source)**: Shows a field error manually.
113
+ - **setErrors(errors, source)**: Applies backend/form-level errors from an object, array, or `ErrorResponse[]`.
114
+ - **getErrors()**: Returns current errors as `{ field, rule, message, source }`.
115
+ - **getValues()**: Returns form values as a plain object.
116
+ - **setValues(values)**: Fills fields by `name` and dispatches `input`/`change`.
117
+ - **clearErrors()**: Clears errors without changing field values.
118
+ - **reset(options)**: Calls native `form.reset()` and clears errors by default.
119
+ - **clearInputs()**: Clears all input fields in the form.
120
+ - **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.
121
+ - **destroy()**: Removes event listeners and runtime artifacts.
122
+
123
+ ## Validation
124
+
125
+ ```html
126
+ <input
127
+ name="email"
128
+ type="email"
129
+ data-validate="required|email"
130
+ data-error-required="Email is required"
131
+ data-error-email="Enter a valid email"
132
+ data-error-container="#email-error"
133
+ />
134
+ <small id="email-error" hidden></small>
135
+ ```
136
+
137
+ - `data-validate` accepts rules separated by `|`, spaces, or commas.
138
+ - `data-error-rule-name` overrides a message for one rule.
139
+ - `data-error-container` points to a custom error container without requiring wrapper markup.
140
+ - During async validation, fields receive `aria-busy="true"` and a `validating` state attribute; stale validator
141
+ responses are ignored.
142
+ - Inline errors receive `role="alert"` and are linked to fields with `aria-describedby`.
143
+ - `data-custom-validate` is still supported for backward compatibility.
144
+
145
+ ### Error summary and a11y
146
+
147
+ ```html
148
+ <form data-form-father novalidate>
149
+ <div data-form-father-summary hidden></div>
150
+ <label>
151
+ Email
152
+ <input class="input" name="email" data-validate="required|email" />
153
+ </label>
154
+ <button type="submit">Submit</button>
155
+ </form>
156
+ ```
157
+
158
+ ```ts
159
+ new Form($form, {
160
+ inputWrapperSelector: 'label',
161
+ errorSummary: {
162
+ title: 'Please check the form',
163
+ focus: true,
164
+ },
165
+ });
166
+ ```
167
+
168
+ - If the form already contains `[data-form-father-summary]`, Form Father uses it; otherwise `errorSummary: true` creates
169
+ a summary at the start of the form.
170
+ - `selector` sends the summary into your own container.
171
+ - `render(errors, form)` lets you fully replace the summary markup.
172
+ - `ariaDescribeErrors: false` disables automatic error id insertion into `aria-describedby`.
173
+
174
+ Final rule order:
175
+
176
+ ```text
177
+ required → schema.rules → data-validate → data-custom-validate
178
+ ```
179
+
180
+ ### Cross-field and form-level validation
181
+
182
+ ```ts
183
+ import { dateOrder, requiredIf, sameAsField } from 'form-father';
184
+
185
+ const form = new Form($form, {
186
+ formValidators: [
187
+ sameAsField('passwordConfirm', 'password', 'Passwords do not match'),
188
+ requiredIf('email', ({ values }) => Boolean(values.subscribe), 'Email is required for subscription'),
189
+ dateOrder('start', 'end', 'End date must be after start'),
190
+ ],
191
+ });
192
+ ```
193
+
194
+ Return a string or an issue without `field` to show a form-level error. In `getErrors()`, it uses
195
+ `FORM_ERROR_FIELD` (`"_form"`).
196
+
197
+ ```ts
198
+ form.setErrors({
199
+ email: 'Email is already used',
200
+ [FORM_ERROR_FIELD]: 'Please check the form',
201
+ });
202
+ ```
203
+
204
+ ### Rule DX helpers
205
+
206
+ ```ts
207
+ import { createLengthValidator, createPatternValidator, registerFieldValidator } from 'form-father';
208
+
209
+ registerFieldValidator('starts-with-a', value => value.startsWith('A'), 'Must start with A');
210
+ registerValidator('slug', createPatternValidator(/^[a-z0-9-]+$/), 'Slug characters only');
211
+ registerValidator('short-name', createLengthValidator({ min: 2, max: 24 }), 'Use 2-24 characters');
212
+ ```
213
+
214
+ ## Schema Adapters
215
+
216
+ Form Father has no schema-library runtime dependency, but can wrap Zod/Valibot/Yup-like schemas with `safeParse()` or
217
+ `parse()`.
218
+
219
+ ```ts
220
+ import { registerSchemaValidator } from 'form-father';
221
+
222
+ registerSchemaValidator('company-email', z.string().email(), 'Use a company email');
223
+ ```
224
+
225
+ For simple checks, use `createFieldValidator(predicate, message)`.
226
+
227
+ ## Demos and Recipes
228
+
229
+ - `demos/index.html` is a static login/callback/search/upload/public API demo after `npm run build`.
230
+ - `npm run demos` builds the package and starts the Vite demo server.
231
+ - `docs/api/README.md` is a compact reference for public API, options, methods, and exports.
232
+ - `docs/demo/README.md` explains manual demo checks and demo form coverage.
233
+ - `docs/recipes/README.md` contains short API, data-attribute, adapter, and server-error recipes.
234
+ - `CHANGELOG.md` tracks releases and coverage baselines.
235
+
236
+ ## Helpers
237
+
238
+ The library provides a number of utility functions:
239
+
240
+ - **serializeToFormData($element)**: Serializes form data into a `FormData` object.
241
+ - **isEmailValid(value)**: Checks if a string is a valid email address.
242
+ - **isUrlValid(value)**: Checks an `http(s)` URL, domain, IP, or `localhost`; the scheme may be omitted.
243
+ - **isPhoneValid(value)**: Checks if a string is a valid phone number.
244
+ - **closest($el, selector)**: Finds the closest parent element matching the given selector.
245
+ - **blockScrollBody()**: Blocks page scrolling.
246
+ - **unblockScrollBody()**: Unblocks page scrolling.
247
+ - **parseCommonResponseProperties(responseBody)**: Processes common properties from the server response.
248
+ - **serializeFormToJSON(form)**: Serializes form data into a plain object.
249
+
250
+ ## Build and Development
251
+
252
+ **Rollup** is used for building the project. Main commands:
253
+
254
+ - **npm run build**: Builds the project.
255
+ - **npm run watch**: Builds the project in watch mode.
256
+ - **npm run docs:check**: Checks docs links, version references, demo files, and package README sync.
257
+ - **npm run demos**: Runs a demo version using Vite.
258
+
259
+ ## Contributing
260
+
261
+ We welcome your suggestions and improvements! Please create [issues](https://github.com/poliklot/form-father/issues) and
262
+ submit [pull requests](https://github.com/poliklot/form-father/pulls).
263
+
264
+ ## License
265
+
266
+ [MIT](LICENSE)
267
+
268
+ ---
269
+
270
+ © 2024 Poliklot
@@ -0,0 +1,150 @@
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
+ import { dateOrder, requiredIf, sameAsField } from 'form-father';
56
+
57
+ const form = new Form(document.querySelector('form')!, {
58
+ formValidators: [
59
+ sameAsField('passwordConfirm', 'password', 'Passwords do not match'),
60
+ requiredIf('email', ({ values }) => Boolean(values.subscribe), 'Email is required'),
61
+ dateOrder('start', 'end', 'End date must be after start'),
62
+ ],
63
+ });
64
+ ```
65
+
66
+ ## Field rule helpers
67
+
68
+ ```ts
69
+ import { createLengthValidator, createPatternValidator, registerFieldValidator, registerValidator } from 'form-father';
70
+
71
+ registerFieldValidator('starts-with-a', value => value.startsWith('A'), 'Must start with A');
72
+ registerValidator('slug', createPatternValidator(/^[a-z0-9-]+$/), 'Slug characters only');
73
+ registerValidator('short-name', createLengthValidator({ min: 2, max: 24 }), 'Use 2-24 characters');
74
+ ```
75
+
76
+ ## Applying backend errors
77
+
78
+ ```ts
79
+ import { FORM_ERROR_FIELD } from 'form-father';
80
+
81
+ form.setErrors({
82
+ email: 'Email is already registered',
83
+ [FORM_ERROR_FIELD]: 'Please check the form',
84
+ });
85
+ ```
86
+
87
+ ## Async validation state
88
+
89
+ ```css
90
+ [data-form-father-state="validating"] {
91
+ opacity: 0.7;
92
+ }
93
+
94
+ [data-form-father-state="invalid"] {
95
+ border-color: #b3261e;
96
+ }
97
+ ```
98
+
99
+ Async validators are race-safe: if an older validation finishes after a newer one started, Form Father ignores the stale
100
+ result and keeps the latest field state.
101
+
102
+ ## Zod-like schema adapter
103
+
104
+ ```ts
105
+ import { registerSchemaValidator } from 'form-father';
106
+
107
+ registerSchemaValidator('email-domain', z.string().email().endsWith('@company.com'), 'Use a company email');
108
+ ```
109
+
110
+ The adapter only expects a `safeParse(value)` or `parse(value)` method, so it can be used with Zod-like libraries without
111
+ adding runtime dependencies to Form Father.
112
+
113
+ ## Server response errors
114
+
115
+ ```json
116
+ {
117
+ "success": false,
118
+ "error": true,
119
+ "error-msg": "Please check the form",
120
+ "errors": [{ "name": "email", "error-msg": "Email is already registered" }]
121
+ }
122
+ ```
123
+
124
+ Field errors are shown through the same rendering path as client-side validation errors.
125
+
126
+ ## Accessible error summary
127
+
128
+ ```html
129
+ <form data-form-father novalidate>
130
+ <div data-form-father-summary hidden></div>
131
+ <label>
132
+ Email
133
+ <input class="input" name="email" data-validate="required|email" />
134
+ </label>
135
+ <button type="submit">Submit</button>
136
+ </form>
137
+ ```
138
+
139
+ ```ts
140
+ const form = new Form(document.querySelector('form')!, {
141
+ inputWrapperSelector: 'label',
142
+ errorSummary: {
143
+ title: 'Please check the form',
144
+ focus: true,
145
+ },
146
+ });
147
+ ```
148
+
149
+ Inline errors are linked to fields with `aria-describedby` by default. Use `ariaDescribeErrors: false` if your design
150
+ system owns that relationship itself.
package/package.json CHANGED
@@ -1,40 +1,47 @@
1
1
  {
2
2
  "name": "form-father",
3
- "version": "0.7.1",
3
+ "version": "0.8.2",
4
4
  "description": "Form Father: Библиотека для обработки форм",
5
5
  "type": "module",
6
- "main": "index.js",
7
- "module": "index.js",
8
- "types": "index.d.ts",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./index.d.ts",
12
- "import": "./index.js",
13
- "default": "./index.js"
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
14
  },
15
- "./FormFather.min.js": "./FormFather.min.js"
15
+ "./FormFather.min.js": "./dist/FormFather.min.js"
16
16
  },
17
- "unpkg": "./FormFather.min.js",
18
- "jsdelivr": "./FormFather.min.js",
17
+ "unpkg": "./dist/FormFather.min.js",
18
+ "jsdelivr": "./dist/FormFather.min.js",
19
19
  "files": [
20
- "index.js",
21
- "index.d.ts",
22
- "FormFather.min.js",
23
- "*.map",
24
- "types",
20
+ "dist",
25
21
  "README.md",
26
22
  "LICENSE",
27
- "RESPONSE_API.md"
23
+ "RESPONSE_API.md",
24
+ "CHANGELOG.md",
25
+ "docs",
26
+ "demos"
28
27
  ],
29
28
  "scripts": {
30
- "build": "rollup -c",
29
+ "build": "npm run build:js && npm run build:types",
30
+ "build:js": "rollup -c",
31
+ "build:types": "tsc -p tsconfig.json --emitDeclarationOnly && rollup -c rollup.types.config.js",
31
32
  "watch": "rollup -c -w",
32
- "release": "node scripts/release.js",
33
+ "check": "npm run release:check",
34
+ "release:check": "npm test -- --runInBand --watchman=false && npm run docs:check && npm run build && npm run smoke:package && npm run pack:dry-run",
35
+ "docs:check": "node scripts/check-docs.mjs",
36
+ "test": "jest --coverage",
37
+ "prepublishOnly": "npm run release:check",
38
+ "pack:dry-run": "npm_config_cache=/tmp/form-father-npm-cache npm pack --dry-run",
39
+ "smoke:package": "node scripts/smoke-package.mjs",
33
40
  "demos": "BUILD_TARGET=demos npm run build && concurrently --kill-others \"vite\" \"npm run watch\""
34
41
  },
35
42
  "repository": {
36
43
  "type": "git",
37
- "url": "git+https://github.com/poliklot/form-father.git"
44
+ "url": "https://github.com/Poliklot/form-father"
38
45
  },
39
46
  "keywords": [
40
47
  "form",
@@ -47,30 +54,37 @@
47
54
  "author": "Poliklot",
48
55
  "license": "MIT",
49
56
  "bugs": {
50
- "url": "https://github.com/poliklot/form-father/issues"
57
+ "url": "https://github.com/Poliklot/form-father/issues"
51
58
  },
52
59
  "sideEffects": false,
53
60
  "devDependencies": {
54
61
  "@babel/preset-env": "^7.26.0",
55
- "@rollup/plugin-babel": "^6.0.4",
56
- "@rollup/plugin-commonjs": "^28.0.1",
62
+ "@rollup/plugin-babel": "^7.1.0",
63
+ "@rollup/plugin-commonjs": "^29.0.3",
57
64
  "@rollup/plugin-json": "^6.1.0",
58
- "@rollup/plugin-node-resolve": "^15.3.0",
59
- "@rollup/plugin-terser": "^0.4.4",
60
- "chalk": "^5.3.0",
65
+ "@rollup/plugin-node-resolve": "^16.0.3",
66
+ "@rollup/plugin-terser": "^1.0.0",
67
+ "@types/jest": "^30.0.0",
68
+ "@types/jsdom": "^21.1.7",
61
69
  "concurrently": "^9.0.1",
62
- "fs-extra": "^11.2.0",
63
- "inquirer": "^12.1.0",
64
- "ora": "^8.1.1",
65
- "postcss-preset-env": "^10.0.8",
70
+ "esbuild": "^0.28.1",
71
+ "jest": "^30.0.5",
72
+ "jest-environment-jsdom": "^30.0.5",
73
+ "jsdom": "^26.1.0",
74
+ "postcss-preset-env": "^11.3.1",
66
75
  "prettier-plugin-jsdoc": "^1.3.0",
67
76
  "rollup": "^4.24.3",
68
77
  "rollup-plugin-copy": "^3.5.0",
69
78
  "rollup-plugin-delete": "^2.1.0",
70
79
  "rollup-plugin-dts": "^6.1.1",
71
- "rollup-plugin-typescript2": "^0.36.0",
80
+ "rollup-plugin-esbuild": "^6.2.1",
72
81
  "sass-embedded": "^1.80.5",
73
- "typescript": "^5.6.3",
74
- "vite": "^5.4.10"
82
+ "ts-jest": "^29.4.0",
83
+ "ts-node": "^10.9.2",
84
+ "typescript": "^6.0.3",
85
+ "vite": "^8.0.16"
86
+ },
87
+ "overrides": {
88
+ "js-yaml": "4.2.0"
75
89
  }
76
- }
90
+ }
package/FormFather.min.js DELETED
@@ -1,2 +0,0 @@
1
- var FormFather=function(t){"use strict";function e(t,e,r,i){return new(r||(r=Promise))((function(n,o){function s(t){try{l(i.next(t))}catch(t){o(t)}}function a(t){try{l(i.throw(t))}catch(t){o(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,a)}l((i=i.apply(t,e||[])).next())}))}function r(t,e){if(null==t)return null;let r=t;for(;r;){if(r.matches(e))return r;r=r.parentElement}return null}"function"==typeof SuppressedError&&SuppressedError;const i=["http:","https:"];function n(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function o(t){return!!t&&("localhost"===t||function(t){const e=t.split(".");return 4===e.length&&e.every((t=>{if(!/^\d{1,3}$/.test(t))return!1;const e=Number(t);return e>=0&&e<=255}))}(t)||function(t){if(!t.startsWith("[")||!t.endsWith("]"))return!1;const e=t.slice(1,-1);return e.includes(":")&&/^[\da-f:.]+$/i.test(e)}(t)||function(t){const e=t.split(".");return e.length>=2&&e.every(n)}(t))}function s(t){const e=function(t){const e=t.trim();return 0===e.length?"":e.startsWith("//")?`https:${e}`:/^[a-z][a-z\d+.-]*:\/\//i.test(e)?e:`https://${e}`}(t);if(0===e.length)return!1;try{const t=new URL(e);return i.includes(t.protocol)&&o(t.hostname)}catch(t){return!1}}function a(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),r=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.disabled||t.hasAttribute("data-no-serialize"))return;if(t instanceof HTMLButtonElement)return;const{name:e,type:i}=t;if("file"===i)if(t.multiple){const i=t.files;if(i)for(let t=0;t<i.length;t++)r.append(`${e}[${t}]`,i[t])}else{const i=t.files;i&&i[0]&&r.append(e,i[0])}else{let n;if("select-one"===i||"select-multiple"===i){const o=t;if("select-multiple"===i){Array.from(o.selectedOptions).map((t=>t.value)).forEach(((t,i)=>{r.append(`${e}[${i}]`,t)}))}else n=o.value,n&&r.append(e,n)}else if("tel"===i)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===i||"radio"===i){if(!t.checked)return;n=t.value}else if("date"===i){const e=t.getAttribute("data-date-format");n=e?function(t,e){const r=new Date(t),i=t=>t.toString().padStart(2,"0"),n=i(r.getDate()),o=i(r.getMonth()+1),s=r.getFullYear().toString();return e.replace("dd",n).replace("mm",o).replace("yyyy",s)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==i&&"select-multiple"!==i&&r.append(e,n)}})),r}function l(t){if(Object.prototype.hasOwnProperty.call(t,"redirect-url"))if(t["redirect-url-delay"]){const e=Number(t["redirect-url-delay"]);setTimeout((()=>{window.location.href=t["redirect-url"]}),e)}else window.location.href=t["redirect-url"];else if(Object.prototype.hasOwnProperty.call(t,"reload")){if(!0===t.reload)if(t["reload-delay"]){const e=Number(t["reload-delay"]);setTimeout((()=>{window.location.reload()}),e)}else window.location.reload()}else if(Object.prototype.hasOwnProperty.call(t,"toast")){const e=t.toast;(Array.isArray(e)?e:[e]).forEach((t=>{window.showToast(t)}))}else Object.prototype.hasOwnProperty.call(t,"error-toast")&&window.showToast(t["error-toast"])}function d(t){const e={};return new FormData(t).forEach(((t,r)=>{e.hasOwnProperty(r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}var c;const u=Symbol.for("FormFather.validators"),h=null!==(c=globalThis[u])&&void 0!==c?c:globalThis[u]=new Map;function m(t,e,r,{override:i=!1}={}){!h.has(t)||i?h.set(t,{fn:e,defaultMessage:r}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const f=t=>h.get(t);function p(t,e,r){h.has(t)||h.set(t,{fn:e,defaultMessage:r})}function g(t,e){return"function"==typeof e?e(t):e||function(t){var e;if(!t||"object"!=typeof t)return;const r=t.issues||t.errors;if(Array.isArray(r)){const t=null===(e=r.find((t=>"string"==typeof(null==t?void 0:t.message))))||void 0===e?void 0:e.message;if(t)return t}return"string"==typeof t.message?t.message:void 0}(t)}function y(t,r={}){return(i,n,o,s)=>e(this,void 0,void 0,(function*(){const e=r.mapValue?r.mapValue(i,n,o,s):i;try{if("function"==typeof t)return a=yield t(e),l=g(void 0,r.message),"object"==typeof a&&null!==a?a:void 0===a||!!a||{valid:!1,message:l};if("function"==typeof t.safeParse){const i=yield t.safeParse(e);return!!i.success||{valid:!1,message:g(i.error||i,r.message)}}if("function"==typeof t.parse)return yield t.parse(e),!0}catch(t){return{valid:!1,message:g(t,r.message)}}var a,l;return{valid:!1,message:g(void 0,r.message)}}))}function b(t,r){return(i,n,o,s)=>e(this,void 0,void 0,(function*(){return!!(yield t(i,n,o,s))||{valid:!1,message:r}}))}var v;p("required",((t,e,r)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=r.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),p("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),p("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),p("url",(t=>s(t)),"Неверный формат"),p("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const E=Symbol.for("formfather.shared"),S=null!==(v=globalThis[E])&&void 0!==v?v:globalThis[E]={defaultParams:{},defaultValidationSchema:void 0},w="_form";function A(t,e){return t[e]}function F(t){return Array.isArray(t)?0===t.length:null==t||0===String(t).trim().length}function V(t,r){return i=>e(this,void 0,void 0,(function*(){return!!(yield t(i))||function(t,r,i,n){return e(this,void 0,void 0,(function*(){const e="function"==typeof r?yield r(t):r;return"string"==typeof e?{field:i,rule:n,message:e}:Object.assign({field:i,rule:n},e)}))}(i,r)}))}let $=0,T=0;const O=new WeakMap,_="data-form-father-describedby-id",x="data-form-father-added-describedby-id",k="data-form-father-summary",L={tel:{selector:'input[type="tel"]',rules:["tel"]},email:{selector:'input[type="email"]',rules:["email"]},required:{selector:"[required]",rules:["required"]},url:{selector:'input[type="url"]',rules:["url"]},"not-numbers":{selector:'input[data-validate="not-numbers"]',rules:["not-numbers"]}};class C{static get defaultParams(){return S.defaultParams}static set defaultParams(t){S.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=S.defaultValidationSchema)&&void 0!==t?t:S.defaultValidationSchema=L}static set defaultValidationSchema(t){S.defaultValidationSchema=t}constructor(t,e={}){this.$submits=[],this.waitResponse=!1,this.inputs=null,this.fieldEventHandlers=[],this.liveValidationTimers=new WeakMap,this.fieldValidationTokens=new WeakMap,this.fieldValidationTokenCounter=0,this.errors=[],this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=e;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,focusFirstErroredInput:!1,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary",validateOn:"submit",revalidateOn:["input","change"],validationDebounce:0,errorContainerAttribute:"data-error-container",validationStateAttribute:"data-form-father-state",ariaDescribeErrors:!0,errorIdPrefix:"form-father-error",errorSummary:!1,observeMutations:!1},C.defaultParams,e),this.$el?(this.$submits=this.findSubmitElements(),this.isCorrectArguments()&&(this.bindSubmitHandlers(),this.initialization(),this.bindFieldValidationEvents(),this.setupMutationObserver(),O.set(this.$el,this))):console.warn("Empty $el")}getOptions(){return this.options}static initAll(t="form[data-form-father]",e={}){return"undefined"==typeof document?[]:Array.from(document.querySelectorAll(t)).filter((t=>"FORM"===t.tagName)).map((t=>{const r=O.get(t);return r?(r.updateOptions(e),r):new C(t,e)}))}updateOptions(t){return this.options=Object.assign(Object.assign({},this.options),t),this.config=Object.assign(Object.assign({},this.config),t),this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents(),this.setupMutationObserver(),this}getErrors(){return this.errors.map((t=>Object.assign({},t)))}getValues(){return d(this.$el)}normalizeValueList(t){return Array.isArray(t)?t.map(String):[String(t)]}setValues(t){return this.getAllFields().forEach((e=>{if(!e.name||!Object.prototype.hasOwnProperty.call(t,e.name))return;const r=t[e.name];if(!(e instanceof HTMLInputElement&&"file"===e.type)){if(e instanceof HTMLInputElement&&"checkbox"===e.type)e.checked="boolean"==typeof r?r:this.normalizeValueList(r).includes(e.value);else if(e instanceof HTMLInputElement&&"radio"===e.type)e.checked=String(r)===e.value;else if(e instanceof HTMLSelectElement&&e.multiple){const t=this.normalizeValueList(r);Array.from(e.options).forEach((e=>{e.selected=t.includes(e.value)}))}else e.value=null==r?"":String(r);e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0}))}})),this}clearErrors(){return this.errors=[],this.getAllFields().forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),this.hideErrorForm(),this.hideErrorSummary(),this}isErrorResponse(t){return!!t&&"object"==typeof t&&"string"==typeof t.name&&"string"==typeof t["error-msg"]}normalizeValidationIssues(t,e){return null==t||!0===t?[]:!1===t?[{message:"Некорректные данные формы",source:e}]:"string"==typeof t?[{message:t,source:e}]:Array.isArray(t)?t.flatMap((t=>this.normalizeValidationIssues(t,e))):this.isErrorResponse(t)?[{field:t.name,rule:e,message:t["error-msg"],source:e}]:"object"==typeof t&&"message"in t&&"string"==typeof t.message?[Object.assign(Object.assign({},t),{source:t.source||e})]:"object"==typeof t?Object.entries(t).map((([t,r])=>({field:t,rule:e,message:r,source:e}))):[]}applyValidationIssue(t,e){const r=t.source||e,i=t.rule||r,n=t.field||w;if(n===w)return this.setErrorRecord(w,i,t.message,r),this.showErrorForm(t.message),null;const o=this.findFieldByName(n);return o?(this.showFieldError(o,t.message,r,i),o):(this.setErrorRecord(n,i,t.message,r),console.warn(`Не найдено поле с именем: ${n}, для вывода ошибки: ${t.message}`),null)}setErrors(t,e="server"){return this.normalizeValidationIssues(t,e).forEach((t=>{this.applyValidationIssue(t,e)})),this.syncErrorSummary(),this}validateFormValidators(){return e(this,void 0,void 0,(function*(){const t=this.config.formValidators?Array.isArray(this.config.formValidators)?this.config.formValidators:[this.config.formValidators]:[],e=[];let r=!0;for(const i of t){const t=yield i({form:this,formElement:this.$el,values:this.getValues(),errors:this.getErrors()}),n=this.normalizeValidationIssues(t,"client");0!==n.length&&(r=!1,n.forEach((t=>{const r=this.applyValidationIssue(t,"client");r&&e.push(r)})))}return{ok:r,erroredInputs:e}}))}reset(t={}){return this.$el.reset(),this.refreshControls(),!1!==t.clearErrors&&this.clearErrors(),this}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}refreshControls(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector)}findSubmitElements(){return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'))}bindSubmitHandlers(){this._onSubmitClickHandler||(this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)}))),this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this.$submits=this.findSubmitElements(),this.$submits.forEach((t=>{t.addEventListener("click",this._onSubmitClickHandler)}))}initialization(){this.refreshControls(),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)})),this.$el.addEventListener("submit",this._onSubmitHandler)}handleSubmitEvent(t){return e(this,void 0,void 0,(function*(){var e,r,i,n,o,s;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const a=yield this.validate();return null===(n=(i=this.config).onAfterValidate)||void 0===n||n.call(i,a,this),a?this.submit():(null===(s=(o=this.config).onValidationError)||void 0===s||s.call(o,this.getErrors(),this),{success:!1,error:this.getErrors()})}))}normalizeTriggers(t,e){const r=null==t?e:Array.isArray(t)?t:[t];return Array.from(new Set(r))}clearFieldValidationEvents(){this.fieldEventHandlers.forEach((({$input:t,event:e,handler:r})=>{t.removeEventListener(e,r)})),this.fieldEventHandlers=[]}bindFieldValidationEvents(){this.clearFieldValidationEvents();const t=this.normalizeTriggers(this.config.validateOn,["submit"]).filter((t=>"submit"!==t)),e=this.normalizeTriggers(this.config.revalidateOn,["input","change"]).filter((t=>"submit"!==t)),r=Array.from(new Set([...t,...e]));if(0===r.length)return;this.getAllFields().forEach((e=>{r.forEach((r=>{const i=()=>{(t.includes(r)||this.hasFieldError(e))&&this.scheduleFieldValidation(e)};e.addEventListener(r,i),this.fieldEventHandlers.push({$input:e,event:r,handler:i})}))}))}scheduleFieldValidation(t){const e=this.liveValidationTimers.get(t);e&&clearTimeout(e);const r=Math.max(0,Number(this.config.validationDebounce)||0),i=()=>{this.validateField(t)};if(0===r)return void i();const n=setTimeout(i,r);this.liveValidationTimers.set(t,n)}setupMutationObserver(){var t;null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0,this.config.observeMutations&&"undefined"!=typeof MutationObserver&&(this.mutationObserver=new MutationObserver((()=>{this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents()})),this.mutationObserver.observe(this.$el,{childList:!0,subtree:!0}))}getErrorContainer(t){const e=this.config.errorContainerAttribute||"data-error-container",r=t.getAttribute(e);if(!r)return null;try{return document.querySelector(r)}catch(t){return console.warn(`[FormFather] Invalid error container selector "${r}"`),null}}toSafeIdPart(t){return t.trim().toLowerCase().replace(/[^a-z0-9_-]+/gi,"-").replace(/^-+|-+$/g,"")||"field"}createErrorId(t){const e=this.config.errorIdPrefix||"form-father-error";return`${this.toSafeIdPart(e)}-${this.toSafeIdPart(this.getFieldKey(t))}-${++T}`}ensureElementId(t,e){return e.id||(e.id=t.getAttribute(_)||this.createErrorId(t)),e.id}addErrorDescription(t,e){e.hasAttribute("role")||e.setAttribute("role","alert");const r=this.ensureElementId(t,e);if(t.setAttribute(_,r),!1===this.config.ariaDescribeErrors)return;const i=(t.getAttribute("aria-describedby")||"").split(/\s+/).filter(Boolean);i.includes(r)||(t.setAttribute("aria-describedby",[...i,r].join(" ")),t.setAttribute(x,r))}removeErrorDescription(t){const e=t.getAttribute(x);if(e){const r=(t.getAttribute("aria-describedby")||"").split(/\s+/).filter((t=>t&&t!==e));r.length>0?t.setAttribute("aria-describedby",r.join(" ")):t.removeAttribute("aria-describedby")}t.removeAttribute(_),t.removeAttribute(x)}showError(t,e){t.setAttribute("aria-invalid","true");const i=this.getErrorContainer(t);if(i)return i.textContent=e,this.addErrorDescription(t,i),void i.removeAttribute("hidden");const n=r(t,this.config.inputWrapperSelector);if(!n)return void console.warn(`Не найдена обёртка для поля с именем: ${t.name||t.type}`);const o=n.showError;if("function"==typeof o){o.call(n,e);const r=n.querySelector("[data-form-father-error]");return void(r&&this.addErrorDescription(t,r))}n.classList.add("input__wrapper--error");let s=n.querySelector("[data-form-father-error]");s||(s=document.createElement("div"),s.setAttribute("data-form-father-error",""),s.setAttribute("role","alert"),t.insertAdjacentElement("afterend",s)),s.textContent=e,this.addErrorDescription(t,s)}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+$++,r=`\n\t\t\t\t<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t<defs>\n\t\t\t\t\t<linearGradient id="${e}" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t\t</linearGradient>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#${e})"></path>\n\t\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t\t</g>\n\t\t\t\t\t</g>\n\t\t\t\t</svg>\n\t\t\t`,i=document.createElement("span");i.innerHTML=r,i.className="button__loader",t.insertAdjacentElement("afterbegin",i),requestAnimationFrame((()=>{i.setAttribute("data-showed","")}))}))}hideLoader(){this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&(e.classList.remove("active"),e.removeAttribute("data-showed"),setTimeout((()=>{e.remove()}),250))}))}hideError(t){var e;const i=r(t,this.config.inputWrapperSelector)||t.parentElement,n=this.getErrorContainer(t);t.removeAttribute("aria-invalid"),this.removeErrorDescription(t),n&&(n.textContent="",n.setAttribute("hidden","")),null===(e=null==i?void 0:i.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==i||i.classList.remove("input__wrapper--error"),i&&i.classList.contains("input__wrapper")&&i.getAttribute("data-type-error")}getErrorSummaryOptions(){return this.config.errorSummary?!0===this.config.errorSummary?{}:this.config.errorSummary:null}getErrorSummaryContainer(t,e){if(t.selector)try{return this.$el.querySelector(t.selector)||document.querySelector(t.selector)}catch(e){return console.warn(`[FormFather] Invalid error summary selector "${t.selector}"`),null}const r=this.$el.querySelector(`[${k}]`);if(r||!e)return r;const i=document.createElement("div");return i.setAttribute(k,""),this.$el.insertAdjacentElement("afterbegin",i),i}prepareErrorSummaryContainer(t){t.setAttribute(k,""),t.hasAttribute("role")||t.setAttribute("role","alert"),t.hasAttribute("tabindex")||t.setAttribute("tabindex","-1"),t.removeAttribute("hidden")}focusErrorSummary(t){try{t.focus({preventScroll:!0})}catch(e){t.focus()}}focusFieldFromSummary(t){"function"==typeof t.scrollIntoView&&t.scrollIntoView({behavior:"smooth",block:"center"});try{t.focus({preventScroll:!0})}catch(e){t.focus()}}renderDefaultErrorSummary(t,e,r){t.textContent="";const i=document.createElement("p");i.setAttribute("data-form-father-summary-title",""),i.textContent=r.title||"Проверьте поля формы";const n=document.createElement("ul");n.setAttribute("data-form-father-summary-list",""),e.forEach((t=>{const e=document.createElement("li"),r=t.field===w?null:this.findFieldByName(t.field);if(r){const i=document.createElement("button");i.type="button",i.setAttribute("data-form-father-summary-field",t.field),i.textContent=t.message,i.addEventListener("click",(()=>this.focusFieldFromSummary(r))),e.appendChild(i)}else{const r=document.createElement("span");r.textContent=t.message,e.appendChild(r)}n.appendChild(e)})),t.append(i,n)}renderErrorSummary(t=this.getErrors()){var e;const r=this.getErrorSummaryOptions();if(!r)return;if(0===t.length)return void this.hideErrorSummary();const i=this.getErrorSummaryContainer(r,!0);if(!i)return;this.prepareErrorSummaryContainer(i);const n=null===(e=r.render)||void 0===e?void 0:e.call(r,t.map((t=>Object.assign({},t))),this);n instanceof HTMLElement?i.replaceChildren(n):"string"==typeof n?i.innerHTML=n:r.render||this.renderDefaultErrorSummary(i,t,r),r.focus&&this.focusErrorSummary(i)}hideErrorSummary(){const t=this.getErrorSummaryOptions()||{},e=this.getErrorSummaryContainer(t,!1);e&&(e.replaceChildren(),e.setAttribute("hidden",""))}syncErrorSummary(){0!==this.errors.length?this.renderErrorSummary():this.hideErrorSummary()}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input, textarea, select"));let r=null,i=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<i&&(i=n,r=t)})),r){const t=r.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,i=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:i,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}focusFirstErroredInput(t){var e;if(0===t.length)return;const r=Array.from(this.$el.querySelectorAll("input, textarea, select")),i=t.slice().sort(((t,e)=>r.indexOf(t)-r.indexOf(e)))[0];null===(e=null==i?void 0:i.focus)||void 0===e||e.call(i,{preventScroll:!0})}getValidationSchema(){return Object.assign(Object.assign({},C.defaultValidationSchema||{}),this.config.validationSchema||{})}getAllFields(t=this.$el){return Array.from(t.querySelectorAll("input, textarea, select"))}getFieldKey(t){return t.name||t.id||t.getAttribute("data-field")||t.type||"field"}hasFieldError(t){const e=this.getFieldKey(t);return"true"===t.getAttribute("aria-invalid")||this.errors.some((t=>t.field===e))}clearFieldErrorRecord(t){const e=this.getFieldKey(t);this.errors=this.errors.filter((t=>t.field!==e))}setErrorRecord(t,e,r,i){this.errors.push({field:t,rule:e,message:r,source:i})}getValidationStateAttribute(){return this.config.validationStateAttribute||"data-form-father-state"}setFieldValidationState(t,e){const r=this.getValidationStateAttribute();e?t.setAttribute(r,e):t.removeAttribute(r)}startFieldValidation(t){const e=++this.fieldValidationTokenCounter;return this.fieldValidationTokens.set(t,e),t.setAttribute("aria-busy","true"),this.setFieldValidationState(t,"validating"),e}isLatestFieldValidation(t,e){return null==e||this.fieldValidationTokens.get(t)===e}finishFieldValidation(t,e,r){this.isLatestFieldValidation(t,e)&&(t.removeAttribute("aria-busy"),this.setFieldValidationState(t,r?"valid":"invalid"))}clearFieldValidationState(t){this.fieldValidationTokens.delete(t),t.removeAttribute("aria-busy"),this.setFieldValidationState(t,null)}setValidationError(t,e,r,i){this.clearFieldErrorRecord(t),this.setErrorRecord(this.getFieldKey(t),e,r,i)}resolveField(t){return"string"!=typeof t?t:this.findFieldByName(t)}parseRuleString(t){const[e,...r]=t.split(":").map((t=>t.trim()));return r.length>0?{rule:e,params:1===r.length?r[0]:r}:e}parseValidationAttribute(t){return(null!=t?t:"").split(/[,\s|]+/).map((t=>t.trim())).filter(Boolean).map((t=>this.parseRuleString(t)))}getRuleName(t){return"string"==typeof t?t:t.rule}getRuleIdentity(t){return JSON.stringify(t)}collectRules(t,e=[],r=new Set){const i=[];t.hasAttribute("required")&&i.push("required"),i.push(...e);const n=this.parseValidationAttribute(t.getAttribute("data-validate")).filter((t=>{const e=this.getRuleName(t);return f(e)||!r.has(e)}));i.push(...n),i.push(...this.parseValidationAttribute(t.getAttribute("data-custom-validate")));const o=[],s=new Set;for(const t of i){const e=this.getRuleName(t);if(!f(e)){console.warn(`[FormFather] Unknown validation rule "${e}"`);continue}const r=this.getRuleIdentity(t);s.has(r)||(s.add(r),o.push(t))}return o}getRuleMessage(t,e,r,i,n){const o=t.getAttribute(`data-error-${e}`);return n||o||r[e]||i}fieldMatchesSchemaDefinition(t,e,r){if(r.selector)try{return t.matches(r.selector)}catch(t){return console.warn(`[FormFather] Invalid validation selector "${r.selector}"`),!1}return this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===e))}getFieldValidationConfig(t,e=this.getValidationSchema()){const r=[],i={};for(const[n,o]of Object.entries(e))this.fieldMatchesSchemaDefinition(t,n,o)&&(r.push(...o.rules),Object.assign(i,o.messages||{}));return{rules:this.collectRules(t,r,new Set(Object.keys(e))),messages:i}}getValidationFields(t,e){const r=new Set,i=t=>{Array.from(t).forEach((t=>{(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&r.add(t)}))};for(const[n,o]of Object.entries(e))if(o.selector)try{i(t.querySelectorAll(o.selector))}catch(t){console.warn(`[FormFather] Invalid validation selector "${o.selector}"`)}else this.getAllFields(t).filter((t=>this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===n)))).forEach((t=>r.add(t)));return i(t.querySelectorAll("[required], [data-validate], [data-custom-validate]")),Array.from(r)}validateInput(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el,n){if(t.hasAttribute("data-no-validate"))return!0;const o=t instanceof HTMLInputElement&&("checkbox"===t.type||"radio"===t.type)?!t.checked:0===t.value.trim().length,s=e.some((t=>"required"===this.getRuleName(t)));if(o&&!s)return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t);for(const o of e){const{rule:e,params:s}="string"==typeof o?{rule:o,params:void 0}:{rule:o.rule,params:o.params},a=f(e);if(!a)continue;const l=yield a.fn(t.value,t,i,s),d="object"==typeof l&&null!==l,c=d?!0===l.valid:!!l;if(!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(d&&l.effect&&(yield l.effect({value:t.value,$input:t,$form:i,params:s})),!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(!c){const i=l,n=this.getRuleMessage(t,e,r,a.defaultMessage,d&&"string"==typeof i.message?i.message:void 0);return this.setValidationError(t,e,n,"client"),this.showError(t,n),!1}if(d&&l.stopOthers)return!0}return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t)}))}runFieldValidation(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el){const n=this.startFieldValidation(t);try{const o=yield this.validateInput(t,e,r,i,n);return this.finishFieldValidation(t,n,o),o}catch(e){throw this.isLatestFieldValidation(t,n)&&this.clearFieldValidationState(t),e}}))}getRadioGroup(t,e){return t instanceof HTMLInputElement&&"radio"===t.type&&t.name?Array.from(e.querySelectorAll('input[type="radio"]')).filter((e=>e.name===t.name)):[]}showFieldError(t,e,r="manual",i=r){const n=this.resolveField(t);return n?(this.setValidationError(n,i,e,r),this.showError(n,e),n.removeAttribute("aria-busy"),this.setFieldValidationState(n,"invalid"),this.syncErrorSummary(),!0):(console.warn(`[FormFather] Field "${String(t)}" not found`),!1)}validateField(t){return e(this,void 0,void 0,(function*(){const e=this.resolveField(t);if(!e)return console.warn(`[FormFather] Field "${String(t)}" not found`),!1;const{rules:r,messages:i}=this.getFieldValidationConfig(e),n=yield this.runFieldValidation(e,r,i);return this.syncErrorSummary(),n}))}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const e=this.getValidationSchema(),r=[],i=new WeakSet;let n=!0;this.errors=[];for(const o of this.getValidationFields(t,e)){if(i.has(o))continue;const s=this.getRadioGroup(o,t);s.length>0?s.forEach((t=>i.add(t))):i.add(o);const{rules:a,messages:l}=this.getFieldValidationConfig(o,e);(yield this.runFieldValidation(o,a,l,t))||(n=!1,r.push(o))}const o=yield this.validateFormValidators();o.ok||(n=!1,r.push(...o.erroredInputs));const s=r.filter((t=>!t.hasAttribute("data-no-error-scroll")));return!n&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(s),!n&&this.config.focusFirstErroredInput&&this.focusFirstErroredInput(s),n?this.hideErrorSummary():this.renderErrorSummary(),n}))}disableSubmit(){this.$submits.forEach((t=>{t.setAttribute("disabled","")}))}enableSubmit(){this.$submits.forEach((t=>{t.removeAttribute("disabled")}))}clearInputs(){var t;null===(t=this.inputs)||void 0===t||t.forEach((t=>{var e;const r=t;"radio"===r.type||"checkbox"===r.type?r.checked=!1:(r.value="",null===(e=r.parentElement)||void 0===e||e.classList.remove("filled"),r.dispatchEvent(new Event("input")))}))}_prefersReducedMotion(){var t;return"undefined"!=typeof window&&(null===(t=window.matchMedia)||void 0===t?void 0:t.call(window,"(prefers-reduced-motion: reduce)").matches)}_canAnimate(t){return!(!t||!("animate"in t)||this._prefersReducedMotion())}_cancelAnimations(t){var e;const r=t;null===(e=null==r?void 0:r.getAnimations)||void 0===e||e.call(r).forEach((t=>t.cancel()))}showErrorForm(t){var e;const r=this.$el.querySelectorAll(this.config.inputWrapperSelector),i=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||i&&i.nextElementSibling!==n?(n=document.createElement("div"),n.className="error-block-under-input__wrapper",n.innerHTML='\n <div class="error-block-under-input error-block-under-input--warning" style="display:grid;grid-template-columns:auto 1fr;gap:8px;align-items:start;">\n <div class="error-block-under-input__icon-wrapper" style="position:relative;width:20px;height:20px;">\n <span class="error-block-under-input__icon" style="display:block;width:20px;height:20px;"></span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:block;position:absolute;inset:0;"></span>\n </div>\n <p class="error-block-under-input__text" style="margin:0;">\n <span class="error-block-under-input__main-text"></span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>\n ',Object.assign(n.style,{boxSizing:"border-box",overflow:"hidden",height:"0px",paddingTop:"0px",opacity:"0"}),i?i.insertAdjacentElement("afterend",n):this.$el.appendChild(n)):(n.style.boxSizing="border-box",n.style.overflow="hidden"),n.querySelector(".error-block-under-input__main-text").textContent=t;const o=n.querySelector(".error-block-under-input__secondary-text");o&&(o.textContent=""),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const s=n.style.height,a=n.style.paddingTop;n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`;const l=n.clientHeight;if(n.style.height=s||"0px",n.style.paddingTop=a||"0px",parseFloat(n.style.height)===l&&1===parseFloat(n.style.opacity||"1"))this._ringIcon(n);else{if(this._cancelAnimations(n),this._canAnimate(n)){n.animate([{height:`${parseFloat(n.style.height)||0}px`,paddingTop:`${parseFloat(n.style.paddingTop)||0}px`,opacity:parseFloat(n.style.opacity||"0")},{height:`${l}px`,paddingTop:`${this._anim.paddingTopOpened}px`,opacity:1}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1"}))}else n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1";this._ringIcon(n)}this.$el.addEventListener("input",(()=>this.hideErrorForm()),{passive:!0,once:!0})}hideErrorForm(){const t=this.$el.querySelectorAll(this.config.inputWrapperSelector),e=t[t.length-1],r=(null==e?void 0:e.nextElementSibling)||this.$el.querySelector(".error-block-under-input__wrapper");if(!r||!r.classList.contains("error-block-under-input__wrapper"))return;if((""===r.style.height||"auto"===r.style.height)&&(r.style.height=`${r.clientHeight}px`),r.style.paddingTop=r.style.paddingTop||`${this._anim.paddingTopOpened}px`,r.style.opacity=r.style.opacity||"1",this._cancelAnimations(r),this._canAnimate(r)){r.animate([{height:`${parseFloat(r.style.height)||r.clientHeight}px`,paddingTop:`${parseFloat(r.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(r.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{r.remove()}))}else r.remove()}_ringIcon(t){const e=t.querySelector(".error-block-under-input__icon--animated");e&&(this._cancelAnimations(e),this._canAnimate(e)&&e.animate([{transform:"rotate(0deg) scale(1)",opacity:0},{transform:"rotate(-8deg) scale(1.05)",opacity:1,offset:.15},{transform:"rotate(8deg) scale(1.05)",opacity:1,offset:.3},{transform:"rotate(-6deg) scale(1.03)",opacity:1,offset:.45},{transform:"rotate(6deg) scale(1.03)",opacity:1,offset:.6},{transform:"rotate(0deg) scale(1)",opacity:0}],{duration:this._anim.iconDuration,easing:"ease"}))}destroy(){var t;this._onSubmitHandler&&(this.$el.removeEventListener("submit",this._onSubmitHandler),this._onSubmitHandler=void 0),this._onSubmitClickHandler&&(this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this._onSubmitClickHandler=void 0),this.clearFieldValidationEvents(),null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0;const e=this.$el.querySelector(".error-block-under-input__wrapper");e&&(this._cancelAnimations(e),e.remove()),this.hideErrorSummary(),this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&e.remove()}));this.$el.querySelectorAll("input, textarea, select").forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),document.documentElement.removeAttribute("data-now-scrolling"),this.inputs=null,this.$submits=[],this.errors=[],this.waitResponse=!1,O.delete(this.$el)}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):0!==this.$submits.length||(console.warn("В форме нет кнопки с типом submit"),!1)}formDataToObject(t){const e={};return t.forEach(((t,r)=>{Object.prototype.hasOwnProperty.call(e,r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}appendFormDataValue(t,e,r){null!=r&&(Array.isArray(r)?r.forEach(((r,i)=>{this.appendFormDataValue(t,`${e}[${i}]`,r)})):r instanceof Blob?t.append(e,r):t.append(e,"object"==typeof r?JSON.stringify(r):String(r)))}objectToFormData(t){const e=new FormData;return Object.entries(t).forEach((([t,r])=>{this.appendFormDataValue(e,t,r)})),e}stringifyFormDataValue(t){return"string"==typeof t?t:t.name}formDataToUrlSearchParams(t){const e=new URLSearchParams;return t.forEach(((t,r)=>{e.append(r,this.stringifyFormDataValue(t))})),e}formDataToTextBody(t){return Array.from(t).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(this.stringifyFormDataValue(e))}`)).join("\n")}isBodylessMethod(t){return"GET"===t||"HEAD"===t}appendQueryString(t,e){const r=e.toString();if(!r)return t;const i=t.includes("?")?"&":"?";return`${t}${i}${r}`}findFieldByName(t){return Array.from(this.$el.querySelectorAll("input, textarea, select")).find((e=>e.name===t))||null}parseResponseBody(t){return e(this,void 0,void 0,(function*(){try{const e=yield t.json();return e&&"object"==typeof e?e:{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}catch(t){return{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}}))}handleUnsuccessfulResponse(t){if(!0===t.error){if(t["error-msg"]&&(this.setErrorRecord(w,"server",t["error-msg"],"server"),this.showErrorForm(t["error-msg"])),t.errors){const e=[];t.errors.forEach((t=>{const r=this.findFieldByName(t.name);r?(e.push(r),this.setValidationError(r,"server",t["error-msg"],"server"),this.showError(r,t["error-msg"]),r.removeAttribute("aria-busy"),this.setFieldValidationState(r,"invalid")):(this.setErrorRecord(t.name,"server",t["error-msg"],"server"),console.warn(`Не найдено поле с именем: ${t.name}, для вывода ошибки: ${t["error-msg"]}`))})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(e),!0===this.config.focusFirstErroredInput&&this.focusFirstErroredInput(e)}this.syncErrorSummary()}}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",r=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",i=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,o={};const s=a(this.$el);let l=this.formDataToObject(s);if(this.config.logging)for(const t of s.keys())console.log(s.get(t));const d="function"==typeof this.config.wrapData;"function"==typeof this.config.wrapData&&(l=this.config.wrapData(l));const c=d?this.objectToFormData(l):s;if(this.isBodylessMethod(r))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(c)),{method:r,headers:o});switch(i){case"application/x-www-form-urlencoded":n=this.formDataToUrlSearchParams(c).toString(),o["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=c;break;case"text/plain":n=this.formDataToTextBody(c),o["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),o["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${i}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(c).toString(),o["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:r,body:n,headers:o})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,r,i,n,o,s,a,d,c,u,h;if(!this.waitResponse){let m=!1;const f=()=>{m||(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),m=!0)};try{if(!1===(yield null===(e=(t=this.config).onBeforeSubmit)||void 0===e?void 0:e.call(t,this)))return{success:!1};!0===this.config.showLoaderButton&&this.showLoader(),null===(i=(r=this.config).onSubmit)||void 0===i||i.call(r,this),this.waitResponse=!0;const u=yield this.sendData(),h=yield this.parseResponseBody(u);return f(),null===(o=(n=this.config).onResponse)||void 0===o||o.call(n,h,this),u.ok&&!0===h.success?null===(a=(s=this.config).onResponseSuccess)||void 0===a||a.call(s,h,this):(null===(c=(d=this.config).onResponseUnsuccess)||void 0===c||c.call(d,h,this),this.handleUnsuccessfulResponse(h)),l(h),{success:u.ok&&!0===h.success,response:u,responseBody:h}}catch(t){throw f(),null===(h=(u=this.config).onSubmitError)||void 0===h||h.call(u,t,this),t}}}))}}return t.FORM_ERROR_FIELD=w,t.blockScrollBody=function(){window.lastBlockScrollPosition=window.pageYOffset,document.body.style.top=-window.lastBlockScrollPosition+"px",document.body.classList.add("body--block-scroll")},t.closest=r,t.createFieldValidator=b,t.createFormValidator=V,t.createLengthValidator=function(t,e){return b((e=>{const r=!1===t.trim?e:e.trim();return 0===r.length&&!0===t.emptyIsValid||!("number"==typeof t.min&&r.length<t.min)&&!("number"==typeof t.max&&r.length>t.max)}),e)},t.createPatternValidator=function(t,e){return b((e=>(t.lastIndex=0,t.test(e))),e)},t.createSchemaValidator=y,t.dateOrder=function(t,e,r="Дата окончания должна быть позже даты начала",i="date-order"){return V((({values:r})=>{const i=A(r,t),n=A(r,e);return!(!F(i)&&!F(n))||Date.parse(String(i))<=Date.parse(String(n))}),{field:e,rule:i,message:r})},t.default=C,t.getAllValidators=function(){return new Map(h)},t.getValidator=f,t.isEmailValid=function(t){return/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(t)},t.isPhoneValid=function(t){const e=[];return Array.from(t).forEach((t=>{(Number(t)||0===Number(t))&&e.push(t)})),!(e.length<11||e.length<12&&"+"===t[1])},t.isUrlValid=s,t.parseCommonResponseProperties=l,t.registerFieldValidator=function(t,e,r,i={}){m(t,b(e),r,i)},t.registerSchemaValidator=function(t,e,r,i={}){const{override:n}=i;m(t,y(e,function(t,e){var r={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(r[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(i=Object.getOwnPropertySymbols(t);n<i.length;n++)e.indexOf(i[n])<0&&Object.prototype.propertyIsEnumerable.call(t,i[n])&&(r[i[n]]=t[i[n]])}return r}(i,["override"])),r,{override:n})},t.registerValidator=m,t.requiredIf=function(t,r,i="Поле обязательно",n="conditional-required"){return V((i=>e(this,void 0,void 0,(function*(){return!(yield r(i))||!F(A(i.values,t))}))),{field:t,rule:n,message:i})},t.sameAsField=function(t,e,r="Значения не совпадают",i="same-as-field"){return V((({values:r})=>A(r,t)===A(r,e)),{field:t,rule:i,message:r})},t.serializeFormToJSON=d,t.serializeToFormData=a,t.unblockScrollBody=function(){document.body.classList.remove("body--block-scroll"),document.body.style.top="",window.scrollTo(0,window.lastBlockScrollPosition||0)},Object.defineProperty(t,"__esModule",{value:!0}),t}({});
2
- //# sourceMappingURL=FormFather.min.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FormFather.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}