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.
- package/CHANGELOG.md +223 -0
- package/RESPONSE_API.md +233 -0
- package/demos/index.html +198 -0
- package/demos/main.js +149 -0
- package/demos/styles.css +276 -0
- package/dist/FormFather.min.js +2 -0
- package/dist/FormFather.min.js.map +1 -0
- package/{index.d.ts → dist/index.d.ts} +2 -1
- package/dist/index.js +1791 -0
- package/dist/index.js.map +1 -0
- package/docs/api/README.md +153 -0
- package/docs/demo/README.md +37 -0
- package/docs/en/README.md +270 -0
- package/docs/recipes/README.md +150 -0
- package/package.json +47 -33
- package/FormFather.min.js +0 -2
- package/FormFather.min.js.map +0 -1
- package/index.js +0 -2076
- package/index.js.map +0 -1
- /package/{types → dist/types}/adapters.d.ts +0 -0
- /package/{types → dist/types}/helpers.d.ts +0 -0
- /package/{types → dist/types}/index.d.ts +0 -0
- /package/{types → dist/types}/validators.d.ts +0 -0
package/index.js
DELETED
|
@@ -1,2076 +0,0 @@
|
|
|
1
|
-
/******************************************************************************
|
|
2
|
-
Copyright (c) Microsoft Corporation.
|
|
3
|
-
|
|
4
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
5
|
-
purpose with or without fee is hereby granted.
|
|
6
|
-
|
|
7
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
9
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
11
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
12
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
13
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
14
|
-
***************************************************************************** */
|
|
15
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function __rest(s, e) {
|
|
19
|
-
var t = {};
|
|
20
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
21
|
-
t[p] = s[p];
|
|
22
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
23
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
24
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
25
|
-
t[p[i]] = s[p[i]];
|
|
26
|
-
}
|
|
27
|
-
return t;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
31
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
32
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
33
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
34
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
35
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
36
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
41
|
-
var e = new Error(message);
|
|
42
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Возвращает форматированную дату.
|
|
47
|
-
*
|
|
48
|
-
* @param {string} dateValue
|
|
49
|
-
* @param {string} format
|
|
50
|
-
*
|
|
51
|
-
* @returns {string}
|
|
52
|
-
*/
|
|
53
|
-
function formatDate(dateValue, format) {
|
|
54
|
-
const date = new Date(dateValue);
|
|
55
|
-
/**
|
|
56
|
-
* Добавляет ведущие нули.
|
|
57
|
-
*
|
|
58
|
-
* @param {number} num
|
|
59
|
-
*
|
|
60
|
-
* @returns {string}
|
|
61
|
-
*/
|
|
62
|
-
const pad = (num) => num.toString().padStart(2, '0');
|
|
63
|
-
const day = pad(date.getDate());
|
|
64
|
-
const month = pad(date.getMonth() + 1);
|
|
65
|
-
const year = date.getFullYear().toString();
|
|
66
|
-
return format.replace('dd', day).replace('mm', month).replace('yyyy', year);
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Находит родительский элемент по заданому селектору.
|
|
70
|
-
*
|
|
71
|
-
* @param {HTMLElement | null} $el - Элемент, чего родителя нужно найти.
|
|
72
|
-
* @param {string} selector - Селектор родителя.
|
|
73
|
-
*
|
|
74
|
-
* @returns {HTMLElement | null} Элемент родителя. Но если родителя с таким селектори не было найдено возвращается null.
|
|
75
|
-
*/
|
|
76
|
-
function closest($el, selector) {
|
|
77
|
-
if ($el == null)
|
|
78
|
-
return null;
|
|
79
|
-
let parent = $el;
|
|
80
|
-
while (parent) {
|
|
81
|
-
if (parent.matches(selector)) {
|
|
82
|
-
return parent;
|
|
83
|
-
}
|
|
84
|
-
parent = parent.parentElement;
|
|
85
|
-
}
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
/** Включает блокировку скролла страницы */
|
|
89
|
-
function blockScrollBody() {
|
|
90
|
-
window.lastBlockScrollPosition = window.pageYOffset;
|
|
91
|
-
document.body.style.top = `${-window.lastBlockScrollPosition}px`;
|
|
92
|
-
document.body.classList.add('body--block-scroll');
|
|
93
|
-
}
|
|
94
|
-
/** Отменяет блокировку скролла страницы */
|
|
95
|
-
function unblockScrollBody() {
|
|
96
|
-
document.body.classList.remove('body--block-scroll');
|
|
97
|
-
document.body.style.top = '';
|
|
98
|
-
window.scrollTo(0, window.lastBlockScrollPosition || 0);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Проверяет является ли строка валидным адресом электронной почты.
|
|
102
|
-
*
|
|
103
|
-
* @param {string} value - Строка, которую нужно проверить.
|
|
104
|
-
*
|
|
105
|
-
* @returns {boolean} Является ли строка валидным адресом электронной почты true/false.
|
|
106
|
-
*/
|
|
107
|
-
function isEmailValid(value) {
|
|
108
|
-
return /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(value);
|
|
109
|
-
}
|
|
110
|
-
const HTTP_PROTOCOLS = ['http:', 'https:'];
|
|
111
|
-
function getNormalizedUrlString(originalUrlString) {
|
|
112
|
-
const trimmedUrl = originalUrlString.trim();
|
|
113
|
-
if (trimmedUrl.length === 0)
|
|
114
|
-
return '';
|
|
115
|
-
if (trimmedUrl.startsWith('//')) {
|
|
116
|
-
return `https:${trimmedUrl}`;
|
|
117
|
-
}
|
|
118
|
-
return /^[a-z][a-z\d+.-]*:\/\//i.test(trimmedUrl) ? trimmedUrl : `https://${trimmedUrl}`;
|
|
119
|
-
}
|
|
120
|
-
function isValidIpv4(hostname) {
|
|
121
|
-
const parts = hostname.split('.');
|
|
122
|
-
if (parts.length !== 4)
|
|
123
|
-
return false;
|
|
124
|
-
return parts.every(part => {
|
|
125
|
-
if (!/^\d{1,3}$/.test(part))
|
|
126
|
-
return false;
|
|
127
|
-
const num = Number(part);
|
|
128
|
-
return num >= 0 && num <= 255;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
function isValidIpv6(hostname) {
|
|
132
|
-
if (!hostname.startsWith('[') || !hostname.endsWith(']'))
|
|
133
|
-
return false;
|
|
134
|
-
const normalizedHostname = hostname.slice(1, -1);
|
|
135
|
-
return normalizedHostname.includes(':') && /^[\da-f:.]+$/i.test(normalizedHostname);
|
|
136
|
-
}
|
|
137
|
-
function isValidDomainLabel(label) {
|
|
138
|
-
return label.length > 0 && label.length <= 63 && /^[a-z\d-]+$/i.test(label) && !label.startsWith('-') && !label.endsWith('-');
|
|
139
|
-
}
|
|
140
|
-
function isValidDomainHostname(hostname) {
|
|
141
|
-
const labels = hostname.split('.');
|
|
142
|
-
return labels.length >= 2 && labels.every(isValidDomainLabel);
|
|
143
|
-
}
|
|
144
|
-
function isAllowedHostname(hostname) {
|
|
145
|
-
if (!hostname)
|
|
146
|
-
return false;
|
|
147
|
-
return (hostname === 'localhost' || isValidIpv4(hostname) || isValidIpv6(hostname) || isValidDomainHostname(hostname));
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Проверяет является ли строка валидным url.
|
|
151
|
-
*
|
|
152
|
-
* @param {string} originalUrlString - Строка, которую нужно проверить.
|
|
153
|
-
*
|
|
154
|
-
* @returns {boolean} Является ли строка валидным url адресом true/false.
|
|
155
|
-
*/
|
|
156
|
-
function isUrlValid(originalUrlString) {
|
|
157
|
-
const urlString = getNormalizedUrlString(originalUrlString);
|
|
158
|
-
if (urlString.length === 0)
|
|
159
|
-
return false;
|
|
160
|
-
try {
|
|
161
|
-
const url = new URL(urlString);
|
|
162
|
-
return HTTP_PROTOCOLS.includes(url.protocol) && isAllowedHostname(url.hostname);
|
|
163
|
-
}
|
|
164
|
-
catch (_a) {
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Проверяет является ли строка валидным номером телефона.
|
|
170
|
-
*
|
|
171
|
-
* @param {string} value - Строка, которую нужно проверить.
|
|
172
|
-
*
|
|
173
|
-
* @returns {boolean} Является ли строка валидным номером телефона true/false.
|
|
174
|
-
*/
|
|
175
|
-
function isPhoneValid(value) {
|
|
176
|
-
const numbersArray = [];
|
|
177
|
-
Array.from(value).forEach(char => {
|
|
178
|
-
if (!!Number(char) || Number(char) === 0)
|
|
179
|
-
numbersArray.push(char);
|
|
180
|
-
});
|
|
181
|
-
if (numbersArray.length < 11 || (numbersArray.length < 12 && value[1] === '+'))
|
|
182
|
-
return false;
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Кастомная сериализация формы в объект FormData.
|
|
187
|
-
*
|
|
188
|
-
* @param {HTMLElement} $element - DOM элемент для сериализации.
|
|
189
|
-
*
|
|
190
|
-
* @returns {FormData} Сериализованные данные формы в объекте FormData.
|
|
191
|
-
*/
|
|
192
|
-
function serializeToFormData($element) {
|
|
193
|
-
const elements = $element.tagName === 'FORM'
|
|
194
|
-
? Array.from($element.elements)
|
|
195
|
-
: Array.from($element.querySelectorAll('input, select, textarea, button'));
|
|
196
|
-
const data = new FormData();
|
|
197
|
-
elements.forEach(element => {
|
|
198
|
-
if (!(element instanceof HTMLInputElement ||
|
|
199
|
-
element instanceof HTMLSelectElement ||
|
|
200
|
-
element instanceof HTMLTextAreaElement ||
|
|
201
|
-
element instanceof HTMLButtonElement)) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (!element.name || element.disabled || element.hasAttribute('data-no-serialize')) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (element instanceof HTMLButtonElement) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
const { name, type } = element;
|
|
211
|
-
if (type === 'file') {
|
|
212
|
-
if (element.multiple) {
|
|
213
|
-
const files = element.files;
|
|
214
|
-
if (files) {
|
|
215
|
-
for (let i = 0; i < files.length; i++) {
|
|
216
|
-
// Добавляем каждый файл с уникальным ключом
|
|
217
|
-
data.append(`${name}[${i}]`, files[i]);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
const files = element.files;
|
|
223
|
-
if (files && files[0])
|
|
224
|
-
data.append(name, files[0]);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
let value;
|
|
229
|
-
if (type === 'select-one' || type === 'select-multiple') {
|
|
230
|
-
// Обработка select элементов
|
|
231
|
-
const select = element;
|
|
232
|
-
if (type === 'select-multiple') {
|
|
233
|
-
const selectedOptions = Array.from(select.selectedOptions).map(option => option.value);
|
|
234
|
-
selectedOptions.forEach((val, index) => {
|
|
235
|
-
data.append(`${name}[${index}]`, val);
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
value = select.value;
|
|
240
|
-
if (value)
|
|
241
|
-
data.append(name, value);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
else if (type === 'tel') {
|
|
245
|
-
value = element.value
|
|
246
|
-
.split(/[\s()-]/)
|
|
247
|
-
.join('')
|
|
248
|
-
.replace(/^8/, '+7');
|
|
249
|
-
}
|
|
250
|
-
else if (type === 'checkbox' || type === 'radio') {
|
|
251
|
-
if (!element.checked)
|
|
252
|
-
return;
|
|
253
|
-
value = element.value;
|
|
254
|
-
}
|
|
255
|
-
else if (type === 'date') {
|
|
256
|
-
const format = element.getAttribute('data-date-format');
|
|
257
|
-
value = format ? formatDate(element.value, format) : element.value;
|
|
258
|
-
}
|
|
259
|
-
else {
|
|
260
|
-
value = element.value;
|
|
261
|
-
}
|
|
262
|
-
if (value && type !== 'select-one' && type !== 'select-multiple') {
|
|
263
|
-
data.append(name, value);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
return data;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Функция для разбора общих свойств ответа.
|
|
271
|
-
*
|
|
272
|
-
* @param {any} responseBody - Тело ответа.
|
|
273
|
-
*/
|
|
274
|
-
function parseCommonResponseProperties(responseBody) {
|
|
275
|
-
if (Object.prototype.hasOwnProperty.call(responseBody, 'redirect-url')) {
|
|
276
|
-
if (responseBody['redirect-url-delay']) {
|
|
277
|
-
const redirectUrlDelay = Number(responseBody['redirect-url-delay']);
|
|
278
|
-
setTimeout(() => {
|
|
279
|
-
window.location.href = responseBody['redirect-url'];
|
|
280
|
-
}, redirectUrlDelay);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
window.location.href = responseBody['redirect-url'];
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
else if (Object.prototype.hasOwnProperty.call(responseBody, 'reload')) {
|
|
287
|
-
if (responseBody.reload === true) {
|
|
288
|
-
if (responseBody['reload-delay']) {
|
|
289
|
-
const reloadDelay = Number(responseBody['reload-delay']);
|
|
290
|
-
setTimeout(() => {
|
|
291
|
-
window.location.reload();
|
|
292
|
-
}, reloadDelay);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
window.location.reload();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// Не показываем тост если делается редирект или обновление страницы (чтобы небыло вспышек) - в этом случае тосты обрабытываются в ответе onResponseSuccess | onResponseUnsuccess
|
|
300
|
-
else {
|
|
301
|
-
if (Object.prototype.hasOwnProperty.call(responseBody, 'toast')) {
|
|
302
|
-
const toasts = responseBody['toast'];
|
|
303
|
-
const normalizedToasts = Array.isArray(toasts) ? toasts : [toasts];
|
|
304
|
-
normalizedToasts.forEach(toast => {
|
|
305
|
-
window.showToast(toast);
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
else if (Object.prototype.hasOwnProperty.call(responseBody, 'error-toast')) {
|
|
309
|
-
/** @deprecated */
|
|
310
|
-
window.showToast(responseBody['error-toast']);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Сериализует данные формы в объект JSON.
|
|
316
|
-
*
|
|
317
|
-
* @param {HTMLFormElement} form - Элемент формы.
|
|
318
|
-
*
|
|
319
|
-
* @returns {Object} Сериализованные данные формы.
|
|
320
|
-
*/
|
|
321
|
-
function serializeFormToJSON(form) {
|
|
322
|
-
const obj = {};
|
|
323
|
-
const formData = new FormData(form);
|
|
324
|
-
formData.forEach((value, key) => {
|
|
325
|
-
if (obj.hasOwnProperty(key)) {
|
|
326
|
-
if (!Array.isArray(obj[key])) {
|
|
327
|
-
obj[key] = [obj[key]];
|
|
328
|
-
}
|
|
329
|
-
obj[key].push(value);
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
obj[key] = value;
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
return obj;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
var _a$1;
|
|
339
|
-
/* ---------- singleton-storage на globalThis ---------- */
|
|
340
|
-
const GLOBAL_KEY = Symbol.for('FormFather.validators');
|
|
341
|
-
const validators = (_a$1 = globalThis[GLOBAL_KEY]) !== null && _a$1 !== void 0 ? _a$1 : (globalThis[GLOBAL_KEY] = new Map());
|
|
342
|
-
/* ---------- API ---------- */
|
|
343
|
-
function registerValidator(name, fn, defaultMessage, { override = false } = {}) {
|
|
344
|
-
if (validators.has(name) && !override) {
|
|
345
|
-
console.warn(`[FormFather] Validator "${name}" already exists; pass { override: true } to replace`);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
validators.set(name, { fn, defaultMessage });
|
|
349
|
-
}
|
|
350
|
-
const getValidator = (name) => validators.get(name);
|
|
351
|
-
function getAllValidators() {
|
|
352
|
-
return new Map(validators); // копию, чтобы никто не мутировал оригинал
|
|
353
|
-
}
|
|
354
|
-
/* ---------- built-in rules ---------- */
|
|
355
|
-
function registerBuiltinValidator(name, fn, defaultMessage) {
|
|
356
|
-
if (!validators.has(name)) {
|
|
357
|
-
validators.set(name, { fn, defaultMessage });
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
registerBuiltinValidator('required', (value, $input, $form) => {
|
|
361
|
-
if ($input && $input.type) {
|
|
362
|
-
if ($input.type === 'checkbox') {
|
|
363
|
-
return $input.checked;
|
|
364
|
-
}
|
|
365
|
-
if ($input.type === 'radio') {
|
|
366
|
-
const group = $form.querySelectorAll(`input[type="radio"][name="${$input.name}"]`);
|
|
367
|
-
return Array.from(group).some(el => el.checked);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
// fallback — если нет $input (например, в unit-тестах)
|
|
371
|
-
return value.trim().length > 0;
|
|
372
|
-
}, 'Пустое значение');
|
|
373
|
-
registerBuiltinValidator('email', v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), 'Неверный формат');
|
|
374
|
-
registerBuiltinValidator('tel', v => /^\+7\d{10}$/.test(v), 'Неверный формат');
|
|
375
|
-
registerBuiltinValidator('url', v => isUrlValid(v), 'Неверный формат');
|
|
376
|
-
registerBuiltinValidator('not-numbers', v => !/[0-9]/.test(v), 'Неверный формат');
|
|
377
|
-
|
|
378
|
-
function readFirstIssueMessage(error) {
|
|
379
|
-
var _a;
|
|
380
|
-
if (!error || typeof error !== 'object')
|
|
381
|
-
return undefined;
|
|
382
|
-
const issues = error.issues || error.errors;
|
|
383
|
-
if (Array.isArray(issues)) {
|
|
384
|
-
const firstMessage = (_a = issues.find(issue => typeof (issue === null || issue === void 0 ? void 0 : issue.message) === 'string')) === null || _a === void 0 ? void 0 : _a.message;
|
|
385
|
-
if (firstMessage)
|
|
386
|
-
return firstMessage;
|
|
387
|
-
}
|
|
388
|
-
return typeof error.message === 'string' ? error.message : undefined;
|
|
389
|
-
}
|
|
390
|
-
function resolveMessage(error, message) {
|
|
391
|
-
if (typeof message === 'function')
|
|
392
|
-
return message(error);
|
|
393
|
-
return message || readFirstIssueMessage(error);
|
|
394
|
-
}
|
|
395
|
-
function normalizeResult(result, message) {
|
|
396
|
-
if (typeof result === 'object' && result !== null)
|
|
397
|
-
return result;
|
|
398
|
-
if (result === undefined)
|
|
399
|
-
return true;
|
|
400
|
-
return result ? true : { valid: false, message };
|
|
401
|
-
}
|
|
402
|
-
function createSchemaValidator(schema, options = {}) {
|
|
403
|
-
return (value, $input, $form, params) => __awaiter(this, void 0, void 0, function* () {
|
|
404
|
-
const mappedValue = options.mapValue ? options.mapValue(value, $input, $form, params) : value;
|
|
405
|
-
try {
|
|
406
|
-
if (typeof schema === 'function') {
|
|
407
|
-
return normalizeResult(yield schema(mappedValue), resolveMessage(undefined, options.message));
|
|
408
|
-
}
|
|
409
|
-
if (typeof schema.safeParse === 'function') {
|
|
410
|
-
const result = yield schema.safeParse(mappedValue);
|
|
411
|
-
if (result.success)
|
|
412
|
-
return true;
|
|
413
|
-
return { valid: false, message: resolveMessage(result.error || result, options.message) };
|
|
414
|
-
}
|
|
415
|
-
if (typeof schema.parse === 'function') {
|
|
416
|
-
yield schema.parse(mappedValue);
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
return { valid: false, message: resolveMessage(error, options.message) };
|
|
422
|
-
}
|
|
423
|
-
return { valid: false, message: resolveMessage(undefined, options.message) };
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
function registerSchemaValidator(name, schema, defaultMessage, options = {}) {
|
|
427
|
-
const { override } = options, adapterOptions = __rest(options, ["override"]);
|
|
428
|
-
registerValidator(name, createSchemaValidator(schema, adapterOptions), defaultMessage, { override });
|
|
429
|
-
}
|
|
430
|
-
function registerFieldValidator(name, predicate, defaultMessage, options = {}) {
|
|
431
|
-
registerValidator(name, createFieldValidator(predicate), defaultMessage, options);
|
|
432
|
-
}
|
|
433
|
-
function createFieldValidator(predicate, message) {
|
|
434
|
-
return (value, $input, $form, params) => __awaiter(this, void 0, void 0, function* () {
|
|
435
|
-
const valid = yield predicate(value, $input, $form, params);
|
|
436
|
-
return valid ? true : { valid: false, message };
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
function createPatternValidator(pattern, message) {
|
|
440
|
-
return createFieldValidator(value => {
|
|
441
|
-
pattern.lastIndex = 0;
|
|
442
|
-
return pattern.test(value);
|
|
443
|
-
}, message);
|
|
444
|
-
}
|
|
445
|
-
function createLengthValidator(options, message) {
|
|
446
|
-
return createFieldValidator(value => {
|
|
447
|
-
const normalizedValue = options.trim === false ? value : value.trim();
|
|
448
|
-
if (normalizedValue.length === 0 && options.emptyIsValid === true)
|
|
449
|
-
return true;
|
|
450
|
-
if (typeof options.min === 'number' && normalizedValue.length < options.min)
|
|
451
|
-
return false;
|
|
452
|
-
if (typeof options.max === 'number' && normalizedValue.length > options.max)
|
|
453
|
-
return false;
|
|
454
|
-
return true;
|
|
455
|
-
}, message);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
var _a;
|
|
459
|
-
// --- shared cross-file state (singleton via globalThis) ---
|
|
460
|
-
const FORM_GLOBAL_KEY = Symbol.for('formfather.shared');
|
|
461
|
-
const __shared =
|
|
462
|
-
// уже существует? используем его
|
|
463
|
-
(_a = globalThis[FORM_GLOBAL_KEY]) !== null && _a !== void 0 ? _a :
|
|
464
|
-
// иначе создаём
|
|
465
|
-
(globalThis[FORM_GLOBAL_KEY] = {
|
|
466
|
-
defaultParams: {},
|
|
467
|
-
defaultValidationSchema: undefined,
|
|
468
|
-
});
|
|
469
|
-
const FORM_ERROR_FIELD = '_form';
|
|
470
|
-
function readFormValue(values, field) {
|
|
471
|
-
return values[field];
|
|
472
|
-
}
|
|
473
|
-
function isEmptyFormValue(value) {
|
|
474
|
-
if (Array.isArray(value))
|
|
475
|
-
return value.length === 0;
|
|
476
|
-
return value === undefined || value === null || String(value).trim().length === 0;
|
|
477
|
-
}
|
|
478
|
-
function resolveFormValidatorIssue(context, issue, fallbackField, fallbackRule) {
|
|
479
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
480
|
-
const resolvedIssue = typeof issue === 'function' ? yield issue(context) : issue;
|
|
481
|
-
if (typeof resolvedIssue === 'string') {
|
|
482
|
-
return {
|
|
483
|
-
field: fallbackField,
|
|
484
|
-
rule: fallbackRule,
|
|
485
|
-
message: resolvedIssue,
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
return Object.assign({ field: fallbackField, rule: fallbackRule }, resolvedIssue);
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
function createFormValidator(predicate, issue) {
|
|
492
|
-
return (context) => __awaiter(this, void 0, void 0, function* () {
|
|
493
|
-
const valid = yield predicate(context);
|
|
494
|
-
return valid ? true : resolveFormValidatorIssue(context, issue);
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
function sameAsField(field, otherField, message = 'Значения не совпадают', rule = 'same-as-field') {
|
|
498
|
-
return createFormValidator(({ values }) => readFormValue(values, field) === readFormValue(values, otherField), { field, rule, message });
|
|
499
|
-
}
|
|
500
|
-
function requiredIf(field, condition, message = 'Поле обязательно', rule = 'conditional-required') {
|
|
501
|
-
return createFormValidator((context) => __awaiter(this, void 0, void 0, function* () { return !(yield condition(context)) || !isEmptyFormValue(readFormValue(context.values, field)); }), { field, rule, message });
|
|
502
|
-
}
|
|
503
|
-
function dateOrder(startField, endField, message = 'Дата окончания должна быть позже даты начала', rule = 'date-order') {
|
|
504
|
-
return createFormValidator(({ values }) => {
|
|
505
|
-
const start = readFormValue(values, startField);
|
|
506
|
-
const end = readFormValue(values, endField);
|
|
507
|
-
if (isEmptyFormValue(start) || isEmptyFormValue(end))
|
|
508
|
-
return true;
|
|
509
|
-
return Date.parse(String(start)) <= Date.parse(String(end));
|
|
510
|
-
}, { field: endField, rule, message });
|
|
511
|
-
}
|
|
512
|
-
let loaderIdCounter = 0;
|
|
513
|
-
let errorIdCounter = 0;
|
|
514
|
-
const FORM_INSTANCES = new WeakMap();
|
|
515
|
-
const ERROR_DESCRIBEDBY_ID_ATTRIBUTE = 'data-form-father-describedby-id';
|
|
516
|
-
const ADDED_DESCRIBEDBY_ID_ATTRIBUTE = 'data-form-father-added-describedby-id';
|
|
517
|
-
const ERROR_SUMMARY_ATTRIBUTE = 'data-form-father-summary';
|
|
518
|
-
/** Изначальная дефолтная схема. */
|
|
519
|
-
const INITIAL_DEFAULT_SCHEMA = {
|
|
520
|
-
tel: {
|
|
521
|
-
selector: 'input[type="tel"]',
|
|
522
|
-
rules: ['tel'],
|
|
523
|
-
},
|
|
524
|
-
email: {
|
|
525
|
-
selector: 'input[type="email"]',
|
|
526
|
-
rules: ['email'],
|
|
527
|
-
},
|
|
528
|
-
required: {
|
|
529
|
-
selector: '[required]',
|
|
530
|
-
rules: ['required'],
|
|
531
|
-
},
|
|
532
|
-
url: {
|
|
533
|
-
selector: 'input[type="url"]',
|
|
534
|
-
rules: ['url'],
|
|
535
|
-
},
|
|
536
|
-
'not-numbers': {
|
|
537
|
-
selector: 'input[data-validate="not-numbers"]',
|
|
538
|
-
rules: ['not-numbers'],
|
|
539
|
-
},
|
|
540
|
-
};
|
|
541
|
-
/**
|
|
542
|
-
* Реализует форму отправки данных.
|
|
543
|
-
*
|
|
544
|
-
* @class
|
|
545
|
-
*/
|
|
546
|
-
class Form {
|
|
547
|
-
static get defaultParams() {
|
|
548
|
-
return __shared.defaultParams;
|
|
549
|
-
}
|
|
550
|
-
static set defaultParams(v) {
|
|
551
|
-
__shared.defaultParams = v;
|
|
552
|
-
}
|
|
553
|
-
static get defaultValidationSchema() {
|
|
554
|
-
var _a;
|
|
555
|
-
return ((_a = __shared.defaultValidationSchema) !== null && _a !== void 0 ? _a : (__shared.defaultValidationSchema = INITIAL_DEFAULT_SCHEMA));
|
|
556
|
-
}
|
|
557
|
-
static set defaultValidationSchema(v) {
|
|
558
|
-
__shared.defaultValidationSchema = v;
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* Создать форму.
|
|
562
|
-
*
|
|
563
|
-
* @param {HTMLElement} $el - Элемент формы (тег form!).
|
|
564
|
-
* @param {Object} options - Параметры формы.
|
|
565
|
-
*/
|
|
566
|
-
constructor($el, options = {}) {
|
|
567
|
-
this.$submits = [];
|
|
568
|
-
this.waitResponse = false;
|
|
569
|
-
this.inputs = null;
|
|
570
|
-
this.fieldEventHandlers = [];
|
|
571
|
-
this.liveValidationTimers = new WeakMap();
|
|
572
|
-
this.fieldValidationTokens = new WeakMap();
|
|
573
|
-
this.fieldValidationTokenCounter = 0;
|
|
574
|
-
this.errors = [];
|
|
575
|
-
/** Общие настройки анимаций (можно подправить под вкус) */
|
|
576
|
-
this._anim = {
|
|
577
|
-
duration: 220,
|
|
578
|
-
easing: 'ease',
|
|
579
|
-
paddingTopOpened: 16,
|
|
580
|
-
iconDuration: 700,
|
|
581
|
-
};
|
|
582
|
-
this.$el = $el;
|
|
583
|
-
this.options = options;
|
|
584
|
-
const defaultConfig = {
|
|
585
|
-
onSubmit: () => { },
|
|
586
|
-
onResponse: () => { },
|
|
587
|
-
onResponseSuccess: () => { },
|
|
588
|
-
onResponseUnsuccess: () => { },
|
|
589
|
-
customTypeError: null,
|
|
590
|
-
loaderColor: '#fff',
|
|
591
|
-
showLoaderButton: true,
|
|
592
|
-
scrollToFirstErroredInput: true,
|
|
593
|
-
focusFirstErroredInput: false,
|
|
594
|
-
logging: false,
|
|
595
|
-
inputSelector: '.input',
|
|
596
|
-
inputWrapperSelector: '.input-primary',
|
|
597
|
-
validateOn: 'submit',
|
|
598
|
-
revalidateOn: ['input', 'change'],
|
|
599
|
-
validationDebounce: 0,
|
|
600
|
-
errorContainerAttribute: 'data-error-container',
|
|
601
|
-
validationStateAttribute: 'data-form-father-state',
|
|
602
|
-
ariaDescribeErrors: true,
|
|
603
|
-
errorIdPrefix: 'form-father-error',
|
|
604
|
-
errorSummary: false,
|
|
605
|
-
observeMutations: false,
|
|
606
|
-
};
|
|
607
|
-
/* Слияние параметров: глобальные параметры → пользовательские параметры */
|
|
608
|
-
this.config = Object.assign({}, defaultConfig, Form.defaultParams, options);
|
|
609
|
-
if (this.$el) {
|
|
610
|
-
this.$submits = this.findSubmitElements();
|
|
611
|
-
if (this.isCorrectArguments()) {
|
|
612
|
-
this.bindSubmitHandlers();
|
|
613
|
-
this.initialization();
|
|
614
|
-
this.bindFieldValidationEvents();
|
|
615
|
-
this.setupMutationObserver();
|
|
616
|
-
FORM_INSTANCES.set(this.$el, this);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
console.warn('Empty $el');
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
getOptions() {
|
|
624
|
-
return this.options;
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Инициализирует сразу несколько форм по CSS-селектору. Если форма уже инициализирована, её настройки обновятся.
|
|
628
|
-
*
|
|
629
|
-
* @param selector - CSS-селектор форм. По умолчанию `form[data-form-father]`.
|
|
630
|
-
* @param options - Общие параметры для найденных форм.
|
|
631
|
-
*/
|
|
632
|
-
static initAll(selector = 'form[data-form-father]', options = {}) {
|
|
633
|
-
/* istanbul ignore next -- SSR guard: jsdom does not allow document to be unset cleanly. */
|
|
634
|
-
if (typeof document === 'undefined')
|
|
635
|
-
return [];
|
|
636
|
-
return Array.from(document.querySelectorAll(selector))
|
|
637
|
-
.filter($form => $form.tagName === 'FORM')
|
|
638
|
-
.map($form => {
|
|
639
|
-
const existing = FORM_INSTANCES.get($form);
|
|
640
|
-
if (existing) {
|
|
641
|
-
existing.updateOptions(options);
|
|
642
|
-
return existing;
|
|
643
|
-
}
|
|
644
|
-
return new Form($form, options);
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
/** Обновляет настройки конкретного инстанса формы без пересоздания обработчиков вручную. */
|
|
648
|
-
updateOptions(options) {
|
|
649
|
-
this.options = Object.assign(Object.assign({}, this.options), options);
|
|
650
|
-
this.config = Object.assign(Object.assign({}, this.config), options);
|
|
651
|
-
this.refreshControls();
|
|
652
|
-
this.bindSubmitHandlers();
|
|
653
|
-
this.bindFieldValidationEvents();
|
|
654
|
-
this.setupMutationObserver();
|
|
655
|
-
return this;
|
|
656
|
-
}
|
|
657
|
-
/** Возвращает текущий список ошибок формы. */
|
|
658
|
-
getErrors() {
|
|
659
|
-
return this.errors.map(error => (Object.assign({}, error)));
|
|
660
|
-
}
|
|
661
|
-
/** Возвращает значения формы обычным объектом. */
|
|
662
|
-
getValues() {
|
|
663
|
-
return serializeFormToJSON(this.$el);
|
|
664
|
-
}
|
|
665
|
-
normalizeValueList(value) {
|
|
666
|
-
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
667
|
-
}
|
|
668
|
-
/** Проставляет значения полей по `name` и уведомляет UI через `input`/`change` события. */
|
|
669
|
-
setValues(values) {
|
|
670
|
-
this.getAllFields().forEach($field => {
|
|
671
|
-
if (!$field.name || !Object.prototype.hasOwnProperty.call(values, $field.name))
|
|
672
|
-
return;
|
|
673
|
-
const value = values[$field.name];
|
|
674
|
-
if ($field instanceof HTMLInputElement && $field.type === 'file')
|
|
675
|
-
return;
|
|
676
|
-
if ($field instanceof HTMLInputElement && $field.type === 'checkbox') {
|
|
677
|
-
if (typeof value === 'boolean') {
|
|
678
|
-
$field.checked = value;
|
|
679
|
-
}
|
|
680
|
-
else {
|
|
681
|
-
$field.checked = this.normalizeValueList(value).includes($field.value);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
else if ($field instanceof HTMLInputElement && $field.type === 'radio') {
|
|
685
|
-
$field.checked = String(value) === $field.value;
|
|
686
|
-
}
|
|
687
|
-
else if ($field instanceof HTMLSelectElement && $field.multiple) {
|
|
688
|
-
const valuesList = this.normalizeValueList(value);
|
|
689
|
-
Array.from($field.options).forEach(option => {
|
|
690
|
-
option.selected = valuesList.includes(option.value);
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
$field.value = value == null ? '' : String(value);
|
|
695
|
-
}
|
|
696
|
-
$field.dispatchEvent(new Event('input', { bubbles: true }));
|
|
697
|
-
$field.dispatchEvent(new Event('change', { bubbles: true }));
|
|
698
|
-
});
|
|
699
|
-
return this;
|
|
700
|
-
}
|
|
701
|
-
/** Снимает все клиентские и серверные ошибки с формы, не меняя значения полей. */
|
|
702
|
-
clearErrors() {
|
|
703
|
-
this.errors = [];
|
|
704
|
-
this.getAllFields().forEach($field => {
|
|
705
|
-
this.hideError($field);
|
|
706
|
-
this.clearFieldValidationState($field);
|
|
707
|
-
});
|
|
708
|
-
this.hideErrorForm();
|
|
709
|
-
this.hideErrorSummary();
|
|
710
|
-
return this;
|
|
711
|
-
}
|
|
712
|
-
isErrorResponse(issue) {
|
|
713
|
-
return (!!issue &&
|
|
714
|
-
typeof issue === 'object' &&
|
|
715
|
-
typeof issue.name === 'string' &&
|
|
716
|
-
typeof issue['error-msg'] === 'string');
|
|
717
|
-
}
|
|
718
|
-
normalizeValidationIssues(issues, source) {
|
|
719
|
-
if (issues == null || issues === true)
|
|
720
|
-
return [];
|
|
721
|
-
if (issues === false)
|
|
722
|
-
return [{ message: 'Некорректные данные формы', source }];
|
|
723
|
-
if (typeof issues === 'string')
|
|
724
|
-
return [{ message: issues, source }];
|
|
725
|
-
if (Array.isArray(issues)) {
|
|
726
|
-
return issues.flatMap(issue => this.normalizeValidationIssues(issue, source));
|
|
727
|
-
}
|
|
728
|
-
if (this.isErrorResponse(issues)) {
|
|
729
|
-
return [
|
|
730
|
-
{
|
|
731
|
-
field: issues.name,
|
|
732
|
-
rule: source,
|
|
733
|
-
message: issues['error-msg'],
|
|
734
|
-
source,
|
|
735
|
-
},
|
|
736
|
-
];
|
|
737
|
-
}
|
|
738
|
-
if (typeof issues === 'object' && 'message' in issues && typeof issues.message === 'string') {
|
|
739
|
-
return [Object.assign(Object.assign({}, issues), { source: issues.source || source })];
|
|
740
|
-
}
|
|
741
|
-
if (typeof issues === 'object') {
|
|
742
|
-
return Object.entries(issues).map(([field, message]) => ({
|
|
743
|
-
field,
|
|
744
|
-
rule: source,
|
|
745
|
-
message,
|
|
746
|
-
source,
|
|
747
|
-
}));
|
|
748
|
-
}
|
|
749
|
-
return [];
|
|
750
|
-
}
|
|
751
|
-
applyValidationIssue(issue, source) {
|
|
752
|
-
const issueSource = issue.source || source;
|
|
753
|
-
const rule = issue.rule || issueSource;
|
|
754
|
-
const field = issue.field || FORM_ERROR_FIELD;
|
|
755
|
-
if (field === FORM_ERROR_FIELD) {
|
|
756
|
-
this.setErrorRecord(FORM_ERROR_FIELD, rule, issue.message, issueSource);
|
|
757
|
-
this.showErrorForm(issue.message);
|
|
758
|
-
return null;
|
|
759
|
-
}
|
|
760
|
-
const $field = this.findFieldByName(field);
|
|
761
|
-
if (!$field) {
|
|
762
|
-
this.setErrorRecord(field, rule, issue.message, issueSource);
|
|
763
|
-
console.warn(`Не найдено поле с именем: ${field}, для вывода ошибки: ${issue.message}`);
|
|
764
|
-
return null;
|
|
765
|
-
}
|
|
766
|
-
this.showFieldError($field, issue.message, issueSource, rule);
|
|
767
|
-
return $field;
|
|
768
|
-
}
|
|
769
|
-
/** Показывает несколько ошибок формы или полей: из backend map/array, `ErrorResponse[]` или form validators. */
|
|
770
|
-
setErrors(issues, source = 'server') {
|
|
771
|
-
this.normalizeValidationIssues(issues, source).forEach(issue => {
|
|
772
|
-
this.applyValidationIssue(issue, source);
|
|
773
|
-
});
|
|
774
|
-
this.syncErrorSummary();
|
|
775
|
-
return this;
|
|
776
|
-
}
|
|
777
|
-
validateFormValidators() {
|
|
778
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
779
|
-
const validators = this.config.formValidators
|
|
780
|
-
? Array.isArray(this.config.formValidators)
|
|
781
|
-
? this.config.formValidators
|
|
782
|
-
: [this.config.formValidators]
|
|
783
|
-
: [];
|
|
784
|
-
const erroredInputs = [];
|
|
785
|
-
let ok = true;
|
|
786
|
-
for (const validator of validators) {
|
|
787
|
-
const result = yield validator({
|
|
788
|
-
form: this,
|
|
789
|
-
formElement: this.$el,
|
|
790
|
-
values: this.getValues(),
|
|
791
|
-
errors: this.getErrors(),
|
|
792
|
-
});
|
|
793
|
-
const issues = this.normalizeValidationIssues(result, 'client');
|
|
794
|
-
if (issues.length === 0)
|
|
795
|
-
continue;
|
|
796
|
-
ok = false;
|
|
797
|
-
issues.forEach(issue => {
|
|
798
|
-
const $field = this.applyValidationIssue(issue, 'client');
|
|
799
|
-
if ($field)
|
|
800
|
-
erroredInputs.push($field);
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
return { ok, erroredInputs };
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
/** Сбрасывает форму через native `reset()` и опционально очищает ошибки. */
|
|
807
|
-
reset(options = {}) {
|
|
808
|
-
this.$el.reset();
|
|
809
|
-
this.refreshControls();
|
|
810
|
-
if (options.clearErrors !== false) {
|
|
811
|
-
this.clearErrors();
|
|
812
|
-
}
|
|
813
|
-
return this;
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* Обновляет параметры по умолчанию для настроек формы. Метод объединяет переданные параметры с уже существующими
|
|
817
|
-
* параметрами по умолчанию.
|
|
818
|
-
*
|
|
819
|
-
* @param {Partial<FormOptions>} params - Объект, содержащий новые значения параметров. Можно передать только те
|
|
820
|
-
* свойства, которые необходимо обновить; остальные сохранятся без изменений.
|
|
821
|
-
*/
|
|
822
|
-
static setDefaultParams(params) {
|
|
823
|
-
this.defaultParams = Object.assign(Object.assign({}, this.defaultParams), params);
|
|
824
|
-
}
|
|
825
|
-
refreshControls() {
|
|
826
|
-
this.inputs = this.$el.querySelectorAll(this.config.inputSelector);
|
|
827
|
-
}
|
|
828
|
-
findSubmitElements() {
|
|
829
|
-
return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'));
|
|
830
|
-
}
|
|
831
|
-
bindSubmitHandlers() {
|
|
832
|
-
if (!this._onSubmitClickHandler) {
|
|
833
|
-
this._onSubmitClickHandler = (e) => __awaiter(this, void 0, void 0, function* () {
|
|
834
|
-
yield this.handleSubmitEvent(e);
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
this.$submits.forEach($submit => {
|
|
838
|
-
$submit.removeEventListener('click', this._onSubmitClickHandler);
|
|
839
|
-
});
|
|
840
|
-
this.$submits = this.findSubmitElements();
|
|
841
|
-
this.$submits.forEach($submit => {
|
|
842
|
-
$submit.addEventListener('click', this._onSubmitClickHandler);
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
initialization() {
|
|
846
|
-
this.refreshControls();
|
|
847
|
-
this._onSubmitHandler = (e) => __awaiter(this, void 0, void 0, function* () {
|
|
848
|
-
yield this.handleSubmitEvent(e);
|
|
849
|
-
});
|
|
850
|
-
this.$el.addEventListener('submit', this._onSubmitHandler);
|
|
851
|
-
}
|
|
852
|
-
handleSubmitEvent(e) {
|
|
853
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
854
|
-
var _a, _b, _c, _d, _e, _f;
|
|
855
|
-
e.preventDefault();
|
|
856
|
-
(_b = (_a = this.config).onBeforeValidate) === null || _b === void 0 ? void 0 : _b.call(_a, this);
|
|
857
|
-
const isValid = yield this.validate();
|
|
858
|
-
(_d = (_c = this.config).onAfterValidate) === null || _d === void 0 ? void 0 : _d.call(_c, isValid, this);
|
|
859
|
-
if (!isValid) {
|
|
860
|
-
(_f = (_e = this.config).onValidationError) === null || _f === void 0 ? void 0 : _f.call(_e, this.getErrors(), this);
|
|
861
|
-
return {
|
|
862
|
-
success: false,
|
|
863
|
-
error: this.getErrors(),
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
return this.submit();
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
normalizeTriggers(value, fallback) {
|
|
870
|
-
const triggers = value == null ? fallback : Array.isArray(value) ? value : [value];
|
|
871
|
-
return Array.from(new Set(triggers));
|
|
872
|
-
}
|
|
873
|
-
clearFieldValidationEvents() {
|
|
874
|
-
this.fieldEventHandlers.forEach(({ $input, event, handler }) => {
|
|
875
|
-
$input.removeEventListener(event, handler);
|
|
876
|
-
});
|
|
877
|
-
this.fieldEventHandlers = [];
|
|
878
|
-
}
|
|
879
|
-
bindFieldValidationEvents() {
|
|
880
|
-
this.clearFieldValidationEvents();
|
|
881
|
-
const validateOn = this.normalizeTriggers(this.config.validateOn, ['submit']).filter((event) => event !== 'submit');
|
|
882
|
-
const revalidateOn = this.normalizeTriggers(this.config.revalidateOn, ['input', 'change']).filter((event) => event !== 'submit');
|
|
883
|
-
const events = Array.from(new Set([...validateOn, ...revalidateOn]));
|
|
884
|
-
if (events.length === 0)
|
|
885
|
-
return;
|
|
886
|
-
const fields = this.getAllFields();
|
|
887
|
-
fields.forEach($input => {
|
|
888
|
-
events.forEach(event => {
|
|
889
|
-
const handler = () => {
|
|
890
|
-
const shouldValidate = validateOn.includes(event) || this.hasFieldError($input);
|
|
891
|
-
if (shouldValidate)
|
|
892
|
-
this.scheduleFieldValidation($input);
|
|
893
|
-
};
|
|
894
|
-
$input.addEventListener(event, handler);
|
|
895
|
-
this.fieldEventHandlers.push({ $input, event, handler });
|
|
896
|
-
});
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
scheduleFieldValidation($input) {
|
|
900
|
-
const currentTimer = this.liveValidationTimers.get($input);
|
|
901
|
-
if (currentTimer)
|
|
902
|
-
clearTimeout(currentTimer);
|
|
903
|
-
const delay = Math.max(0, Number(this.config.validationDebounce) || 0);
|
|
904
|
-
const run = () => {
|
|
905
|
-
void this.validateField($input);
|
|
906
|
-
};
|
|
907
|
-
if (delay === 0) {
|
|
908
|
-
run();
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const timer = setTimeout(run, delay);
|
|
912
|
-
this.liveValidationTimers.set($input, timer);
|
|
913
|
-
}
|
|
914
|
-
setupMutationObserver() {
|
|
915
|
-
var _a;
|
|
916
|
-
(_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
917
|
-
this.mutationObserver = undefined;
|
|
918
|
-
if (!this.config.observeMutations || typeof MutationObserver === 'undefined')
|
|
919
|
-
return;
|
|
920
|
-
this.mutationObserver = new MutationObserver(() => {
|
|
921
|
-
this.refreshControls();
|
|
922
|
-
this.bindSubmitHandlers();
|
|
923
|
-
this.bindFieldValidationEvents();
|
|
924
|
-
});
|
|
925
|
-
this.mutationObserver.observe(this.$el, {
|
|
926
|
-
childList: true,
|
|
927
|
-
subtree: true,
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
getErrorContainer($input) {
|
|
931
|
-
const attrName = this.config.errorContainerAttribute || 'data-error-container';
|
|
932
|
-
const selector = $input.getAttribute(attrName);
|
|
933
|
-
if (!selector)
|
|
934
|
-
return null;
|
|
935
|
-
try {
|
|
936
|
-
return document.querySelector(selector);
|
|
937
|
-
}
|
|
938
|
-
catch (_a) {
|
|
939
|
-
console.warn(`[FormFather] Invalid error container selector "${selector}"`);
|
|
940
|
-
return null;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
toSafeIdPart(value) {
|
|
944
|
-
return (value
|
|
945
|
-
.trim()
|
|
946
|
-
.toLowerCase()
|
|
947
|
-
.replace(/[^a-z0-9_-]+/gi, '-')
|
|
948
|
-
.replace(/^-+|-+$/g, '') || 'field');
|
|
949
|
-
}
|
|
950
|
-
createErrorId($input) {
|
|
951
|
-
const prefix = this.config.errorIdPrefix || 'form-father-error';
|
|
952
|
-
return `${this.toSafeIdPart(prefix)}-${this.toSafeIdPart(this.getFieldKey($input))}-${++errorIdCounter}`;
|
|
953
|
-
}
|
|
954
|
-
ensureElementId($input, $error) {
|
|
955
|
-
if (!$error.id) {
|
|
956
|
-
$error.id = $input.getAttribute(ERROR_DESCRIBEDBY_ID_ATTRIBUTE) || this.createErrorId($input);
|
|
957
|
-
}
|
|
958
|
-
return $error.id;
|
|
959
|
-
}
|
|
960
|
-
addErrorDescription($input, $error) {
|
|
961
|
-
if (!$error.hasAttribute('role'))
|
|
962
|
-
$error.setAttribute('role', 'alert');
|
|
963
|
-
const errorId = this.ensureElementId($input, $error);
|
|
964
|
-
$input.setAttribute(ERROR_DESCRIBEDBY_ID_ATTRIBUTE, errorId);
|
|
965
|
-
if (this.config.ariaDescribeErrors === false)
|
|
966
|
-
return;
|
|
967
|
-
const currentIds = ($input.getAttribute('aria-describedby') || '').split(/\s+/).filter(Boolean);
|
|
968
|
-
if (!currentIds.includes(errorId)) {
|
|
969
|
-
$input.setAttribute('aria-describedby', [...currentIds, errorId].join(' '));
|
|
970
|
-
$input.setAttribute(ADDED_DESCRIBEDBY_ID_ATTRIBUTE, errorId);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
removeErrorDescription($input) {
|
|
974
|
-
const addedId = $input.getAttribute(ADDED_DESCRIBEDBY_ID_ATTRIBUTE);
|
|
975
|
-
if (addedId) {
|
|
976
|
-
const nextIds = ($input.getAttribute('aria-describedby') || '')
|
|
977
|
-
.split(/\s+/)
|
|
978
|
-
.filter(id => id && id !== addedId);
|
|
979
|
-
if (nextIds.length > 0) {
|
|
980
|
-
$input.setAttribute('aria-describedby', nextIds.join(' '));
|
|
981
|
-
}
|
|
982
|
-
else {
|
|
983
|
-
$input.removeAttribute('aria-describedby');
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
$input.removeAttribute(ERROR_DESCRIBEDBY_ID_ATTRIBUTE);
|
|
987
|
-
$input.removeAttribute(ADDED_DESCRIBEDBY_ID_ATTRIBUTE);
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Показывает ошибку для поля ввода.
|
|
991
|
-
*
|
|
992
|
-
* @param {HTMLInputElement} $input - Поле ввода.
|
|
993
|
-
* @param {String} text - Текст ошибки.
|
|
994
|
-
*/
|
|
995
|
-
showError($input, text) {
|
|
996
|
-
$input.setAttribute('aria-invalid', 'true');
|
|
997
|
-
const $errorContainer = this.getErrorContainer($input);
|
|
998
|
-
if ($errorContainer) {
|
|
999
|
-
$errorContainer.textContent = text;
|
|
1000
|
-
this.addErrorDescription($input, $errorContainer);
|
|
1001
|
-
$errorContainer.removeAttribute('hidden');
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
const $inputWrapper = closest($input, this.config.inputWrapperSelector);
|
|
1005
|
-
if (!$inputWrapper) {
|
|
1006
|
-
console.warn(`Не найдена обёртка для поля с именем: ${$input.name || $input.type}`);
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
const customShowError = $inputWrapper.showError;
|
|
1010
|
-
if (typeof customShowError === 'function') {
|
|
1011
|
-
customShowError.call($inputWrapper, text);
|
|
1012
|
-
const $customError = $inputWrapper.querySelector('[data-form-father-error]');
|
|
1013
|
-
if ($customError)
|
|
1014
|
-
this.addErrorDescription($input, $customError);
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
$inputWrapper.classList.add('input__wrapper--error');
|
|
1018
|
-
let $error = $inputWrapper.querySelector('[data-form-father-error]');
|
|
1019
|
-
if (!$error) {
|
|
1020
|
-
$error = document.createElement('div');
|
|
1021
|
-
$error.setAttribute('data-form-father-error', '');
|
|
1022
|
-
$error.setAttribute('role', 'alert');
|
|
1023
|
-
$input.insertAdjacentElement('afterend', $error);
|
|
1024
|
-
}
|
|
1025
|
-
$error.textContent = text;
|
|
1026
|
-
this.addErrorDescription($input, $error);
|
|
1027
|
-
}
|
|
1028
|
-
/** Показывает лоадер */
|
|
1029
|
-
showLoader() {
|
|
1030
|
-
this.$submits.forEach($submit => {
|
|
1031
|
-
$submit.classList.add('button--loading');
|
|
1032
|
-
if ($submit.querySelector('.button__loader'))
|
|
1033
|
-
return;
|
|
1034
|
-
const gradientId = `form-father-loader-${loaderIdCounter++}`;
|
|
1035
|
-
const loaderSvg = `
|
|
1036
|
-
<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">
|
|
1037
|
-
<defs>
|
|
1038
|
-
<linearGradient id="${gradientId}" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">
|
|
1039
|
-
<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>
|
|
1040
|
-
<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>
|
|
1041
|
-
<stop offset="100%" stop-color="${this.config.loaderColor}"/>
|
|
1042
|
-
</linearGradient>
|
|
1043
|
-
</defs>
|
|
1044
|
-
<g fill-rule="evenodd" fill="none">
|
|
1045
|
-
<g transform="translate(1 1)">
|
|
1046
|
-
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#${gradientId})"></path>
|
|
1047
|
-
<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>
|
|
1048
|
-
</g>
|
|
1049
|
-
</g>
|
|
1050
|
-
</svg>
|
|
1051
|
-
`;
|
|
1052
|
-
const $loaderEl = document.createElement('span');
|
|
1053
|
-
$loaderEl.innerHTML = loaderSvg;
|
|
1054
|
-
$loaderEl.className = 'button__loader';
|
|
1055
|
-
$submit.insertAdjacentElement('afterbegin', $loaderEl);
|
|
1056
|
-
requestAnimationFrame(() => {
|
|
1057
|
-
$loaderEl.setAttribute('data-showed', '');
|
|
1058
|
-
});
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
1061
|
-
/** Скрывает лоадер */
|
|
1062
|
-
hideLoader() {
|
|
1063
|
-
this.$submits.forEach($submit => {
|
|
1064
|
-
$submit.classList.remove('button--loading');
|
|
1065
|
-
const $loader = $submit.querySelector('.button__loader');
|
|
1066
|
-
if ($loader) {
|
|
1067
|
-
$loader.classList.remove('active');
|
|
1068
|
-
$loader.removeAttribute('data-showed');
|
|
1069
|
-
setTimeout(() => {
|
|
1070
|
-
$loader.remove();
|
|
1071
|
-
}, 250);
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
/**
|
|
1076
|
-
* Скрывает ошибку.
|
|
1077
|
-
*
|
|
1078
|
-
* @param {HTMLInputElement} $input - Поле ввода.
|
|
1079
|
-
*/
|
|
1080
|
-
hideError($input) {
|
|
1081
|
-
var _a;
|
|
1082
|
-
const $inputWrapper = closest($input, this.config.inputWrapperSelector) || $input.parentElement;
|
|
1083
|
-
const $errorContainer = this.getErrorContainer($input);
|
|
1084
|
-
$input.removeAttribute('aria-invalid');
|
|
1085
|
-
this.removeErrorDescription($input);
|
|
1086
|
-
if ($errorContainer) {
|
|
1087
|
-
$errorContainer.textContent = '';
|
|
1088
|
-
$errorContainer.setAttribute('hidden', '');
|
|
1089
|
-
}
|
|
1090
|
-
(_a = $inputWrapper === null || $inputWrapper === void 0 ? void 0 : $inputWrapper.querySelector('[data-form-father-error]')) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1091
|
-
$inputWrapper === null || $inputWrapper === void 0 ? void 0 : $inputWrapper.classList.remove('input__wrapper--error');
|
|
1092
|
-
if ($inputWrapper && $inputWrapper.classList.contains('input__wrapper')) {
|
|
1093
|
-
$inputWrapper.getAttribute('data-type-error') || 'default';
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
getErrorSummaryOptions() {
|
|
1097
|
-
if (!this.config.errorSummary)
|
|
1098
|
-
return null;
|
|
1099
|
-
return this.config.errorSummary === true ? {} : this.config.errorSummary;
|
|
1100
|
-
}
|
|
1101
|
-
getErrorSummaryContainer(options, create) {
|
|
1102
|
-
if (options.selector) {
|
|
1103
|
-
try {
|
|
1104
|
-
return (this.$el.querySelector(options.selector) ||
|
|
1105
|
-
document.querySelector(options.selector));
|
|
1106
|
-
}
|
|
1107
|
-
catch (_a) {
|
|
1108
|
-
console.warn(`[FormFather] Invalid error summary selector "${options.selector}"`);
|
|
1109
|
-
return null;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
const existing = this.$el.querySelector(`[${ERROR_SUMMARY_ATTRIBUTE}]`);
|
|
1113
|
-
if (existing || !create)
|
|
1114
|
-
return existing;
|
|
1115
|
-
const $summary = document.createElement('div');
|
|
1116
|
-
$summary.setAttribute(ERROR_SUMMARY_ATTRIBUTE, '');
|
|
1117
|
-
this.$el.insertAdjacentElement('afterbegin', $summary);
|
|
1118
|
-
return $summary;
|
|
1119
|
-
}
|
|
1120
|
-
prepareErrorSummaryContainer($summary) {
|
|
1121
|
-
$summary.setAttribute(ERROR_SUMMARY_ATTRIBUTE, '');
|
|
1122
|
-
if (!$summary.hasAttribute('role'))
|
|
1123
|
-
$summary.setAttribute('role', 'alert');
|
|
1124
|
-
if (!$summary.hasAttribute('tabindex'))
|
|
1125
|
-
$summary.setAttribute('tabindex', '-1');
|
|
1126
|
-
$summary.removeAttribute('hidden');
|
|
1127
|
-
}
|
|
1128
|
-
focusErrorSummary($summary) {
|
|
1129
|
-
try {
|
|
1130
|
-
$summary.focus({ preventScroll: true });
|
|
1131
|
-
}
|
|
1132
|
-
catch (_a) {
|
|
1133
|
-
$summary.focus();
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
focusFieldFromSummary($field) {
|
|
1137
|
-
if (typeof $field.scrollIntoView === 'function') {
|
|
1138
|
-
$field.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1139
|
-
}
|
|
1140
|
-
try {
|
|
1141
|
-
$field.focus({ preventScroll: true });
|
|
1142
|
-
}
|
|
1143
|
-
catch (_a) {
|
|
1144
|
-
$field.focus();
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
renderDefaultErrorSummary($summary, errors, options) {
|
|
1148
|
-
$summary.textContent = '';
|
|
1149
|
-
const $title = document.createElement('p');
|
|
1150
|
-
$title.setAttribute('data-form-father-summary-title', '');
|
|
1151
|
-
$title.textContent = options.title || 'Проверьте поля формы';
|
|
1152
|
-
const $list = document.createElement('ul');
|
|
1153
|
-
$list.setAttribute('data-form-father-summary-list', '');
|
|
1154
|
-
errors.forEach(error => {
|
|
1155
|
-
const $item = document.createElement('li');
|
|
1156
|
-
const $field = error.field === FORM_ERROR_FIELD ? null : this.findFieldByName(error.field);
|
|
1157
|
-
if ($field) {
|
|
1158
|
-
const $button = document.createElement('button');
|
|
1159
|
-
$button.type = 'button';
|
|
1160
|
-
$button.setAttribute('data-form-father-summary-field', error.field);
|
|
1161
|
-
$button.textContent = error.message;
|
|
1162
|
-
$button.addEventListener('click', () => this.focusFieldFromSummary($field));
|
|
1163
|
-
$item.appendChild($button);
|
|
1164
|
-
}
|
|
1165
|
-
else {
|
|
1166
|
-
const $message = document.createElement('span');
|
|
1167
|
-
$message.textContent = error.message;
|
|
1168
|
-
$item.appendChild($message);
|
|
1169
|
-
}
|
|
1170
|
-
$list.appendChild($item);
|
|
1171
|
-
});
|
|
1172
|
-
$summary.append($title, $list);
|
|
1173
|
-
}
|
|
1174
|
-
renderErrorSummary(errors = this.getErrors()) {
|
|
1175
|
-
var _a;
|
|
1176
|
-
const options = this.getErrorSummaryOptions();
|
|
1177
|
-
if (!options)
|
|
1178
|
-
return;
|
|
1179
|
-
if (errors.length === 0) {
|
|
1180
|
-
this.hideErrorSummary();
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
const $summary = this.getErrorSummaryContainer(options, true);
|
|
1184
|
-
if (!$summary)
|
|
1185
|
-
return;
|
|
1186
|
-
this.prepareErrorSummaryContainer($summary);
|
|
1187
|
-
const rendered = (_a = options.render) === null || _a === void 0 ? void 0 : _a.call(options, errors.map(error => (Object.assign({}, error))), this);
|
|
1188
|
-
if (rendered instanceof HTMLElement) {
|
|
1189
|
-
$summary.replaceChildren(rendered);
|
|
1190
|
-
}
|
|
1191
|
-
else if (typeof rendered === 'string') {
|
|
1192
|
-
$summary.innerHTML = rendered;
|
|
1193
|
-
}
|
|
1194
|
-
else if (!options.render) {
|
|
1195
|
-
this.renderDefaultErrorSummary($summary, errors, options);
|
|
1196
|
-
}
|
|
1197
|
-
if (options.focus)
|
|
1198
|
-
this.focusErrorSummary($summary);
|
|
1199
|
-
}
|
|
1200
|
-
hideErrorSummary() {
|
|
1201
|
-
const options = this.getErrorSummaryOptions() || {};
|
|
1202
|
-
const $summary = this.getErrorSummaryContainer(options, false);
|
|
1203
|
-
if (!$summary)
|
|
1204
|
-
return;
|
|
1205
|
-
$summary.replaceChildren();
|
|
1206
|
-
$summary.setAttribute('hidden', '');
|
|
1207
|
-
}
|
|
1208
|
-
syncErrorSummary() {
|
|
1209
|
-
if (this.errors.length === 0) {
|
|
1210
|
-
this.hideErrorSummary();
|
|
1211
|
-
return;
|
|
1212
|
-
}
|
|
1213
|
-
this.renderErrorSummary();
|
|
1214
|
-
}
|
|
1215
|
-
/**
|
|
1216
|
-
* Проскроливает до первого ошибочного поля.
|
|
1217
|
-
*
|
|
1218
|
-
* @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
|
|
1219
|
-
*/
|
|
1220
|
-
scrollToFirstErroredInput(inputsList) {
|
|
1221
|
-
if (inputsList.length > 0) {
|
|
1222
|
-
const allInputInFormList = Array.from(this.$el.querySelectorAll('input, textarea, select'));
|
|
1223
|
-
let $firstInputOnForm = null;
|
|
1224
|
-
let minIndex = Infinity;
|
|
1225
|
-
inputsList.forEach($input => {
|
|
1226
|
-
const findIndex = allInputInFormList.findIndex($item => $item === $input);
|
|
1227
|
-
if (findIndex < minIndex) {
|
|
1228
|
-
minIndex = findIndex;
|
|
1229
|
-
$firstInputOnForm = $input;
|
|
1230
|
-
}
|
|
1231
|
-
});
|
|
1232
|
-
if ($firstInputOnForm) {
|
|
1233
|
-
const rect = $firstInputOnForm.getBoundingClientRect();
|
|
1234
|
-
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
1235
|
-
const scrollTargetY = rect.top + scrollTop - window.innerHeight / 2;
|
|
1236
|
-
document.documentElement.setAttribute('data-now-scrolling', '');
|
|
1237
|
-
window.scrollTo({
|
|
1238
|
-
top: scrollTargetY,
|
|
1239
|
-
behavior: 'smooth',
|
|
1240
|
-
});
|
|
1241
|
-
setTimeout(() => {
|
|
1242
|
-
document.documentElement.removeAttribute('data-now-scrolling');
|
|
1243
|
-
}, 800);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
focusFirstErroredInput(inputsList) {
|
|
1248
|
-
var _a;
|
|
1249
|
-
if (inputsList.length === 0)
|
|
1250
|
-
return;
|
|
1251
|
-
const allInputInFormList = Array.from(this.$el.querySelectorAll('input, textarea, select'));
|
|
1252
|
-
const $firstInputOnForm = inputsList
|
|
1253
|
-
.slice()
|
|
1254
|
-
.sort((a, b) => allInputInFormList.indexOf(a) - allInputInFormList.indexOf(b))[0];
|
|
1255
|
-
(_a = $firstInputOnForm === null || $firstInputOnForm === void 0 ? void 0 : $firstInputOnForm.focus) === null || _a === void 0 ? void 0 : _a.call($firstInputOnForm, { preventScroll: true });
|
|
1256
|
-
}
|
|
1257
|
-
getValidationSchema() {
|
|
1258
|
-
return Object.assign(Object.assign({}, (Form.defaultValidationSchema || {})), (this.config.validationSchema || {}));
|
|
1259
|
-
}
|
|
1260
|
-
getAllFields($block = this.$el) {
|
|
1261
|
-
return Array.from($block.querySelectorAll('input, textarea, select'));
|
|
1262
|
-
}
|
|
1263
|
-
getFieldKey($input) {
|
|
1264
|
-
return $input.name || $input.id || $input.getAttribute('data-field') || $input.type || 'field';
|
|
1265
|
-
}
|
|
1266
|
-
hasFieldError($input) {
|
|
1267
|
-
const field = this.getFieldKey($input);
|
|
1268
|
-
return $input.getAttribute('aria-invalid') === 'true' || this.errors.some(error => error.field === field);
|
|
1269
|
-
}
|
|
1270
|
-
clearFieldErrorRecord($input) {
|
|
1271
|
-
const field = this.getFieldKey($input);
|
|
1272
|
-
this.errors = this.errors.filter(error => error.field !== field);
|
|
1273
|
-
}
|
|
1274
|
-
setErrorRecord(field, rule, message, source) {
|
|
1275
|
-
this.errors.push({
|
|
1276
|
-
field,
|
|
1277
|
-
rule,
|
|
1278
|
-
message,
|
|
1279
|
-
source,
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
getValidationStateAttribute() {
|
|
1283
|
-
return this.config.validationStateAttribute || 'data-form-father-state';
|
|
1284
|
-
}
|
|
1285
|
-
setFieldValidationState($input, state) {
|
|
1286
|
-
const attr = this.getValidationStateAttribute();
|
|
1287
|
-
if (state) {
|
|
1288
|
-
$input.setAttribute(attr, state);
|
|
1289
|
-
}
|
|
1290
|
-
else {
|
|
1291
|
-
$input.removeAttribute(attr);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
startFieldValidation($input) {
|
|
1295
|
-
const token = ++this.fieldValidationTokenCounter;
|
|
1296
|
-
this.fieldValidationTokens.set($input, token);
|
|
1297
|
-
$input.setAttribute('aria-busy', 'true');
|
|
1298
|
-
this.setFieldValidationState($input, 'validating');
|
|
1299
|
-
return token;
|
|
1300
|
-
}
|
|
1301
|
-
isLatestFieldValidation($input, token) {
|
|
1302
|
-
return token == null || this.fieldValidationTokens.get($input) === token;
|
|
1303
|
-
}
|
|
1304
|
-
finishFieldValidation($input, token, isValid) {
|
|
1305
|
-
if (!this.isLatestFieldValidation($input, token))
|
|
1306
|
-
return;
|
|
1307
|
-
$input.removeAttribute('aria-busy');
|
|
1308
|
-
this.setFieldValidationState($input, isValid ? 'valid' : 'invalid');
|
|
1309
|
-
}
|
|
1310
|
-
clearFieldValidationState($input) {
|
|
1311
|
-
this.fieldValidationTokens.delete($input);
|
|
1312
|
-
$input.removeAttribute('aria-busy');
|
|
1313
|
-
this.setFieldValidationState($input, null);
|
|
1314
|
-
}
|
|
1315
|
-
setValidationError($input, rule, message, source) {
|
|
1316
|
-
this.clearFieldErrorRecord($input);
|
|
1317
|
-
this.setErrorRecord(this.getFieldKey($input), rule, message, source);
|
|
1318
|
-
}
|
|
1319
|
-
resolveField(field) {
|
|
1320
|
-
if (typeof field !== 'string')
|
|
1321
|
-
return field;
|
|
1322
|
-
return this.findFieldByName(field);
|
|
1323
|
-
}
|
|
1324
|
-
parseRuleString(ruleString) {
|
|
1325
|
-
const [rule, ...params] = ruleString.split(':').map(s => s.trim());
|
|
1326
|
-
return params.length > 0 ? { rule, params: params.length === 1 ? params[0] : params } : rule;
|
|
1327
|
-
}
|
|
1328
|
-
parseValidationAttribute(value) {
|
|
1329
|
-
return (value !== null && value !== void 0 ? value : '')
|
|
1330
|
-
.split(/[,\s|]+/)
|
|
1331
|
-
.map(s => s.trim())
|
|
1332
|
-
.filter(Boolean)
|
|
1333
|
-
.map(ruleString => this.parseRuleString(ruleString));
|
|
1334
|
-
}
|
|
1335
|
-
getRuleName(rule) {
|
|
1336
|
-
return typeof rule === 'string' ? rule : rule.rule;
|
|
1337
|
-
}
|
|
1338
|
-
getRuleIdentity(rule) {
|
|
1339
|
-
return JSON.stringify(rule);
|
|
1340
|
-
}
|
|
1341
|
-
collectRules($input, fromSchema = [], schemaKeys = new Set()) {
|
|
1342
|
-
const list = [];
|
|
1343
|
-
if ($input.hasAttribute('required'))
|
|
1344
|
-
list.push('required');
|
|
1345
|
-
list.push(...fromSchema);
|
|
1346
|
-
const dataValidateRules = this.parseValidationAttribute($input.getAttribute('data-validate')).filter(rule => {
|
|
1347
|
-
const ruleName = this.getRuleName(rule);
|
|
1348
|
-
return getValidator(ruleName) || !schemaKeys.has(ruleName);
|
|
1349
|
-
});
|
|
1350
|
-
list.push(...dataValidateRules);
|
|
1351
|
-
list.push(...this.parseValidationAttribute($input.getAttribute('data-custom-validate')));
|
|
1352
|
-
const filtered = [];
|
|
1353
|
-
const seen = new Set();
|
|
1354
|
-
for (const rule of list) {
|
|
1355
|
-
const ruleName = this.getRuleName(rule);
|
|
1356
|
-
if (!getValidator(ruleName)) {
|
|
1357
|
-
console.warn(`[FormFather] Unknown validation rule "${ruleName}"`);
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
const identity = this.getRuleIdentity(rule);
|
|
1361
|
-
if (seen.has(identity))
|
|
1362
|
-
continue;
|
|
1363
|
-
seen.add(identity);
|
|
1364
|
-
filtered.push(rule);
|
|
1365
|
-
}
|
|
1366
|
-
return filtered;
|
|
1367
|
-
}
|
|
1368
|
-
getRuleMessage($input, rule, messages, defaultMessage, overrideMessage) {
|
|
1369
|
-
const attrMessage = $input.getAttribute(`data-error-${rule}`);
|
|
1370
|
-
return overrideMessage || attrMessage || messages[rule] || defaultMessage;
|
|
1371
|
-
}
|
|
1372
|
-
fieldMatchesSchemaDefinition($input, key, def) {
|
|
1373
|
-
if (def.selector) {
|
|
1374
|
-
try {
|
|
1375
|
-
return $input.matches(def.selector);
|
|
1376
|
-
}
|
|
1377
|
-
catch (_a) {
|
|
1378
|
-
console.warn(`[FormFather] Invalid validation selector "${def.selector}"`);
|
|
1379
|
-
return false;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
return this.parseValidationAttribute($input.getAttribute('data-validate')).some(rule => this.getRuleName(rule) === key);
|
|
1383
|
-
}
|
|
1384
|
-
getFieldValidationConfig($input, schema = this.getValidationSchema()) {
|
|
1385
|
-
const rules = [];
|
|
1386
|
-
const messages = {};
|
|
1387
|
-
for (const [key, def] of Object.entries(schema)) {
|
|
1388
|
-
if (!this.fieldMatchesSchemaDefinition($input, key, def))
|
|
1389
|
-
continue;
|
|
1390
|
-
rules.push(...def.rules);
|
|
1391
|
-
Object.assign(messages, def.messages || {});
|
|
1392
|
-
}
|
|
1393
|
-
return {
|
|
1394
|
-
rules: this.collectRules($input, rules, new Set(Object.keys(schema))),
|
|
1395
|
-
messages,
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
|
-
getValidationFields($block, schema) {
|
|
1399
|
-
const fields = new Set();
|
|
1400
|
-
const addFields = (nodeList) => {
|
|
1401
|
-
Array.from(nodeList).forEach($input => {
|
|
1402
|
-
if ($input instanceof HTMLInputElement ||
|
|
1403
|
-
$input instanceof HTMLTextAreaElement ||
|
|
1404
|
-
$input instanceof HTMLSelectElement) {
|
|
1405
|
-
fields.add($input);
|
|
1406
|
-
}
|
|
1407
|
-
});
|
|
1408
|
-
};
|
|
1409
|
-
for (const [key, def] of Object.entries(schema)) {
|
|
1410
|
-
if (def.selector) {
|
|
1411
|
-
try {
|
|
1412
|
-
addFields($block.querySelectorAll(def.selector));
|
|
1413
|
-
}
|
|
1414
|
-
catch (_a) {
|
|
1415
|
-
console.warn(`[FormFather] Invalid validation selector "${def.selector}"`);
|
|
1416
|
-
}
|
|
1417
|
-
continue;
|
|
1418
|
-
}
|
|
1419
|
-
this.getAllFields($block)
|
|
1420
|
-
.filter($input => this.parseValidationAttribute($input.getAttribute('data-validate')).some(rule => this.getRuleName(rule) === key))
|
|
1421
|
-
.forEach($input => fields.add($input));
|
|
1422
|
-
}
|
|
1423
|
-
addFields($block.querySelectorAll('[required], [data-validate], [data-custom-validate]'));
|
|
1424
|
-
return Array.from(fields);
|
|
1425
|
-
}
|
|
1426
|
-
validateInput($input_1, rules_1) {
|
|
1427
|
-
return __awaiter(this, arguments, void 0, function* ($input, rules, messages = {}, $block = this.$el, validationToken) {
|
|
1428
|
-
if ($input.hasAttribute('data-no-validate'))
|
|
1429
|
-
return true;
|
|
1430
|
-
const isCheckable = $input instanceof HTMLInputElement && ($input.type === 'checkbox' || $input.type === 'radio');
|
|
1431
|
-
const empty = isCheckable ? !$input.checked : $input.value.trim().length === 0;
|
|
1432
|
-
const hasRequiredRule = rules.some(rule => this.getRuleName(rule) === 'required');
|
|
1433
|
-
if (empty && !hasRequiredRule) {
|
|
1434
|
-
if (!this.isLatestFieldValidation($input, validationToken))
|
|
1435
|
-
return !this.hasFieldError($input);
|
|
1436
|
-
this.clearFieldErrorRecord($input);
|
|
1437
|
-
this.hideError($input);
|
|
1438
|
-
return true;
|
|
1439
|
-
}
|
|
1440
|
-
for (const validationRule of rules) {
|
|
1441
|
-
const { rule, params } = typeof validationRule === 'string'
|
|
1442
|
-
? { rule: validationRule, params: undefined }
|
|
1443
|
-
: { rule: validationRule.rule, params: validationRule.params };
|
|
1444
|
-
const validator = getValidator(rule);
|
|
1445
|
-
if (!validator)
|
|
1446
|
-
continue;
|
|
1447
|
-
const raw = yield validator.fn($input.value, $input, $block, params);
|
|
1448
|
-
const isObjectResult = typeof raw === 'object' && raw !== null;
|
|
1449
|
-
const passed = isObjectResult ? raw.valid === true : !!raw;
|
|
1450
|
-
if (!this.isLatestFieldValidation($input, validationToken))
|
|
1451
|
-
return !this.hasFieldError($input);
|
|
1452
|
-
if (isObjectResult && raw.effect) {
|
|
1453
|
-
yield raw.effect({
|
|
1454
|
-
value: $input.value,
|
|
1455
|
-
$input,
|
|
1456
|
-
$form: $block,
|
|
1457
|
-
params,
|
|
1458
|
-
});
|
|
1459
|
-
}
|
|
1460
|
-
if (!this.isLatestFieldValidation($input, validationToken))
|
|
1461
|
-
return !this.hasFieldError($input);
|
|
1462
|
-
if (!passed) {
|
|
1463
|
-
const result = raw;
|
|
1464
|
-
const message = this.getRuleMessage($input, rule, messages, validator.defaultMessage, isObjectResult && typeof result.message === 'string' ? result.message : undefined);
|
|
1465
|
-
this.setValidationError($input, rule, message, 'client');
|
|
1466
|
-
this.showError($input, message);
|
|
1467
|
-
return false;
|
|
1468
|
-
}
|
|
1469
|
-
if (isObjectResult && raw.stopOthers) {
|
|
1470
|
-
return true;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
if (!this.isLatestFieldValidation($input, validationToken))
|
|
1474
|
-
return !this.hasFieldError($input);
|
|
1475
|
-
this.clearFieldErrorRecord($input);
|
|
1476
|
-
this.hideError($input);
|
|
1477
|
-
return true;
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
runFieldValidation($input_1, rules_1) {
|
|
1481
|
-
return __awaiter(this, arguments, void 0, function* ($input, rules, messages = {}, $block = this.$el) {
|
|
1482
|
-
const validationToken = this.startFieldValidation($input);
|
|
1483
|
-
try {
|
|
1484
|
-
const isValid = yield this.validateInput($input, rules, messages, $block, validationToken);
|
|
1485
|
-
this.finishFieldValidation($input, validationToken, isValid);
|
|
1486
|
-
return isValid;
|
|
1487
|
-
}
|
|
1488
|
-
catch (error) {
|
|
1489
|
-
if (this.isLatestFieldValidation($input, validationToken)) {
|
|
1490
|
-
this.clearFieldValidationState($input);
|
|
1491
|
-
}
|
|
1492
|
-
throw error;
|
|
1493
|
-
}
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
getRadioGroup($input, $block) {
|
|
1497
|
-
if (!($input instanceof HTMLInputElement) || $input.type !== 'radio' || !$input.name)
|
|
1498
|
-
return [];
|
|
1499
|
-
return Array.from($block.querySelectorAll('input[type="radio"]')).filter($radio => $radio.name === $input.name);
|
|
1500
|
-
}
|
|
1501
|
-
/** Показывает ошибку конкретного поля по имени или DOM-элементу. */
|
|
1502
|
-
showFieldError(field, message, source = 'manual', rule = source) {
|
|
1503
|
-
const $field = this.resolveField(field);
|
|
1504
|
-
if (!$field) {
|
|
1505
|
-
console.warn(`[FormFather] Field "${String(field)}" not found`);
|
|
1506
|
-
return false;
|
|
1507
|
-
}
|
|
1508
|
-
this.setValidationError($field, rule, message, source);
|
|
1509
|
-
this.showError($field, message);
|
|
1510
|
-
$field.removeAttribute('aria-busy');
|
|
1511
|
-
this.setFieldValidationState($field, 'invalid');
|
|
1512
|
-
this.syncErrorSummary();
|
|
1513
|
-
return true;
|
|
1514
|
-
}
|
|
1515
|
-
/** Проверяет одно поле по имени или DOM-элементу. */
|
|
1516
|
-
validateField(field) {
|
|
1517
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1518
|
-
const $field = this.resolveField(field);
|
|
1519
|
-
if (!$field) {
|
|
1520
|
-
console.warn(`[FormFather] Field "${String(field)}" not found`);
|
|
1521
|
-
return false;
|
|
1522
|
-
}
|
|
1523
|
-
const { rules, messages } = this.getFieldValidationConfig($field);
|
|
1524
|
-
const isValid = yield this.runFieldValidation($field, rules, messages);
|
|
1525
|
-
this.syncErrorSummary();
|
|
1526
|
-
return isValid;
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
/**
|
|
1530
|
-
* Проверяет все поля ввода.
|
|
1531
|
-
*
|
|
1532
|
-
* Порядок для каждого поля:
|
|
1533
|
-
*
|
|
1534
|
-
* 1. `required` — если атрибут `required`
|
|
1535
|
-
* 2. правила из `validationSchema`
|
|
1536
|
-
* 3. правила из `data-validate`
|
|
1537
|
-
* 4. правила из `data-custom-validate`
|
|
1538
|
-
*
|
|
1539
|
-
* Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
|
|
1540
|
-
* валидации.
|
|
1541
|
-
*/
|
|
1542
|
-
validate() {
|
|
1543
|
-
return __awaiter(this, arguments, void 0, function* ($block = this.$el) {
|
|
1544
|
-
const schema = this.getValidationSchema();
|
|
1545
|
-
const erroredInputs = [];
|
|
1546
|
-
const processed = new WeakSet();
|
|
1547
|
-
let ok = true;
|
|
1548
|
-
this.errors = [];
|
|
1549
|
-
for (const $input of this.getValidationFields($block, schema)) {
|
|
1550
|
-
if (processed.has($input))
|
|
1551
|
-
continue;
|
|
1552
|
-
const radioGroup = this.getRadioGroup($input, $block);
|
|
1553
|
-
if (radioGroup.length > 0) {
|
|
1554
|
-
radioGroup.forEach($radio => processed.add($radio));
|
|
1555
|
-
}
|
|
1556
|
-
else {
|
|
1557
|
-
processed.add($input);
|
|
1558
|
-
}
|
|
1559
|
-
const { rules, messages } = this.getFieldValidationConfig($input, schema);
|
|
1560
|
-
const fieldValid = yield this.runFieldValidation($input, rules, messages, $block);
|
|
1561
|
-
if (!fieldValid) {
|
|
1562
|
-
ok = false;
|
|
1563
|
-
erroredInputs.push($input);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
const formValidation = yield this.validateFormValidators();
|
|
1567
|
-
if (!formValidation.ok) {
|
|
1568
|
-
ok = false;
|
|
1569
|
-
erroredInputs.push(...formValidation.erroredInputs);
|
|
1570
|
-
}
|
|
1571
|
-
const visibleErroredInputs = erroredInputs.filter(item => !item.hasAttribute('data-no-error-scroll'));
|
|
1572
|
-
if (!ok && this.config.scrollToFirstErroredInput) {
|
|
1573
|
-
this.scrollToFirstErroredInput(visibleErroredInputs);
|
|
1574
|
-
}
|
|
1575
|
-
if (!ok && this.config.focusFirstErroredInput) {
|
|
1576
|
-
this.focusFirstErroredInput(visibleErroredInputs);
|
|
1577
|
-
}
|
|
1578
|
-
if (ok) {
|
|
1579
|
-
this.hideErrorSummary();
|
|
1580
|
-
}
|
|
1581
|
-
else {
|
|
1582
|
-
this.renderErrorSummary();
|
|
1583
|
-
}
|
|
1584
|
-
return ok;
|
|
1585
|
-
});
|
|
1586
|
-
}
|
|
1587
|
-
/** Блокирует кнопку отправки данных. */
|
|
1588
|
-
disableSubmit() {
|
|
1589
|
-
this.$submits.forEach($submit => {
|
|
1590
|
-
$submit.setAttribute('disabled', '');
|
|
1591
|
-
});
|
|
1592
|
-
}
|
|
1593
|
-
/** Снимает блокировку с кнопки отправки данных. */
|
|
1594
|
-
enableSubmit() {
|
|
1595
|
-
this.$submits.forEach($submit => {
|
|
1596
|
-
$submit.removeAttribute('disabled');
|
|
1597
|
-
});
|
|
1598
|
-
}
|
|
1599
|
-
/** Очищает поля ввода формы. */
|
|
1600
|
-
clearInputs() {
|
|
1601
|
-
var _a;
|
|
1602
|
-
(_a = this.inputs) === null || _a === void 0 ? void 0 : _a.forEach($inputEl => {
|
|
1603
|
-
var _a;
|
|
1604
|
-
const $input = $inputEl;
|
|
1605
|
-
if ($input.type === 'radio' || $input.type === 'checkbox') {
|
|
1606
|
-
$input.checked = false;
|
|
1607
|
-
}
|
|
1608
|
-
else {
|
|
1609
|
-
$input.value = '';
|
|
1610
|
-
(_a = $input.parentElement) === null || _a === void 0 ? void 0 : _a.classList.remove('filled');
|
|
1611
|
-
$input.dispatchEvent(new Event('input'));
|
|
1612
|
-
}
|
|
1613
|
-
});
|
|
1614
|
-
}
|
|
1615
|
-
_prefersReducedMotion() {
|
|
1616
|
-
var _a;
|
|
1617
|
-
return typeof window !== 'undefined' && ((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-reduced-motion: reduce)').matches);
|
|
1618
|
-
}
|
|
1619
|
-
_canAnimate(el) {
|
|
1620
|
-
return !!(el && 'animate' in el && !this._prefersReducedMotion());
|
|
1621
|
-
}
|
|
1622
|
-
_cancelAnimations(el) {
|
|
1623
|
-
var _a;
|
|
1624
|
-
// Отменяем все активные WAAPI-анимации у элемента
|
|
1625
|
-
const animatedEl = el;
|
|
1626
|
-
(_a = animatedEl === null || animatedEl === void 0 ? void 0 : animatedEl.getAnimations) === null || _a === void 0 ? void 0 : _a.call(animatedEl).forEach(animation => animation.cancel());
|
|
1627
|
-
}
|
|
1628
|
-
/**
|
|
1629
|
-
* Показывает ошибку под полями ввода - ошибку, относящуюся ко всей форме.
|
|
1630
|
-
*
|
|
1631
|
-
* @param {String} text - Текст ошибки.
|
|
1632
|
-
*/
|
|
1633
|
-
showErrorForm(text) {
|
|
1634
|
-
var _a;
|
|
1635
|
-
const inputWrappersList = this.$el.querySelectorAll(this.config.inputWrapperSelector);
|
|
1636
|
-
const $lastInputWrapper = inputWrappersList[inputWrappersList.length - 1];
|
|
1637
|
-
let $errorWrapper = this.$el.querySelector('.error-block-under-input__wrapper');
|
|
1638
|
-
if (!$errorWrapper || ($lastInputWrapper && !($lastInputWrapper.nextElementSibling === $errorWrapper))) {
|
|
1639
|
-
$errorWrapper = document.createElement('div');
|
|
1640
|
-
$errorWrapper.className = 'error-block-under-input__wrapper';
|
|
1641
|
-
$errorWrapper.innerHTML = `
|
|
1642
|
-
<div class="error-block-under-input error-block-under-input--warning" style="display:grid;grid-template-columns:auto 1fr;gap:8px;align-items:start;">
|
|
1643
|
-
<div class="error-block-under-input__icon-wrapper" style="position:relative;width:20px;height:20px;">
|
|
1644
|
-
<span class="error-block-under-input__icon" style="display:block;width:20px;height:20px;"></span>
|
|
1645
|
-
<span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:block;position:absolute;inset:0;"></span>
|
|
1646
|
-
</div>
|
|
1647
|
-
<p class="error-block-under-input__text" style="margin:0;">
|
|
1648
|
-
<span class="error-block-under-input__main-text"></span>
|
|
1649
|
-
<span class="error-block-under-input__secondary-text"></span>
|
|
1650
|
-
</p>
|
|
1651
|
-
</div>
|
|
1652
|
-
`;
|
|
1653
|
-
// Стартовые inline-стили для анимаций
|
|
1654
|
-
Object.assign($errorWrapper.style, {
|
|
1655
|
-
boxSizing: 'border-box',
|
|
1656
|
-
overflow: 'hidden',
|
|
1657
|
-
height: '0px',
|
|
1658
|
-
paddingTop: '0px',
|
|
1659
|
-
opacity: '0',
|
|
1660
|
-
});
|
|
1661
|
-
if ($lastInputWrapper) {
|
|
1662
|
-
$lastInputWrapper.insertAdjacentElement('afterend', $errorWrapper);
|
|
1663
|
-
}
|
|
1664
|
-
else {
|
|
1665
|
-
this.$el.appendChild($errorWrapper);
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
else {
|
|
1669
|
-
// убедимся, что нужные стартовые inline-стили есть
|
|
1670
|
-
$errorWrapper.style.boxSizing = 'border-box';
|
|
1671
|
-
$errorWrapper.style.overflow = 'hidden';
|
|
1672
|
-
}
|
|
1673
|
-
// Текст
|
|
1674
|
-
$errorWrapper.querySelector('.error-block-under-input__main-text').textContent = text;
|
|
1675
|
-
const $secondaryText = $errorWrapper.querySelector('.error-block-under-input__secondary-text');
|
|
1676
|
-
if ($secondaryText)
|
|
1677
|
-
$secondaryText.textContent = '';
|
|
1678
|
-
// Сброс success-класса, если он устанавливался где-то в другом коде
|
|
1679
|
-
(_a = $errorWrapper.querySelector('.error-block-under-input')) === null || _a === void 0 ? void 0 : _a.classList.remove('error-block-under-input--success');
|
|
1680
|
-
// Посчитать целевую высоту
|
|
1681
|
-
// Чтобы корректно мерить, временно выставим auto
|
|
1682
|
-
const prevHeight = $errorWrapper.style.height;
|
|
1683
|
-
const prevPadding = $errorWrapper.style.paddingTop;
|
|
1684
|
-
$errorWrapper.style.height = 'auto';
|
|
1685
|
-
$errorWrapper.style.paddingTop = `${this._anim.paddingTopOpened}px`;
|
|
1686
|
-
const targetHeight = $errorWrapper.clientHeight; // фактическая высота открытого состояния
|
|
1687
|
-
// Вернуть стартовое состояние (схлопнуто)
|
|
1688
|
-
$errorWrapper.style.height = prevHeight || '0px';
|
|
1689
|
-
$errorWrapper.style.paddingTop = prevPadding || '0px';
|
|
1690
|
-
// Если уже раскрыт на нужную высоту — просто «дзынь» иконке и выходим
|
|
1691
|
-
if (parseFloat($errorWrapper.style.height) === targetHeight &&
|
|
1692
|
-
parseFloat($errorWrapper.style.opacity || '1') === 1) {
|
|
1693
|
-
this._ringIcon($errorWrapper);
|
|
1694
|
-
}
|
|
1695
|
-
else {
|
|
1696
|
-
// Анимация раскрытия
|
|
1697
|
-
this._cancelAnimations($errorWrapper);
|
|
1698
|
-
if (this._canAnimate($errorWrapper)) {
|
|
1699
|
-
const anim = $errorWrapper.animate([
|
|
1700
|
-
{
|
|
1701
|
-
height: `${parseFloat($errorWrapper.style.height) || 0}px`,
|
|
1702
|
-
paddingTop: `${parseFloat($errorWrapper.style.paddingTop) || 0}px`,
|
|
1703
|
-
opacity: parseFloat($errorWrapper.style.opacity || '0'),
|
|
1704
|
-
},
|
|
1705
|
-
{
|
|
1706
|
-
height: `${targetHeight}px`,
|
|
1707
|
-
paddingTop: `${this._anim.paddingTopOpened}px`,
|
|
1708
|
-
opacity: 1,
|
|
1709
|
-
},
|
|
1710
|
-
], { duration: this._anim.duration, easing: this._anim.easing, fill: 'forwards' });
|
|
1711
|
-
anim.addEventListener('finish', () => {
|
|
1712
|
-
// После анимации: фиксируем авто-высоту, чтобы текст мог меняться
|
|
1713
|
-
$errorWrapper.style.height = 'auto';
|
|
1714
|
-
$errorWrapper.style.paddingTop = `${this._anim.paddingTopOpened}px`;
|
|
1715
|
-
$errorWrapper.style.opacity = '1';
|
|
1716
|
-
});
|
|
1717
|
-
}
|
|
1718
|
-
else {
|
|
1719
|
-
// Фолбэк без анимации
|
|
1720
|
-
$errorWrapper.style.height = 'auto';
|
|
1721
|
-
$errorWrapper.style.paddingTop = `${this._anim.paddingTopOpened}px`;
|
|
1722
|
-
$errorWrapper.style.opacity = '1';
|
|
1723
|
-
}
|
|
1724
|
-
this._ringIcon($errorWrapper);
|
|
1725
|
-
}
|
|
1726
|
-
// Скрыть при первом вводе после показа
|
|
1727
|
-
this.$el.addEventListener('input', () => this.hideErrorForm(), { passive: true, once: true });
|
|
1728
|
-
}
|
|
1729
|
-
/** Скрывает ошибку под полями ввода - ошибку, относящуюся ко всей форме. */
|
|
1730
|
-
hideErrorForm() {
|
|
1731
|
-
const inputWrappersList = this.$el.querySelectorAll(this.config.inputWrapperSelector);
|
|
1732
|
-
const $lastInputWrapper = inputWrappersList[inputWrappersList.length - 1];
|
|
1733
|
-
const $errorWrapper = (($lastInputWrapper === null || $lastInputWrapper === void 0 ? void 0 : $lastInputWrapper.nextElementSibling) || this.$el.querySelector('.error-block-under-input__wrapper'));
|
|
1734
|
-
if (!$errorWrapper || !$errorWrapper.classList.contains('error-block-under-input__wrapper'))
|
|
1735
|
-
return;
|
|
1736
|
-
// Текущая высота (если была auto) — измерим и зафиксируем
|
|
1737
|
-
const wasAuto = $errorWrapper.style.height === '' || $errorWrapper.style.height === 'auto';
|
|
1738
|
-
if (wasAuto) {
|
|
1739
|
-
$errorWrapper.style.height = `${$errorWrapper.clientHeight}px`;
|
|
1740
|
-
}
|
|
1741
|
-
$errorWrapper.style.paddingTop = $errorWrapper.style.paddingTop || `${this._anim.paddingTopOpened}px`;
|
|
1742
|
-
$errorWrapper.style.opacity = $errorWrapper.style.opacity || '1';
|
|
1743
|
-
// Анимация схлопывания
|
|
1744
|
-
this._cancelAnimations($errorWrapper);
|
|
1745
|
-
if (this._canAnimate($errorWrapper)) {
|
|
1746
|
-
const anim = $errorWrapper.animate([
|
|
1747
|
-
{
|
|
1748
|
-
height: `${parseFloat($errorWrapper.style.height) || $errorWrapper.clientHeight}px`,
|
|
1749
|
-
paddingTop: `${parseFloat($errorWrapper.style.paddingTop) || this._anim.paddingTopOpened}px`,
|
|
1750
|
-
opacity: parseFloat($errorWrapper.style.opacity || '1'),
|
|
1751
|
-
},
|
|
1752
|
-
{ height: '0px', paddingTop: '0px', opacity: 0 },
|
|
1753
|
-
], { duration: this._anim.duration, easing: this._anim.easing, fill: 'forwards' });
|
|
1754
|
-
anim.addEventListener('finish', () => {
|
|
1755
|
-
// После закрытия можно удалить узел или оставить схлопнутым
|
|
1756
|
-
$errorWrapper.remove();
|
|
1757
|
-
// Если хотите оставлять в DOM:
|
|
1758
|
-
// $errorWrapper.style.height = '0px';
|
|
1759
|
-
// $errorWrapper.style.paddingTop = '0px';
|
|
1760
|
-
// $errorWrapper.style.opacity = '0';
|
|
1761
|
-
});
|
|
1762
|
-
}
|
|
1763
|
-
else {
|
|
1764
|
-
// Фолбэк без анимации
|
|
1765
|
-
$errorWrapper.remove();
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
/** Короткая анимация иконки без CSS */
|
|
1769
|
-
_ringIcon($wrapper) {
|
|
1770
|
-
const $icon = $wrapper.querySelector('.error-block-under-input__icon--animated');
|
|
1771
|
-
if (!$icon)
|
|
1772
|
-
return;
|
|
1773
|
-
this._cancelAnimations($icon);
|
|
1774
|
-
if (!this._canAnimate($icon))
|
|
1775
|
-
return;
|
|
1776
|
-
// Небольшое «качание» и вспышка прозрачности
|
|
1777
|
-
$icon.animate([
|
|
1778
|
-
{ transform: 'rotate(0deg) scale(1)', opacity: 0 },
|
|
1779
|
-
{ transform: 'rotate(-8deg) scale(1.05)', opacity: 1, offset: 0.15 },
|
|
1780
|
-
{ transform: 'rotate(8deg) scale(1.05)', opacity: 1, offset: 0.3 },
|
|
1781
|
-
{ transform: 'rotate(-6deg) scale(1.03)', opacity: 1, offset: 0.45 },
|
|
1782
|
-
{ transform: 'rotate(6deg) scale(1.03)', opacity: 1, offset: 0.6 },
|
|
1783
|
-
{ transform: 'rotate(0deg) scale(1)', opacity: 0 },
|
|
1784
|
-
], { duration: this._anim.iconDuration, easing: 'ease' });
|
|
1785
|
-
}
|
|
1786
|
-
/** Полностью снимает слушатели и чистит артефакты формы. */
|
|
1787
|
-
destroy() {
|
|
1788
|
-
var _a;
|
|
1789
|
-
// 1) Снять submit-слушатель
|
|
1790
|
-
if (this._onSubmitHandler) {
|
|
1791
|
-
this.$el.removeEventListener('submit', this._onSubmitHandler);
|
|
1792
|
-
this._onSubmitHandler = undefined;
|
|
1793
|
-
}
|
|
1794
|
-
// 1.1) Снять click-слушатель с кнопки submit
|
|
1795
|
-
if (this._onSubmitClickHandler) {
|
|
1796
|
-
this.$submits.forEach($submit => {
|
|
1797
|
-
$submit.removeEventListener('click', this._onSubmitClickHandler);
|
|
1798
|
-
});
|
|
1799
|
-
this._onSubmitClickHandler = undefined;
|
|
1800
|
-
}
|
|
1801
|
-
this.clearFieldValidationEvents();
|
|
1802
|
-
(_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
1803
|
-
this.mutationObserver = undefined;
|
|
1804
|
-
// 2) Удалить формовый error-блок под инпутами (если есть)
|
|
1805
|
-
const formErrorWrapper = this.$el.querySelector('.error-block-under-input__wrapper');
|
|
1806
|
-
if (formErrorWrapper) {
|
|
1807
|
-
this._cancelAnimations(formErrorWrapper);
|
|
1808
|
-
formErrorWrapper.remove();
|
|
1809
|
-
}
|
|
1810
|
-
this.hideErrorSummary();
|
|
1811
|
-
// 3) Скрыть/удалить лоадер на кнопке submit
|
|
1812
|
-
this.$submits.forEach($submit => {
|
|
1813
|
-
$submit.classList.remove('button--loading');
|
|
1814
|
-
const btnLoader = $submit.querySelector('.button__loader');
|
|
1815
|
-
if (btnLoader)
|
|
1816
|
-
btnLoader.remove();
|
|
1817
|
-
});
|
|
1818
|
-
// 4) Сбросить ошибки у инпутов (если разметка позволяет)
|
|
1819
|
-
const inputs = this.$el.querySelectorAll('input, textarea, select');
|
|
1820
|
-
inputs.forEach($i => {
|
|
1821
|
-
this.hideError($i);
|
|
1822
|
-
this.clearFieldValidationState($i);
|
|
1823
|
-
});
|
|
1824
|
-
// 5) Убрать возможный флаг скролла
|
|
1825
|
-
document.documentElement.removeAttribute('data-now-scrolling');
|
|
1826
|
-
// 6) Очистить ссылки на элементы, чтобы помочь GC
|
|
1827
|
-
this.inputs = null;
|
|
1828
|
-
this.$submits = [];
|
|
1829
|
-
this.errors = [];
|
|
1830
|
-
this.waitResponse = false;
|
|
1831
|
-
FORM_INSTANCES.delete(this.$el);
|
|
1832
|
-
}
|
|
1833
|
-
/**
|
|
1834
|
-
* Проверяет входные данные на их корректность.
|
|
1835
|
-
*
|
|
1836
|
-
* @returns {boolean} Корректны данные или нет (true/false).
|
|
1837
|
-
*/
|
|
1838
|
-
isCorrectArguments() {
|
|
1839
|
-
if (this.$el.tagName !== 'FORM') {
|
|
1840
|
-
console.warn(`$el не является тегом form`);
|
|
1841
|
-
return false;
|
|
1842
|
-
}
|
|
1843
|
-
if (this.$submits.length === 0) {
|
|
1844
|
-
console.warn(`В форме нет кнопки с типом submit`);
|
|
1845
|
-
return false;
|
|
1846
|
-
}
|
|
1847
|
-
return true;
|
|
1848
|
-
}
|
|
1849
|
-
formDataToObject(formData) {
|
|
1850
|
-
const data = {};
|
|
1851
|
-
formData.forEach((value, key) => {
|
|
1852
|
-
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
1853
|
-
if (!Array.isArray(data[key])) {
|
|
1854
|
-
data[key] = [data[key]];
|
|
1855
|
-
}
|
|
1856
|
-
data[key].push(value);
|
|
1857
|
-
}
|
|
1858
|
-
else {
|
|
1859
|
-
data[key] = value;
|
|
1860
|
-
}
|
|
1861
|
-
});
|
|
1862
|
-
return data;
|
|
1863
|
-
}
|
|
1864
|
-
appendFormDataValue(formData, key, value) {
|
|
1865
|
-
if (value === undefined || value === null)
|
|
1866
|
-
return;
|
|
1867
|
-
if (Array.isArray(value)) {
|
|
1868
|
-
value.forEach((item, index) => {
|
|
1869
|
-
this.appendFormDataValue(formData, `${key}[${index}]`, item);
|
|
1870
|
-
});
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
if (value instanceof Blob) {
|
|
1874
|
-
formData.append(key, value);
|
|
1875
|
-
return;
|
|
1876
|
-
}
|
|
1877
|
-
formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
1878
|
-
}
|
|
1879
|
-
objectToFormData(data) {
|
|
1880
|
-
const formData = new FormData();
|
|
1881
|
-
Object.entries(data).forEach(([key, value]) => {
|
|
1882
|
-
this.appendFormDataValue(formData, key, value);
|
|
1883
|
-
});
|
|
1884
|
-
return formData;
|
|
1885
|
-
}
|
|
1886
|
-
stringifyFormDataValue(value) {
|
|
1887
|
-
return typeof value === 'string' ? value : value.name;
|
|
1888
|
-
}
|
|
1889
|
-
formDataToUrlSearchParams(formData) {
|
|
1890
|
-
const params = new URLSearchParams();
|
|
1891
|
-
formData.forEach((value, key) => {
|
|
1892
|
-
params.append(key, this.stringifyFormDataValue(value));
|
|
1893
|
-
});
|
|
1894
|
-
return params;
|
|
1895
|
-
}
|
|
1896
|
-
formDataToTextBody(formData) {
|
|
1897
|
-
return Array.from(formData)
|
|
1898
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(this.stringifyFormDataValue(value))}`)
|
|
1899
|
-
.join('\n');
|
|
1900
|
-
}
|
|
1901
|
-
isBodylessMethod(method) {
|
|
1902
|
-
return method === 'GET' || method === 'HEAD';
|
|
1903
|
-
}
|
|
1904
|
-
appendQueryString(action, params) {
|
|
1905
|
-
const query = params.toString();
|
|
1906
|
-
if (!query)
|
|
1907
|
-
return action;
|
|
1908
|
-
const separator = action.includes('?') ? '&' : '?';
|
|
1909
|
-
return `${action}${separator}${query}`;
|
|
1910
|
-
}
|
|
1911
|
-
findFieldByName(name) {
|
|
1912
|
-
const fields = Array.from(this.$el.querySelectorAll('input, textarea, select'));
|
|
1913
|
-
return fields.find($field => $field.name === name) || null;
|
|
1914
|
-
}
|
|
1915
|
-
parseResponseBody(response) {
|
|
1916
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1917
|
-
try {
|
|
1918
|
-
const responseBody = yield response.json();
|
|
1919
|
-
return responseBody && typeof responseBody === 'object'
|
|
1920
|
-
? responseBody
|
|
1921
|
-
: {
|
|
1922
|
-
success: false,
|
|
1923
|
-
error: true,
|
|
1924
|
-
'error-msg': 'Некорректный ответ сервера',
|
|
1925
|
-
};
|
|
1926
|
-
}
|
|
1927
|
-
catch (_a) {
|
|
1928
|
-
return {
|
|
1929
|
-
success: false,
|
|
1930
|
-
error: true,
|
|
1931
|
-
'error-msg': 'Некорректный ответ сервера',
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
|
-
handleUnsuccessfulResponse(responseBody) {
|
|
1937
|
-
if (responseBody.error !== true)
|
|
1938
|
-
return;
|
|
1939
|
-
if (responseBody['error-msg']) {
|
|
1940
|
-
this.setErrorRecord(FORM_ERROR_FIELD, 'server', responseBody['error-msg'], 'server');
|
|
1941
|
-
this.showErrorForm(responseBody['error-msg']);
|
|
1942
|
-
}
|
|
1943
|
-
if (responseBody.errors) {
|
|
1944
|
-
const fieldsList = [];
|
|
1945
|
-
responseBody.errors.forEach(error => {
|
|
1946
|
-
const $field = this.findFieldByName(error.name);
|
|
1947
|
-
if ($field) {
|
|
1948
|
-
fieldsList.push($field);
|
|
1949
|
-
this.setValidationError($field, 'server', error['error-msg'], 'server');
|
|
1950
|
-
this.showError($field, error['error-msg']);
|
|
1951
|
-
$field.removeAttribute('aria-busy');
|
|
1952
|
-
this.setFieldValidationState($field, 'invalid');
|
|
1953
|
-
}
|
|
1954
|
-
else {
|
|
1955
|
-
this.setErrorRecord(error.name, 'server', error['error-msg'], 'server');
|
|
1956
|
-
console.warn(`Не найдено поле с именем: ${error.name}, для вывода ошибки: ${error['error-msg']}`);
|
|
1957
|
-
}
|
|
1958
|
-
});
|
|
1959
|
-
if (this.config.scrollToFirstErroredInput === true)
|
|
1960
|
-
this.scrollToFirstErroredInput(fieldsList);
|
|
1961
|
-
if (this.config.focusFirstErroredInput === true)
|
|
1962
|
-
this.focusFirstErroredInput(fieldsList);
|
|
1963
|
-
}
|
|
1964
|
-
this.syncErrorSummary();
|
|
1965
|
-
}
|
|
1966
|
-
sendData() {
|
|
1967
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1968
|
-
var _a;
|
|
1969
|
-
const action = this.$el.getAttribute('action') || '';
|
|
1970
|
-
const method = ((_a = this.$el.getAttribute('method')) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'POST';
|
|
1971
|
-
const enctype = this.$el.getAttribute('enctype') || 'application/x-www-form-urlencoded';
|
|
1972
|
-
let body = null;
|
|
1973
|
-
let headers = {};
|
|
1974
|
-
// Сбор данных из формы
|
|
1975
|
-
const formData = serializeToFormData(this.$el);
|
|
1976
|
-
let data = this.formDataToObject(formData);
|
|
1977
|
-
if (this.config.logging) {
|
|
1978
|
-
for (const key of formData.keys())
|
|
1979
|
-
console.log(formData.get(key));
|
|
1980
|
-
}
|
|
1981
|
-
// Применение обёртки, если указано в настройках
|
|
1982
|
-
const hasDataWrapper = typeof this.config.wrapData === 'function';
|
|
1983
|
-
if (typeof this.config.wrapData === 'function') {
|
|
1984
|
-
data = this.config.wrapData(data);
|
|
1985
|
-
}
|
|
1986
|
-
const requestFormData = hasDataWrapper ? this.objectToFormData(data) : formData;
|
|
1987
|
-
if (this.isBodylessMethod(method)) {
|
|
1988
|
-
return fetch(this.appendQueryString(action, this.formDataToUrlSearchParams(requestFormData)), {
|
|
1989
|
-
method,
|
|
1990
|
-
headers,
|
|
1991
|
-
});
|
|
1992
|
-
}
|
|
1993
|
-
switch (enctype) {
|
|
1994
|
-
case 'application/x-www-form-urlencoded':
|
|
1995
|
-
body = this.formDataToUrlSearchParams(requestFormData).toString();
|
|
1996
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
1997
|
-
break;
|
|
1998
|
-
case 'multipart/form-data':
|
|
1999
|
-
body = requestFormData;
|
|
2000
|
-
// ВАЖНО: Не указываем Content-Type, т.к. браузер сам добавит с корректной границей!
|
|
2001
|
-
break;
|
|
2002
|
-
case 'text/plain':
|
|
2003
|
-
body = this.formDataToTextBody(requestFormData);
|
|
2004
|
-
headers['Content-Type'] = 'text/plain';
|
|
2005
|
-
break;
|
|
2006
|
-
case 'application/json':
|
|
2007
|
-
body = JSON.stringify(data);
|
|
2008
|
-
headers['Content-Type'] = 'application/json';
|
|
2009
|
-
break;
|
|
2010
|
-
default:
|
|
2011
|
-
console.warn(`Неизвестный enctype: ${enctype}. Используется application/x-www-form-urlencoded.`);
|
|
2012
|
-
body = this.formDataToUrlSearchParams(requestFormData).toString();
|
|
2013
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
2014
|
-
}
|
|
2015
|
-
return fetch(action, {
|
|
2016
|
-
method,
|
|
2017
|
-
body,
|
|
2018
|
-
headers,
|
|
2019
|
-
});
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
submit() {
|
|
2023
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2024
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
2025
|
-
if (!this.waitResponse) {
|
|
2026
|
-
let loadingFinished = false;
|
|
2027
|
-
const finishLoading = () => {
|
|
2028
|
-
if (loadingFinished)
|
|
2029
|
-
return;
|
|
2030
|
-
this.waitResponse = false;
|
|
2031
|
-
if (this.config.showLoaderButton === true)
|
|
2032
|
-
this.hideLoader();
|
|
2033
|
-
loadingFinished = true;
|
|
2034
|
-
};
|
|
2035
|
-
try {
|
|
2036
|
-
const beforeSubmitResult = yield ((_b = (_a = this.config).onBeforeSubmit) === null || _b === void 0 ? void 0 : _b.call(_a, this));
|
|
2037
|
-
if (beforeSubmitResult === false) {
|
|
2038
|
-
return {
|
|
2039
|
-
success: false,
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
if (this.config.showLoaderButton === true)
|
|
2043
|
-
this.showLoader();
|
|
2044
|
-
(_d = (_c = this.config).onSubmit) === null || _d === void 0 ? void 0 : _d.call(_c, this);
|
|
2045
|
-
this.waitResponse = true;
|
|
2046
|
-
const response = yield this.sendData();
|
|
2047
|
-
const responseBody = yield this.parseResponseBody(response);
|
|
2048
|
-
finishLoading();
|
|
2049
|
-
(_f = (_e = this.config).onResponse) === null || _f === void 0 ? void 0 : _f.call(_e, responseBody, this);
|
|
2050
|
-
if (response.ok && responseBody.success === true) {
|
|
2051
|
-
(_h = (_g = this.config).onResponseSuccess) === null || _h === void 0 ? void 0 : _h.call(_g, responseBody, this);
|
|
2052
|
-
}
|
|
2053
|
-
else {
|
|
2054
|
-
(_k = (_j = this.config).onResponseUnsuccess) === null || _k === void 0 ? void 0 : _k.call(_j, responseBody, this);
|
|
2055
|
-
this.handleUnsuccessfulResponse(responseBody);
|
|
2056
|
-
}
|
|
2057
|
-
parseCommonResponseProperties(responseBody);
|
|
2058
|
-
return {
|
|
2059
|
-
success: response.ok && responseBody.success === true,
|
|
2060
|
-
response,
|
|
2061
|
-
responseBody,
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
catch (error) {
|
|
2065
|
-
finishLoading();
|
|
2066
|
-
(_m = (_l = this.config).onSubmitError) === null || _m === void 0 ? void 0 : _m.call(_l, error, this);
|
|
2067
|
-
throw error;
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
return undefined;
|
|
2071
|
-
});
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
export { FORM_ERROR_FIELD, blockScrollBody, closest, createFieldValidator, createFormValidator, createLengthValidator, createPatternValidator, createSchemaValidator, dateOrder, Form as default, getAllValidators, getValidator, isEmailValid, isPhoneValid, isUrlValid, parseCommonResponseProperties, registerFieldValidator, registerSchemaValidator, registerValidator, requiredIf, sameAsField, serializeFormToJSON, serializeToFormData, unblockScrollBody };
|
|
2076
|
-
//# sourceMappingURL=index.js.map
|