form-father 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +16 -9
- package/demos/main.js +3 -15
- package/dist/FormFather.min.js +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +64 -1
- package/dist/index.js.map +1 -1
- package/dist/types/adapters.d.ts +12 -0
- package/dist/types/index.d.ts +6 -0
- package/docs/en/README.md +16 -9
- package/docs/recipes/README.md +17 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.5.0 - 2026-04-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added field validator DX helpers: `registerFieldValidator()`, `createPatternValidator()`, and
|
|
10
|
+
`createLengthValidator()`.
|
|
11
|
+
- Added form validator DX helpers: `createFormValidator()`, `sameAsField()`, `requiredIf()`, and `dateOrder()`.
|
|
12
|
+
- Added helper types for reusable form validator predicates and issue factories.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Demo and documentation now use higher-level helper APIs for common password-confirm and validation rule recipes.
|
|
17
|
+
|
|
18
|
+
### Tests
|
|
19
|
+
|
|
20
|
+
- Test suite expanded to 115 tests.
|
|
21
|
+
- Coverage baseline: 98.19% statements, 86.91% branches, 97.04% functions, 100% lines.
|
|
22
|
+
|
|
5
23
|
## 0.4.0 - 2026-04-27
|
|
6
24
|
|
|
7
25
|
### Added
|
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ import Form, {
|
|
|
78
78
|
type FormResetOptions,
|
|
79
79
|
type FormValidator,
|
|
80
80
|
type ValidationIssue,
|
|
81
|
+
type FormValidatorPredicate,
|
|
81
82
|
} from 'form-father';
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -329,17 +330,13 @@ required → schema.rules → data-validate → data-custom-validate
|
|
|
329
330
|
### Cross-field и form-level validation
|
|
330
331
|
|
|
331
332
|
```ts
|
|
333
|
+
import { dateOrder, requiredIf, sameAsField } from 'form-father';
|
|
334
|
+
|
|
332
335
|
const form = new Form($form, {
|
|
333
336
|
formValidators: [
|
|
334
|
-
(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
: {
|
|
338
|
-
field: 'passwordConfirm',
|
|
339
|
-
rule: 'same-as-password',
|
|
340
|
-
message: 'Пароли не совпадают',
|
|
341
|
-
},
|
|
342
|
-
({ values }) => (values.start <= values.end ? true : { field: 'end', message: 'Дата окончания раньше начала' }),
|
|
337
|
+
sameAsField('passwordConfirm', 'password', 'Пароли не совпадают'),
|
|
338
|
+
requiredIf('email', ({ values }) => Boolean(values.subscribe), 'Email обязателен для подписки'),
|
|
339
|
+
dateOrder('start', 'end', 'Дата окончания раньше начала'),
|
|
343
340
|
],
|
|
344
341
|
});
|
|
345
342
|
```
|
|
@@ -354,6 +351,16 @@ form.setErrors({
|
|
|
354
351
|
});
|
|
355
352
|
```
|
|
356
353
|
|
|
354
|
+
### DX helpers для правил
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import { createLengthValidator, createPatternValidator, registerFieldValidator } from 'form-father';
|
|
358
|
+
|
|
359
|
+
registerFieldValidator('starts-with-a', value => value.startsWith('A'), 'Должно начинаться с A');
|
|
360
|
+
registerValidator('slug', createPatternValidator(/^[a-z0-9-]+$/), 'Только slug-символы');
|
|
361
|
+
registerValidator('short-name', createLengthValidator({ min: 2, max: 24 }), 'От 2 до 24 символов');
|
|
362
|
+
```
|
|
363
|
+
|
|
357
364
|
## Адаптеры схем
|
|
358
365
|
|
|
359
366
|
Form Father не тянет внешние зависимости, но умеет оборачивать Zod/Valibot/Yup-подобные схемы с `safeParse()` или
|
package/demos/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const api = window.FormFather || {};
|
|
2
2
|
const Form = api.default || api.Form || api;
|
|
3
|
-
const { registerValidator } = api;
|
|
3
|
+
const { createLengthValidator, registerValidator, sameAsField } = api;
|
|
4
4
|
const output = document.querySelector('#demo-output');
|
|
5
5
|
|
|
6
6
|
function writeOutput(title, payload) {
|
|
@@ -57,9 +57,7 @@ window.fetch = async (url, options = {}) => {
|
|
|
57
57
|
return json({ success: false, error: true, 'error-msg': `No demo route for ${requestUrl}` }, 404);
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
registerValidator('min-length', (
|
|
61
|
-
override: true,
|
|
62
|
-
});
|
|
60
|
+
registerValidator('min-length', createLengthValidator({ min: 6 }), 'Слишком короткое значение', { override: true });
|
|
63
61
|
registerValidator(
|
|
64
62
|
'available-email',
|
|
65
63
|
value =>
|
|
@@ -78,17 +76,7 @@ Form.initAll('form[data-form-father]', {
|
|
|
78
76
|
validationDebounce: 120,
|
|
79
77
|
focusFirstErroredInput: true,
|
|
80
78
|
loaderColor: 'currentColor',
|
|
81
|
-
formValidators(
|
|
82
|
-
if (values.password && values.passwordConfirm && values.password !== values.passwordConfirm) {
|
|
83
|
-
return {
|
|
84
|
-
field: 'passwordConfirm',
|
|
85
|
-
rule: 'same-as-password',
|
|
86
|
-
message: 'Пароли не совпадают',
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return true;
|
|
91
|
-
},
|
|
79
|
+
formValidators: sameAsField('passwordConfirm', 'password', 'Пароли не совпадают'),
|
|
92
80
|
onValidationError(errors) {
|
|
93
81
|
writeOutput('Client validation errors', errors);
|
|
94
82
|
},
|
package/dist/FormFather.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var FormFather=function(t){"use strict";function e(t,e,r,i){return new(r||(r=Promise))((function(n,s){function o(t){try{l(i.next(t))}catch(t){s(t)}}function a(t){try{l(i.throw(t))}catch(t){s(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(o,a)}l((i=i.apply(t,e||[])).next())}))}function r(t,e){if(null==t)return null;let r=t;for(;r;){if(r.matches(e))return r;r=r.parentElement}return null}"function"==typeof SuppressedError&&SuppressedError;const i=["http:","https:"];function n(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function s(t){return!!t&&("localhost"===t||function(t){const e=t.split(".");return 4===e.length&&e.every((t=>{if(!/^\d{1,3}$/.test(t))return!1;const e=Number(t);return e>=0&&e<=255}))}(t)||function(t){if(!t.startsWith("[")||!t.endsWith("]"))return!1;const e=t.slice(1,-1);return e.includes(":")&&/^[\da-f:.]+$/i.test(e)}(t)||function(t){const e=t.split(".");return e.length>=2&&e.every(n)}(t))}function o(t){const e=function(t){const e=t.trim();return 0===e.length?"":e.startsWith("//")?`https:${e}`:/^[a-z][a-z\d+.-]*:\/\//i.test(e)?e:`https://${e}`}(t);if(0===e.length)return!1;try{const t=new URL(e);return i.includes(t.protocol)&&s(t.hostname)}catch(t){return!1}}function a(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),r=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.disabled||t.hasAttribute("data-no-serialize"))return;if(t instanceof HTMLButtonElement)return;const{name:e,type:i}=t;if("file"===i)if(t.multiple){const i=t.files;if(i)for(let t=0;t<i.length;t++)r.append(`${e}[${t}]`,i[t])}else{const i=t.files;i&&i[0]&&r.append(e,i[0])}else{let n;if("select-one"===i||"select-multiple"===i){const s=t;if("select-multiple"===i){Array.from(s.selectedOptions).map((t=>t.value)).forEach(((t,i)=>{r.append(`${e}[${i}]`,t)}))}else n=s.value,n&&r.append(e,n)}else if("tel"===i)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===i||"radio"===i){if(!t.checked)return;n=t.value}else if("date"===i){const e=t.getAttribute("data-date-format");n=e?function(t,e){const r=new Date(t),i=t=>t.toString().padStart(2,"0"),n=i(r.getDate()),s=i(r.getMonth()+1),o=r.getFullYear().toString();return e.replace("dd",n).replace("mm",s).replace("yyyy",o)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==i&&"select-multiple"!==i&&r.append(e,n)}})),r}function l(t){if(Object.prototype.hasOwnProperty.call(t,"redirect-url"))if(t["redirect-url-delay"]){const e=Number(t["redirect-url-delay"]);setTimeout((()=>{window.location.href=t["redirect-url"]}),e)}else window.location.href=t["redirect-url"];else if(Object.prototype.hasOwnProperty.call(t,"reload")){if(!0===t.reload)if(t["reload-delay"]){const e=Number(t["reload-delay"]);setTimeout((()=>{window.location.reload()}),e)}else window.location.reload()}else if(Object.prototype.hasOwnProperty.call(t,"toast")){const e=t.toast;(Array.isArray(e)?e:[e]).forEach((t=>{window.showToast(t)}))}else Object.prototype.hasOwnProperty.call(t,"error-toast")&&window.showToast(t["error-toast"])}function d(t){const e={};return new FormData(t).forEach(((t,r)=>{e.hasOwnProperty(r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}var c;const u=Symbol.for("FormFather.validators"),h=null!==(c=globalThis[u])&&void 0!==c?c:globalThis[u]=new Map;function p(t,e,r,{override:i=!1}={}){!h.has(t)||i?h.set(t,{fn:e,defaultMessage:r}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const f=t=>h.get(t);function m(t,e,r){h.has(t)||h.set(t,{fn:e,defaultMessage:r})}function g(t,e){return"function"==typeof e?e(t):e||function(t){var e;if(!t||"object"!=typeof t)return;const r=t.issues||t.errors;if(Array.isArray(r)){const t=null===(e=r.find((t=>"string"==typeof(null==t?void 0:t.message))))||void 0===e?void 0:e.message;if(t)return t}return"string"==typeof t.message?t.message:void 0}(t)}function y(t,r={}){return(i,n,s,o)=>e(this,void 0,void 0,(function*(){const e=r.mapValue?r.mapValue(i,n,s,o):i;try{if("function"==typeof t)return a=yield t(e),l=g(void 0,r.message),"object"==typeof a&&null!==a?a:void 0===a||!!a||{valid:!1,message:l};if("function"==typeof t.safeParse){const i=yield t.safeParse(e);return!!i.success||{valid:!1,message:g(i.error||i,r.message)}}if("function"==typeof t.parse)return yield t.parse(e),!0}catch(t){return{valid:!1,message:g(t,r.message)}}var a,l;return{valid:!1,message:g(void 0,r.message)}}))}var b;m("required",((t,e,r)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=r.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),m("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),m("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),m("url",(t=>o(t)),"Неверный формат"),m("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const v=Symbol.for("formfather.shared"),E=null!==(b=globalThis[v])&&void 0!==b?b:globalThis[v]={defaultParams:{},defaultValidationSchema:void 0},w="_form";let S=0;const F=new WeakMap,A={tel:{selector:'input[type="tel"]',rules:["tel"]},email:{selector:'input[type="email"]',rules:["email"]},required:{selector:"[required]",rules:["required"]},url:{selector:'input[type="url"]',rules:["url"]},"not-numbers":{selector:'input[data-validate="not-numbers"]',rules:["not-numbers"]}};class V{static get defaultParams(){return E.defaultParams}static set defaultParams(t){E.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=E.defaultValidationSchema)&&void 0!==t?t:E.defaultValidationSchema=A}static set defaultValidationSchema(t){E.defaultValidationSchema=t}constructor(t,e={}){this.$submits=[],this.waitResponse=!1,this.inputs=null,this.fieldEventHandlers=[],this.liveValidationTimers=new WeakMap,this.fieldValidationTokens=new WeakMap,this.fieldValidationTokenCounter=0,this.errors=[],this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=e;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,focusFirstErroredInput:!1,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary",validateOn:"submit",revalidateOn:["input","change"],validationDebounce:0,errorContainerAttribute:"data-error-container",validationStateAttribute:"data-form-father-state",observeMutations:!1},V.defaultParams,e),this.$el?(this.$submits=this.findSubmitElements(),this.isCorrectArguments()&&(this.bindSubmitHandlers(),this.initialization(),this.bindFieldValidationEvents(),this.setupMutationObserver(),F.set(this.$el,this))):console.warn("Empty $el")}getOptions(){return this.options}static initAll(t="form[data-form-father]",e={}){return"undefined"==typeof document?[]:Array.from(document.querySelectorAll(t)).filter((t=>"FORM"===t.tagName)).map((t=>{const r=F.get(t);return r?(r.updateOptions(e),r):new V(t,e)}))}updateOptions(t){return this.options=Object.assign(Object.assign({},this.options),t),this.config=Object.assign(Object.assign({},this.config),t),this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents(),this.setupMutationObserver(),this}getErrors(){return this.errors.map((t=>Object.assign({},t)))}getValues(){return d(this.$el)}normalizeValueList(t){return Array.isArray(t)?t.map(String):[String(t)]}setValues(t){return this.getAllFields().forEach((e=>{if(!e.name||!Object.prototype.hasOwnProperty.call(t,e.name))return;const r=t[e.name];if(!(e instanceof HTMLInputElement&&"file"===e.type)){if(e instanceof HTMLInputElement&&"checkbox"===e.type)e.checked="boolean"==typeof r?r:this.normalizeValueList(r).includes(e.value);else if(e instanceof HTMLInputElement&&"radio"===e.type)e.checked=String(r)===e.value;else if(e instanceof HTMLSelectElement&&e.multiple){const t=this.normalizeValueList(r);Array.from(e.options).forEach((e=>{e.selected=t.includes(e.value)}))}else e.value=null==r?"":String(r);e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0}))}})),this}clearErrors(){return this.errors=[],this.getAllFields().forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),this.hideErrorForm(),this}isErrorResponse(t){return!!t&&"object"==typeof t&&"string"==typeof t.name&&"string"==typeof t["error-msg"]}normalizeValidationIssues(t,e){return null==t||!0===t?[]:!1===t?[{message:"Некорректные данные формы",source:e}]:"string"==typeof t?[{message:t,source:e}]:Array.isArray(t)?t.flatMap((t=>this.normalizeValidationIssues(t,e))):this.isErrorResponse(t)?[{field:t.name,rule:e,message:t["error-msg"],source:e}]:"object"==typeof t&&"message"in t&&"string"==typeof t.message?[Object.assign(Object.assign({},t),{source:t.source||e})]:"object"==typeof t?Object.entries(t).map((([t,r])=>({field:t,rule:e,message:r,source:e}))):[]}applyValidationIssue(t,e){const r=t.source||e,i=t.rule||r,n=t.field||w;if(n===w)return this.setErrorRecord(w,i,t.message,r),this.showErrorForm(t.message),null;const s=this.findFieldByName(n);return s?(this.showFieldError(s,t.message,r,i),s):(this.setErrorRecord(n,i,t.message,r),console.warn(`Не найдено поле с именем: ${n}, для вывода ошибки: ${t.message}`),null)}setErrors(t,e="server"){return this.normalizeValidationIssues(t,e).forEach((t=>{this.applyValidationIssue(t,e)})),this}validateFormValidators(){return e(this,void 0,void 0,(function*(){const t=this.config.formValidators?Array.isArray(this.config.formValidators)?this.config.formValidators:[this.config.formValidators]:[],e=[];let r=!0;for(const i of t){const t=yield i({form:this,formElement:this.$el,values:this.getValues(),errors:this.getErrors()}),n=this.normalizeValidationIssues(t,"client");0!==n.length&&(r=!1,n.forEach((t=>{const r=this.applyValidationIssue(t,"client");r&&e.push(r)})))}return{ok:r,erroredInputs:e}}))}reset(t={}){return this.$el.reset(),this.refreshControls(),!1!==t.clearErrors&&this.clearErrors(),this}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}refreshControls(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector)}findSubmitElements(){return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'))}bindSubmitHandlers(){this._onSubmitClickHandler||(this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)}))),this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this.$submits=this.findSubmitElements(),this.$submits.forEach((t=>{t.addEventListener("click",this._onSubmitClickHandler)}))}initialization(){this.refreshControls(),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)})),this.$el.addEventListener("submit",this._onSubmitHandler)}handleSubmitEvent(t){return e(this,void 0,void 0,(function*(){var e,r,i,n,s,o;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const a=yield this.validate();return null===(n=(i=this.config).onAfterValidate)||void 0===n||n.call(i,a,this),a?this.submit():(null===(o=(s=this.config).onValidationError)||void 0===o||o.call(s,this.getErrors(),this),{success:!1,error:this.getErrors()})}))}normalizeTriggers(t,e){const r=null==t?e:Array.isArray(t)?t:[t];return Array.from(new Set(r))}clearFieldValidationEvents(){this.fieldEventHandlers.forEach((({$input:t,event:e,handler:r})=>{t.removeEventListener(e,r)})),this.fieldEventHandlers=[]}bindFieldValidationEvents(){this.clearFieldValidationEvents();const t=this.normalizeTriggers(this.config.validateOn,["submit"]).filter((t=>"submit"!==t)),e=this.normalizeTriggers(this.config.revalidateOn,["input","change"]).filter((t=>"submit"!==t)),r=Array.from(new Set([...t,...e]));if(0===r.length)return;this.getAllFields().forEach((e=>{r.forEach((r=>{const i=()=>{(t.includes(r)||this.hasFieldError(e))&&this.scheduleFieldValidation(e)};e.addEventListener(r,i),this.fieldEventHandlers.push({$input:e,event:r,handler:i})}))}))}scheduleFieldValidation(t){const e=this.liveValidationTimers.get(t);e&&clearTimeout(e);const r=Math.max(0,Number(this.config.validationDebounce)||0),i=()=>{this.validateField(t)};if(0===r)return void i();const n=setTimeout(i,r);this.liveValidationTimers.set(t,n)}setupMutationObserver(){var t;null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0,this.config.observeMutations&&"undefined"!=typeof MutationObserver&&(this.mutationObserver=new MutationObserver((()=>{this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents()})),this.mutationObserver.observe(this.$el,{childList:!0,subtree:!0}))}getErrorContainer(t){const e=this.config.errorContainerAttribute||"data-error-container",r=t.getAttribute(e);if(!r)return null;try{return document.querySelector(r)}catch(t){return console.warn(`[FormFather] Invalid error container selector "${r}"`),null}}showError(t,e){t.setAttribute("aria-invalid","true");const i=this.getErrorContainer(t);if(i)return i.textContent=e,i.hasAttribute("role")||i.setAttribute("role","alert"),void i.removeAttribute("hidden");const n=r(t,this.config.inputWrapperSelector);if(!n)return void console.warn(`Не найдена обёртка для поля с именем: ${t.name||t.type}`);const s=n.showError;if("function"==typeof s)return void s.call(n,e);n.classList.add("input__wrapper--error");let o=n.querySelector("[data-form-father-error]");o||(o=document.createElement("div"),o.setAttribute("data-form-father-error",""),o.setAttribute("role","alert"),t.insertAdjacentElement("afterend",o)),o.textContent=e}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+S++,r=`\n\t\t\t\t<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t<defs>\n\t\t\t\t\t<linearGradient id="${e}" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t\t</linearGradient>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#${e})"></path>\n\t\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t\t</g>\n\t\t\t\t\t</g>\n\t\t\t\t</svg>\n\t\t\t`,i=document.createElement("span");i.innerHTML=r,i.className="button__loader",t.insertAdjacentElement("afterbegin",i),requestAnimationFrame((()=>{i.setAttribute("data-showed","")}))}))}hideLoader(){this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&(e.classList.remove("active"),e.removeAttribute("data-showed"),setTimeout((()=>{e.remove()}),250))}))}hideError(t){var e;const i=r(t,this.config.inputWrapperSelector)||t.parentElement,n=this.getErrorContainer(t);t.removeAttribute("aria-invalid"),n&&(n.textContent="",n.setAttribute("hidden","")),null===(e=null==i?void 0:i.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==i||i.classList.remove("input__wrapper--error"),i&&i.classList.contains("input__wrapper")&&i.getAttribute("data-type-error")}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input, textarea, select"));let r=null,i=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<i&&(i=n,r=t)})),r){const t=r.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,i=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:i,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}focusFirstErroredInput(t){var e;if(0===t.length)return;const r=Array.from(this.$el.querySelectorAll("input, textarea, select")),i=t.slice().sort(((t,e)=>r.indexOf(t)-r.indexOf(e)))[0];null===(e=null==i?void 0:i.focus)||void 0===e||e.call(i,{preventScroll:!0})}getValidationSchema(){return Object.assign(Object.assign({},V.defaultValidationSchema||{}),this.config.validationSchema||{})}getAllFields(t=this.$el){return Array.from(t.querySelectorAll("input, textarea, select"))}getFieldKey(t){return t.name||t.id||t.getAttribute("data-field")||t.type||"field"}hasFieldError(t){const e=this.getFieldKey(t);return"true"===t.getAttribute("aria-invalid")||this.errors.some((t=>t.field===e))}clearFieldErrorRecord(t){const e=this.getFieldKey(t);this.errors=this.errors.filter((t=>t.field!==e))}setErrorRecord(t,e,r,i){this.errors.push({field:t,rule:e,message:r,source:i})}getValidationStateAttribute(){return this.config.validationStateAttribute||"data-form-father-state"}setFieldValidationState(t,e){const r=this.getValidationStateAttribute();e?t.setAttribute(r,e):t.removeAttribute(r)}startFieldValidation(t){const e=++this.fieldValidationTokenCounter;return this.fieldValidationTokens.set(t,e),t.setAttribute("aria-busy","true"),this.setFieldValidationState(t,"validating"),e}isLatestFieldValidation(t,e){return null==e||this.fieldValidationTokens.get(t)===e}finishFieldValidation(t,e,r){this.isLatestFieldValidation(t,e)&&(t.removeAttribute("aria-busy"),this.setFieldValidationState(t,r?"valid":"invalid"))}clearFieldValidationState(t){this.fieldValidationTokens.delete(t),t.removeAttribute("aria-busy"),this.setFieldValidationState(t,null)}setValidationError(t,e,r,i){this.clearFieldErrorRecord(t),this.setErrorRecord(this.getFieldKey(t),e,r,i)}resolveField(t){return"string"!=typeof t?t:this.findFieldByName(t)}parseRuleString(t){const[e,...r]=t.split(":").map((t=>t.trim()));return r.length>0?{rule:e,params:1===r.length?r[0]:r}:e}parseValidationAttribute(t){return(null!=t?t:"").split(/[,\s|]+/).map((t=>t.trim())).filter(Boolean).map((t=>this.parseRuleString(t)))}getRuleName(t){return"string"==typeof t?t:t.rule}getRuleIdentity(t){return JSON.stringify(t)}collectRules(t,e=[],r=new Set){const i=[];t.hasAttribute("required")&&i.push("required"),i.push(...e);const n=this.parseValidationAttribute(t.getAttribute("data-validate")).filter((t=>{const e=this.getRuleName(t);return f(e)||!r.has(e)}));i.push(...n),i.push(...this.parseValidationAttribute(t.getAttribute("data-custom-validate")));const s=[],o=new Set;for(const t of i){const e=this.getRuleName(t);if(!f(e)){console.warn(`[FormFather] Unknown validation rule "${e}"`);continue}const r=this.getRuleIdentity(t);o.has(r)||(o.add(r),s.push(t))}return s}getRuleMessage(t,e,r,i,n){const s=t.getAttribute(`data-error-${e}`);return n||s||r[e]||i}fieldMatchesSchemaDefinition(t,e,r){if(r.selector)try{return t.matches(r.selector)}catch(t){return console.warn(`[FormFather] Invalid validation selector "${r.selector}"`),!1}return this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===e))}getFieldValidationConfig(t,e=this.getValidationSchema()){const r=[],i={};for(const[n,s]of Object.entries(e))this.fieldMatchesSchemaDefinition(t,n,s)&&(r.push(...s.rules),Object.assign(i,s.messages||{}));return{rules:this.collectRules(t,r,new Set(Object.keys(e))),messages:i}}getValidationFields(t,e){const r=new Set,i=t=>{Array.from(t).forEach((t=>{(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&r.add(t)}))};for(const[n,s]of Object.entries(e))if(s.selector)try{i(t.querySelectorAll(s.selector))}catch(t){console.warn(`[FormFather] Invalid validation selector "${s.selector}"`)}else this.getAllFields(t).filter((t=>this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===n)))).forEach((t=>r.add(t)));return i(t.querySelectorAll("[required], [data-validate], [data-custom-validate]")),Array.from(r)}validateInput(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el,n){if(t.hasAttribute("data-no-validate"))return!0;const s=t instanceof HTMLInputElement&&("checkbox"===t.type||"radio"===t.type)?!t.checked:0===t.value.trim().length,o=e.some((t=>"required"===this.getRuleName(t)));if(s&&!o)return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t);for(const s of e){const{rule:e,params:o}="string"==typeof s?{rule:s,params:void 0}:{rule:s.rule,params:s.params},a=f(e);if(!a)continue;const l=yield a.fn(t.value,t,i,o),d="object"==typeof l&&null!==l,c=d?!0===l.valid:!!l;if(!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(d&&l.effect&&(yield l.effect({value:t.value,$input:t,$form:i,params:o})),!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(!c){const i=l,n=this.getRuleMessage(t,e,r,a.defaultMessage,d&&"string"==typeof i.message?i.message:void 0);return this.setValidationError(t,e,n,"client"),this.showError(t,n),!1}if(d&&l.stopOthers)return!0}return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t)}))}runFieldValidation(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el){const n=this.startFieldValidation(t);try{const s=yield this.validateInput(t,e,r,i,n);return this.finishFieldValidation(t,n,s),s}catch(e){throw this.isLatestFieldValidation(t,n)&&this.clearFieldValidationState(t),e}}))}getRadioGroup(t,e){return t instanceof HTMLInputElement&&"radio"===t.type&&t.name?Array.from(e.querySelectorAll('input[type="radio"]')).filter((e=>e.name===t.name)):[]}showFieldError(t,e,r="manual",i=r){const n=this.resolveField(t);return n?(this.setValidationError(n,i,e,r),this.showError(n,e),n.removeAttribute("aria-busy"),this.setFieldValidationState(n,"invalid"),!0):(console.warn(`[FormFather] Field "${String(t)}" not found`),!1)}validateField(t){return e(this,void 0,void 0,(function*(){const e=this.resolveField(t);if(!e)return console.warn(`[FormFather] Field "${String(t)}" not found`),!1;const{rules:r,messages:i}=this.getFieldValidationConfig(e);return this.runFieldValidation(e,r,i)}))}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const e=this.getValidationSchema(),r=[],i=new WeakSet;let n=!0;this.errors=[];for(const s of this.getValidationFields(t,e)){if(i.has(s))continue;const o=this.getRadioGroup(s,t);o.length>0?o.forEach((t=>i.add(t))):i.add(s);const{rules:a,messages:l}=this.getFieldValidationConfig(s,e);(yield this.runFieldValidation(s,a,l,t))||(n=!1,r.push(s))}const s=yield this.validateFormValidators();s.ok||(n=!1,r.push(...s.erroredInputs));const o=r.filter((t=>!t.hasAttribute("data-no-error-scroll")));return!n&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(o),!n&&this.config.focusFirstErroredInput&&this.focusFirstErroredInput(o),n}))}disableSubmit(){this.$submits.forEach((t=>{t.setAttribute("disabled","")}))}enableSubmit(){this.$submits.forEach((t=>{t.removeAttribute("disabled")}))}clearInputs(){var t;null===(t=this.inputs)||void 0===t||t.forEach((t=>{var e;const r=t;"radio"===r.type||"checkbox"===r.type?r.checked=!1:(r.value="",null===(e=r.parentElement)||void 0===e||e.classList.remove("filled"),r.dispatchEvent(new Event("input")))}))}_prefersReducedMotion(){var t;return"undefined"!=typeof window&&(null===(t=window.matchMedia)||void 0===t?void 0:t.call(window,"(prefers-reduced-motion: reduce)").matches)}_canAnimate(t){return!(!t||!("animate"in t)||this._prefersReducedMotion())}_cancelAnimations(t){var e;const r=t;null===(e=null==r?void 0:r.getAnimations)||void 0===e||e.call(r).forEach((t=>t.cancel()))}showErrorForm(t){var e;const r=this.$el.querySelectorAll(this.config.inputWrapperSelector),i=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||i&&i.nextElementSibling!==n?(n=document.createElement("div"),n.className="error-block-under-input__wrapper",n.innerHTML='\n <div class="error-block-under-input error-block-under-input--warning" style="display:grid;grid-template-columns:auto 1fr;gap:8px;align-items:start;">\n <div class="error-block-under-input__icon-wrapper" style="position:relative;width:20px;height:20px;">\n <span class="error-block-under-input__icon" style="display:block;width:20px;height:20px;"></span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:block;position:absolute;inset:0;"></span>\n </div>\n <p class="error-block-under-input__text" style="margin:0;">\n <span class="error-block-under-input__main-text"></span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>\n ',Object.assign(n.style,{boxSizing:"border-box",overflow:"hidden",height:"0px",paddingTop:"0px",opacity:"0"}),i?i.insertAdjacentElement("afterend",n):this.$el.appendChild(n)):(n.style.boxSizing="border-box",n.style.overflow="hidden"),n.querySelector(".error-block-under-input__main-text").textContent=t;const s=n.querySelector(".error-block-under-input__secondary-text");s&&(s.textContent=""),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const o=n.style.height,a=n.style.paddingTop;n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`;const l=n.clientHeight;if(n.style.height=o||"0px",n.style.paddingTop=a||"0px",parseFloat(n.style.height)===l&&1===parseFloat(n.style.opacity||"1"))this._ringIcon(n);else{if(this._cancelAnimations(n),this._canAnimate(n)){n.animate([{height:`${parseFloat(n.style.height)||0}px`,paddingTop:`${parseFloat(n.style.paddingTop)||0}px`,opacity:parseFloat(n.style.opacity||"0")},{height:`${l}px`,paddingTop:`${this._anim.paddingTopOpened}px`,opacity:1}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1"}))}else n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1";this._ringIcon(n)}this.$el.addEventListener("input",(()=>this.hideErrorForm()),{passive:!0,once:!0})}hideErrorForm(){const t=this.$el.querySelectorAll(this.config.inputWrapperSelector),e=t[t.length-1],r=(null==e?void 0:e.nextElementSibling)||this.$el.querySelector(".error-block-under-input__wrapper");if(!r||!r.classList.contains("error-block-under-input__wrapper"))return;if((""===r.style.height||"auto"===r.style.height)&&(r.style.height=`${r.clientHeight}px`),r.style.paddingTop=r.style.paddingTop||`${this._anim.paddingTopOpened}px`,r.style.opacity=r.style.opacity||"1",this._cancelAnimations(r),this._canAnimate(r)){r.animate([{height:`${parseFloat(r.style.height)||r.clientHeight}px`,paddingTop:`${parseFloat(r.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(r.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{r.remove()}))}else r.remove()}_ringIcon(t){const e=t.querySelector(".error-block-under-input__icon--animated");e&&(this._cancelAnimations(e),this._canAnimate(e)&&e.animate([{transform:"rotate(0deg) scale(1)",opacity:0},{transform:"rotate(-8deg) scale(1.05)",opacity:1,offset:.15},{transform:"rotate(8deg) scale(1.05)",opacity:1,offset:.3},{transform:"rotate(-6deg) scale(1.03)",opacity:1,offset:.45},{transform:"rotate(6deg) scale(1.03)",opacity:1,offset:.6},{transform:"rotate(0deg) scale(1)",opacity:0}],{duration:this._anim.iconDuration,easing:"ease"}))}destroy(){var t;this._onSubmitHandler&&(this.$el.removeEventListener("submit",this._onSubmitHandler),this._onSubmitHandler=void 0),this._onSubmitClickHandler&&(this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this._onSubmitClickHandler=void 0),this.clearFieldValidationEvents(),null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0;const e=this.$el.querySelector(".error-block-under-input__wrapper");e&&(this._cancelAnimations(e),e.remove()),this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&e.remove()}));this.$el.querySelectorAll("input, textarea, select").forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),document.documentElement.removeAttribute("data-now-scrolling"),this.inputs=null,this.$submits=[],this.errors=[],this.waitResponse=!1,F.delete(this.$el)}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):0!==this.$submits.length||(console.warn("В форме нет кнопки с типом submit"),!1)}formDataToObject(t){const e={};return t.forEach(((t,r)=>{Object.prototype.hasOwnProperty.call(e,r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}appendFormDataValue(t,e,r){null!=r&&(Array.isArray(r)?r.forEach(((r,i)=>{this.appendFormDataValue(t,`${e}[${i}]`,r)})):r instanceof Blob?t.append(e,r):t.append(e,"object"==typeof r?JSON.stringify(r):String(r)))}objectToFormData(t){const e=new FormData;return Object.entries(t).forEach((([t,r])=>{this.appendFormDataValue(e,t,r)})),e}stringifyFormDataValue(t){return"string"==typeof t?t:t.name}formDataToUrlSearchParams(t){const e=new URLSearchParams;return t.forEach(((t,r)=>{e.append(r,this.stringifyFormDataValue(t))})),e}formDataToTextBody(t){return Array.from(t).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(this.stringifyFormDataValue(e))}`)).join("\n")}isBodylessMethod(t){return"GET"===t||"HEAD"===t}appendQueryString(t,e){const r=e.toString();if(!r)return t;const i=t.includes("?")?"&":"?";return`${t}${i}${r}`}findFieldByName(t){return Array.from(this.$el.querySelectorAll("input, textarea, select")).find((e=>e.name===t))||null}parseResponseBody(t){return e(this,void 0,void 0,(function*(){try{const e=yield t.json();return e&&"object"==typeof e?e:{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}catch(t){return{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}}))}handleUnsuccessfulResponse(t){if(!0===t.error&&(t["error-msg"]&&this.showErrorForm(t["error-msg"]),t.errors)){const e=[];t.errors.forEach((t=>{const r=this.findFieldByName(t.name);r?(e.push(r),this.setValidationError(r,"server",t["error-msg"],"server"),this.showError(r,t["error-msg"]),r.removeAttribute("aria-busy"),this.setFieldValidationState(r,"invalid")):console.warn(`Не найдено поле с именем: ${t.name}, для вывода ошибки: ${t["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(e),!0===this.config.focusFirstErroredInput&&this.focusFirstErroredInput(e)}}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",r=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",i=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,s={};const o=a(this.$el);let l=this.formDataToObject(o);if(this.config.logging)for(const t of o.keys())console.log(o.get(t));const d="function"==typeof this.config.wrapData;"function"==typeof this.config.wrapData&&(l=this.config.wrapData(l));const c=d?this.objectToFormData(l):o;if(this.isBodylessMethod(r))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(c)),{method:r,headers:s});switch(i){case"application/x-www-form-urlencoded":n=this.formDataToUrlSearchParams(c).toString(),s["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=c;break;case"text/plain":n=this.formDataToTextBody(c),s["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),s["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${i}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(c).toString(),s["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:r,body:n,headers:s})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,r,i,n,s,o,a,d,c,u,h;if(!this.waitResponse){let p=!1;const f=()=>{p||(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),p=!0)};try{if(!1===(yield null===(e=(t=this.config).onBeforeSubmit)||void 0===e?void 0:e.call(t,this)))return{success:!1};!0===this.config.showLoaderButton&&this.showLoader(),null===(i=(r=this.config).onSubmit)||void 0===i||i.call(r,this),this.waitResponse=!0;const u=yield this.sendData(),h=yield this.parseResponseBody(u);return f(),null===(s=(n=this.config).onResponse)||void 0===s||s.call(n,h,this),u.ok&&!0===h.success?null===(a=(o=this.config).onResponseSuccess)||void 0===a||a.call(o,h,this):(null===(c=(d=this.config).onResponseUnsuccess)||void 0===c||c.call(d,h,this),this.handleUnsuccessfulResponse(h)),l(h),{success:u.ok&&!0===h.success,response:u,responseBody:h}}catch(t){throw f(),null===(h=(u=this.config).onSubmitError)||void 0===h||h.call(u,t,this),t}}}))}}return t.FORM_ERROR_FIELD=w,t.blockScrollBody=function(){window.lastBlockScrollPosition=window.pageYOffset,document.body.style.top=-window.lastBlockScrollPosition+"px",document.body.classList.add("body--block-scroll")},t.closest=r,t.createFieldValidator=function(t,r){return(i,n,s,o)=>e(this,void 0,void 0,(function*(){return!!(yield t(i,n,s,o))||{valid:!1,message:r}}))},t.createSchemaValidator=y,t.default=V,t.getAllValidators=function(){return new Map(h)},t.getValidator=f,t.isEmailValid=function(t){return/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(t)},t.isPhoneValid=function(t){const e=[];return Array.from(t).forEach((t=>{(Number(t)||0===Number(t))&&e.push(t)})),!(e.length<11||e.length<12&&"+"===t[1])},t.isUrlValid=o,t.parseCommonResponseProperties=l,t.registerSchemaValidator=function(t,e,r,i={}){const{override:n}=i;p(t,y(e,function(t,e){var r={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(r[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(i=Object.getOwnPropertySymbols(t);n<i.length;n++)e.indexOf(i[n])<0&&Object.prototype.propertyIsEnumerable.call(t,i[n])&&(r[i[n]]=t[i[n]])}return r}(i,["override"])),r,{override:n})},t.registerValidator=p,t.serializeFormToJSON=d,t.serializeToFormData=a,t.unblockScrollBody=function(){document.body.classList.remove("body--block-scroll"),document.body.style.top="",window.scrollTo(0,window.lastBlockScrollPosition||0)},Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
1
|
+
var FormFather=function(t){"use strict";function e(t,e,r,i){return new(r||(r=Promise))((function(n,o){function s(t){try{l(i.next(t))}catch(t){o(t)}}function a(t){try{l(i.throw(t))}catch(t){o(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,a)}l((i=i.apply(t,e||[])).next())}))}function r(t,e){if(null==t)return null;let r=t;for(;r;){if(r.matches(e))return r;r=r.parentElement}return null}"function"==typeof SuppressedError&&SuppressedError;const i=["http:","https:"];function n(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function o(t){return!!t&&("localhost"===t||function(t){const e=t.split(".");return 4===e.length&&e.every((t=>{if(!/^\d{1,3}$/.test(t))return!1;const e=Number(t);return e>=0&&e<=255}))}(t)||function(t){if(!t.startsWith("[")||!t.endsWith("]"))return!1;const e=t.slice(1,-1);return e.includes(":")&&/^[\da-f:.]+$/i.test(e)}(t)||function(t){const e=t.split(".");return e.length>=2&&e.every(n)}(t))}function s(t){const e=function(t){const e=t.trim();return 0===e.length?"":e.startsWith("//")?`https:${e}`:/^[a-z][a-z\d+.-]*:\/\//i.test(e)?e:`https://${e}`}(t);if(0===e.length)return!1;try{const t=new URL(e);return i.includes(t.protocol)&&o(t.hostname)}catch(t){return!1}}function a(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),r=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.disabled||t.hasAttribute("data-no-serialize"))return;if(t instanceof HTMLButtonElement)return;const{name:e,type:i}=t;if("file"===i)if(t.multiple){const i=t.files;if(i)for(let t=0;t<i.length;t++)r.append(`${e}[${t}]`,i[t])}else{const i=t.files;i&&i[0]&&r.append(e,i[0])}else{let n;if("select-one"===i||"select-multiple"===i){const o=t;if("select-multiple"===i){Array.from(o.selectedOptions).map((t=>t.value)).forEach(((t,i)=>{r.append(`${e}[${i}]`,t)}))}else n=o.value,n&&r.append(e,n)}else if("tel"===i)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===i||"radio"===i){if(!t.checked)return;n=t.value}else if("date"===i){const e=t.getAttribute("data-date-format");n=e?function(t,e){const r=new Date(t),i=t=>t.toString().padStart(2,"0"),n=i(r.getDate()),o=i(r.getMonth()+1),s=r.getFullYear().toString();return e.replace("dd",n).replace("mm",o).replace("yyyy",s)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==i&&"select-multiple"!==i&&r.append(e,n)}})),r}function l(t){if(Object.prototype.hasOwnProperty.call(t,"redirect-url"))if(t["redirect-url-delay"]){const e=Number(t["redirect-url-delay"]);setTimeout((()=>{window.location.href=t["redirect-url"]}),e)}else window.location.href=t["redirect-url"];else if(Object.prototype.hasOwnProperty.call(t,"reload")){if(!0===t.reload)if(t["reload-delay"]){const e=Number(t["reload-delay"]);setTimeout((()=>{window.location.reload()}),e)}else window.location.reload()}else if(Object.prototype.hasOwnProperty.call(t,"toast")){const e=t.toast;(Array.isArray(e)?e:[e]).forEach((t=>{window.showToast(t)}))}else Object.prototype.hasOwnProperty.call(t,"error-toast")&&window.showToast(t["error-toast"])}function d(t){const e={};return new FormData(t).forEach(((t,r)=>{e.hasOwnProperty(r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}var c;const u=Symbol.for("FormFather.validators"),h=null!==(c=globalThis[u])&&void 0!==c?c:globalThis[u]=new Map;function p(t,e,r,{override:i=!1}={}){!h.has(t)||i?h.set(t,{fn:e,defaultMessage:r}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const f=t=>h.get(t);function m(t,e,r){h.has(t)||h.set(t,{fn:e,defaultMessage:r})}function g(t,e){return"function"==typeof e?e(t):e||function(t){var e;if(!t||"object"!=typeof t)return;const r=t.issues||t.errors;if(Array.isArray(r)){const t=null===(e=r.find((t=>"string"==typeof(null==t?void 0:t.message))))||void 0===e?void 0:e.message;if(t)return t}return"string"==typeof t.message?t.message:void 0}(t)}function y(t,r={}){return(i,n,o,s)=>e(this,void 0,void 0,(function*(){const e=r.mapValue?r.mapValue(i,n,o,s):i;try{if("function"==typeof t)return a=yield t(e),l=g(void 0,r.message),"object"==typeof a&&null!==a?a:void 0===a||!!a||{valid:!1,message:l};if("function"==typeof t.safeParse){const i=yield t.safeParse(e);return!!i.success||{valid:!1,message:g(i.error||i,r.message)}}if("function"==typeof t.parse)return yield t.parse(e),!0}catch(t){return{valid:!1,message:g(t,r.message)}}var a,l;return{valid:!1,message:g(void 0,r.message)}}))}function v(t,r){return(i,n,o,s)=>e(this,void 0,void 0,(function*(){return!!(yield t(i,n,o,s))||{valid:!1,message:r}}))}var b;m("required",((t,e,r)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=r.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),m("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),m("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),m("url",(t=>s(t)),"Неверный формат"),m("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const E=Symbol.for("formfather.shared"),w=null!==(b=globalThis[E])&&void 0!==b?b:globalThis[E]={defaultParams:{},defaultValidationSchema:void 0},S="_form";function F(t,e){return t[e]}function A(t){return Array.isArray(t)?0===t.length:null==t||0===String(t).trim().length}function V(t,r){return i=>e(this,void 0,void 0,(function*(){return!!(yield t(i))||function(t,r,i,n){return e(this,void 0,void 0,(function*(){const e="function"==typeof r?yield r(t):r;return"string"==typeof e?{field:i,rule:n,message:e}:Object.assign({field:i,rule:n},e)}))}(i,r)}))}let $=0;const T=new WeakMap,O={tel:{selector:'input[type="tel"]',rules:["tel"]},email:{selector:'input[type="email"]',rules:["email"]},required:{selector:"[required]",rules:["required"]},url:{selector:'input[type="url"]',rules:["url"]},"not-numbers":{selector:'input[data-validate="not-numbers"]',rules:["not-numbers"]}};class _{static get defaultParams(){return w.defaultParams}static set defaultParams(t){w.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=w.defaultValidationSchema)&&void 0!==t?t:w.defaultValidationSchema=O}static set defaultValidationSchema(t){w.defaultValidationSchema=t}constructor(t,e={}){this.$submits=[],this.waitResponse=!1,this.inputs=null,this.fieldEventHandlers=[],this.liveValidationTimers=new WeakMap,this.fieldValidationTokens=new WeakMap,this.fieldValidationTokenCounter=0,this.errors=[],this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=e;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,focusFirstErroredInput:!1,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary",validateOn:"submit",revalidateOn:["input","change"],validationDebounce:0,errorContainerAttribute:"data-error-container",validationStateAttribute:"data-form-father-state",observeMutations:!1},_.defaultParams,e),this.$el?(this.$submits=this.findSubmitElements(),this.isCorrectArguments()&&(this.bindSubmitHandlers(),this.initialization(),this.bindFieldValidationEvents(),this.setupMutationObserver(),T.set(this.$el,this))):console.warn("Empty $el")}getOptions(){return this.options}static initAll(t="form[data-form-father]",e={}){return"undefined"==typeof document?[]:Array.from(document.querySelectorAll(t)).filter((t=>"FORM"===t.tagName)).map((t=>{const r=T.get(t);return r?(r.updateOptions(e),r):new _(t,e)}))}updateOptions(t){return this.options=Object.assign(Object.assign({},this.options),t),this.config=Object.assign(Object.assign({},this.config),t),this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents(),this.setupMutationObserver(),this}getErrors(){return this.errors.map((t=>Object.assign({},t)))}getValues(){return d(this.$el)}normalizeValueList(t){return Array.isArray(t)?t.map(String):[String(t)]}setValues(t){return this.getAllFields().forEach((e=>{if(!e.name||!Object.prototype.hasOwnProperty.call(t,e.name))return;const r=t[e.name];if(!(e instanceof HTMLInputElement&&"file"===e.type)){if(e instanceof HTMLInputElement&&"checkbox"===e.type)e.checked="boolean"==typeof r?r:this.normalizeValueList(r).includes(e.value);else if(e instanceof HTMLInputElement&&"radio"===e.type)e.checked=String(r)===e.value;else if(e instanceof HTMLSelectElement&&e.multiple){const t=this.normalizeValueList(r);Array.from(e.options).forEach((e=>{e.selected=t.includes(e.value)}))}else e.value=null==r?"":String(r);e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0}))}})),this}clearErrors(){return this.errors=[],this.getAllFields().forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),this.hideErrorForm(),this}isErrorResponse(t){return!!t&&"object"==typeof t&&"string"==typeof t.name&&"string"==typeof t["error-msg"]}normalizeValidationIssues(t,e){return null==t||!0===t?[]:!1===t?[{message:"Некорректные данные формы",source:e}]:"string"==typeof t?[{message:t,source:e}]:Array.isArray(t)?t.flatMap((t=>this.normalizeValidationIssues(t,e))):this.isErrorResponse(t)?[{field:t.name,rule:e,message:t["error-msg"],source:e}]:"object"==typeof t&&"message"in t&&"string"==typeof t.message?[Object.assign(Object.assign({},t),{source:t.source||e})]:"object"==typeof t?Object.entries(t).map((([t,r])=>({field:t,rule:e,message:r,source:e}))):[]}applyValidationIssue(t,e){const r=t.source||e,i=t.rule||r,n=t.field||S;if(n===S)return this.setErrorRecord(S,i,t.message,r),this.showErrorForm(t.message),null;const o=this.findFieldByName(n);return o?(this.showFieldError(o,t.message,r,i),o):(this.setErrorRecord(n,i,t.message,r),console.warn(`Не найдено поле с именем: ${n}, для вывода ошибки: ${t.message}`),null)}setErrors(t,e="server"){return this.normalizeValidationIssues(t,e).forEach((t=>{this.applyValidationIssue(t,e)})),this}validateFormValidators(){return e(this,void 0,void 0,(function*(){const t=this.config.formValidators?Array.isArray(this.config.formValidators)?this.config.formValidators:[this.config.formValidators]:[],e=[];let r=!0;for(const i of t){const t=yield i({form:this,formElement:this.$el,values:this.getValues(),errors:this.getErrors()}),n=this.normalizeValidationIssues(t,"client");0!==n.length&&(r=!1,n.forEach((t=>{const r=this.applyValidationIssue(t,"client");r&&e.push(r)})))}return{ok:r,erroredInputs:e}}))}reset(t={}){return this.$el.reset(),this.refreshControls(),!1!==t.clearErrors&&this.clearErrors(),this}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}refreshControls(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector)}findSubmitElements(){return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'))}bindSubmitHandlers(){this._onSubmitClickHandler||(this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)}))),this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this.$submits=this.findSubmitElements(),this.$submits.forEach((t=>{t.addEventListener("click",this._onSubmitClickHandler)}))}initialization(){this.refreshControls(),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)})),this.$el.addEventListener("submit",this._onSubmitHandler)}handleSubmitEvent(t){return e(this,void 0,void 0,(function*(){var e,r,i,n,o,s;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const a=yield this.validate();return null===(n=(i=this.config).onAfterValidate)||void 0===n||n.call(i,a,this),a?this.submit():(null===(s=(o=this.config).onValidationError)||void 0===s||s.call(o,this.getErrors(),this),{success:!1,error:this.getErrors()})}))}normalizeTriggers(t,e){const r=null==t?e:Array.isArray(t)?t:[t];return Array.from(new Set(r))}clearFieldValidationEvents(){this.fieldEventHandlers.forEach((({$input:t,event:e,handler:r})=>{t.removeEventListener(e,r)})),this.fieldEventHandlers=[]}bindFieldValidationEvents(){this.clearFieldValidationEvents();const t=this.normalizeTriggers(this.config.validateOn,["submit"]).filter((t=>"submit"!==t)),e=this.normalizeTriggers(this.config.revalidateOn,["input","change"]).filter((t=>"submit"!==t)),r=Array.from(new Set([...t,...e]));if(0===r.length)return;this.getAllFields().forEach((e=>{r.forEach((r=>{const i=()=>{(t.includes(r)||this.hasFieldError(e))&&this.scheduleFieldValidation(e)};e.addEventListener(r,i),this.fieldEventHandlers.push({$input:e,event:r,handler:i})}))}))}scheduleFieldValidation(t){const e=this.liveValidationTimers.get(t);e&&clearTimeout(e);const r=Math.max(0,Number(this.config.validationDebounce)||0),i=()=>{this.validateField(t)};if(0===r)return void i();const n=setTimeout(i,r);this.liveValidationTimers.set(t,n)}setupMutationObserver(){var t;null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0,this.config.observeMutations&&"undefined"!=typeof MutationObserver&&(this.mutationObserver=new MutationObserver((()=>{this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents()})),this.mutationObserver.observe(this.$el,{childList:!0,subtree:!0}))}getErrorContainer(t){const e=this.config.errorContainerAttribute||"data-error-container",r=t.getAttribute(e);if(!r)return null;try{return document.querySelector(r)}catch(t){return console.warn(`[FormFather] Invalid error container selector "${r}"`),null}}showError(t,e){t.setAttribute("aria-invalid","true");const i=this.getErrorContainer(t);if(i)return i.textContent=e,i.hasAttribute("role")||i.setAttribute("role","alert"),void i.removeAttribute("hidden");const n=r(t,this.config.inputWrapperSelector);if(!n)return void console.warn(`Не найдена обёртка для поля с именем: ${t.name||t.type}`);const o=n.showError;if("function"==typeof o)return void o.call(n,e);n.classList.add("input__wrapper--error");let s=n.querySelector("[data-form-father-error]");s||(s=document.createElement("div"),s.setAttribute("data-form-father-error",""),s.setAttribute("role","alert"),t.insertAdjacentElement("afterend",s)),s.textContent=e}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+$++,r=`\n\t\t\t\t<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t<defs>\n\t\t\t\t\t<linearGradient id="${e}" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t\t</linearGradient>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#${e})"></path>\n\t\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t\t</g>\n\t\t\t\t\t</g>\n\t\t\t\t</svg>\n\t\t\t`,i=document.createElement("span");i.innerHTML=r,i.className="button__loader",t.insertAdjacentElement("afterbegin",i),requestAnimationFrame((()=>{i.setAttribute("data-showed","")}))}))}hideLoader(){this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&(e.classList.remove("active"),e.removeAttribute("data-showed"),setTimeout((()=>{e.remove()}),250))}))}hideError(t){var e;const i=r(t,this.config.inputWrapperSelector)||t.parentElement,n=this.getErrorContainer(t);t.removeAttribute("aria-invalid"),n&&(n.textContent="",n.setAttribute("hidden","")),null===(e=null==i?void 0:i.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==i||i.classList.remove("input__wrapper--error"),i&&i.classList.contains("input__wrapper")&&i.getAttribute("data-type-error")}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input, textarea, select"));let r=null,i=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<i&&(i=n,r=t)})),r){const t=r.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,i=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:i,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}focusFirstErroredInput(t){var e;if(0===t.length)return;const r=Array.from(this.$el.querySelectorAll("input, textarea, select")),i=t.slice().sort(((t,e)=>r.indexOf(t)-r.indexOf(e)))[0];null===(e=null==i?void 0:i.focus)||void 0===e||e.call(i,{preventScroll:!0})}getValidationSchema(){return Object.assign(Object.assign({},_.defaultValidationSchema||{}),this.config.validationSchema||{})}getAllFields(t=this.$el){return Array.from(t.querySelectorAll("input, textarea, select"))}getFieldKey(t){return t.name||t.id||t.getAttribute("data-field")||t.type||"field"}hasFieldError(t){const e=this.getFieldKey(t);return"true"===t.getAttribute("aria-invalid")||this.errors.some((t=>t.field===e))}clearFieldErrorRecord(t){const e=this.getFieldKey(t);this.errors=this.errors.filter((t=>t.field!==e))}setErrorRecord(t,e,r,i){this.errors.push({field:t,rule:e,message:r,source:i})}getValidationStateAttribute(){return this.config.validationStateAttribute||"data-form-father-state"}setFieldValidationState(t,e){const r=this.getValidationStateAttribute();e?t.setAttribute(r,e):t.removeAttribute(r)}startFieldValidation(t){const e=++this.fieldValidationTokenCounter;return this.fieldValidationTokens.set(t,e),t.setAttribute("aria-busy","true"),this.setFieldValidationState(t,"validating"),e}isLatestFieldValidation(t,e){return null==e||this.fieldValidationTokens.get(t)===e}finishFieldValidation(t,e,r){this.isLatestFieldValidation(t,e)&&(t.removeAttribute("aria-busy"),this.setFieldValidationState(t,r?"valid":"invalid"))}clearFieldValidationState(t){this.fieldValidationTokens.delete(t),t.removeAttribute("aria-busy"),this.setFieldValidationState(t,null)}setValidationError(t,e,r,i){this.clearFieldErrorRecord(t),this.setErrorRecord(this.getFieldKey(t),e,r,i)}resolveField(t){return"string"!=typeof t?t:this.findFieldByName(t)}parseRuleString(t){const[e,...r]=t.split(":").map((t=>t.trim()));return r.length>0?{rule:e,params:1===r.length?r[0]:r}:e}parseValidationAttribute(t){return(null!=t?t:"").split(/[,\s|]+/).map((t=>t.trim())).filter(Boolean).map((t=>this.parseRuleString(t)))}getRuleName(t){return"string"==typeof t?t:t.rule}getRuleIdentity(t){return JSON.stringify(t)}collectRules(t,e=[],r=new Set){const i=[];t.hasAttribute("required")&&i.push("required"),i.push(...e);const n=this.parseValidationAttribute(t.getAttribute("data-validate")).filter((t=>{const e=this.getRuleName(t);return f(e)||!r.has(e)}));i.push(...n),i.push(...this.parseValidationAttribute(t.getAttribute("data-custom-validate")));const o=[],s=new Set;for(const t of i){const e=this.getRuleName(t);if(!f(e)){console.warn(`[FormFather] Unknown validation rule "${e}"`);continue}const r=this.getRuleIdentity(t);s.has(r)||(s.add(r),o.push(t))}return o}getRuleMessage(t,e,r,i,n){const o=t.getAttribute(`data-error-${e}`);return n||o||r[e]||i}fieldMatchesSchemaDefinition(t,e,r){if(r.selector)try{return t.matches(r.selector)}catch(t){return console.warn(`[FormFather] Invalid validation selector "${r.selector}"`),!1}return this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===e))}getFieldValidationConfig(t,e=this.getValidationSchema()){const r=[],i={};for(const[n,o]of Object.entries(e))this.fieldMatchesSchemaDefinition(t,n,o)&&(r.push(...o.rules),Object.assign(i,o.messages||{}));return{rules:this.collectRules(t,r,new Set(Object.keys(e))),messages:i}}getValidationFields(t,e){const r=new Set,i=t=>{Array.from(t).forEach((t=>{(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&r.add(t)}))};for(const[n,o]of Object.entries(e))if(o.selector)try{i(t.querySelectorAll(o.selector))}catch(t){console.warn(`[FormFather] Invalid validation selector "${o.selector}"`)}else this.getAllFields(t).filter((t=>this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===n)))).forEach((t=>r.add(t)));return i(t.querySelectorAll("[required], [data-validate], [data-custom-validate]")),Array.from(r)}validateInput(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el,n){if(t.hasAttribute("data-no-validate"))return!0;const o=t instanceof HTMLInputElement&&("checkbox"===t.type||"radio"===t.type)?!t.checked:0===t.value.trim().length,s=e.some((t=>"required"===this.getRuleName(t)));if(o&&!s)return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t);for(const o of e){const{rule:e,params:s}="string"==typeof o?{rule:o,params:void 0}:{rule:o.rule,params:o.params},a=f(e);if(!a)continue;const l=yield a.fn(t.value,t,i,s),d="object"==typeof l&&null!==l,c=d?!0===l.valid:!!l;if(!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(d&&l.effect&&(yield l.effect({value:t.value,$input:t,$form:i,params:s})),!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(!c){const i=l,n=this.getRuleMessage(t,e,r,a.defaultMessage,d&&"string"==typeof i.message?i.message:void 0);return this.setValidationError(t,e,n,"client"),this.showError(t,n),!1}if(d&&l.stopOthers)return!0}return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t)}))}runFieldValidation(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el){const n=this.startFieldValidation(t);try{const o=yield this.validateInput(t,e,r,i,n);return this.finishFieldValidation(t,n,o),o}catch(e){throw this.isLatestFieldValidation(t,n)&&this.clearFieldValidationState(t),e}}))}getRadioGroup(t,e){return t instanceof HTMLInputElement&&"radio"===t.type&&t.name?Array.from(e.querySelectorAll('input[type="radio"]')).filter((e=>e.name===t.name)):[]}showFieldError(t,e,r="manual",i=r){const n=this.resolveField(t);return n?(this.setValidationError(n,i,e,r),this.showError(n,e),n.removeAttribute("aria-busy"),this.setFieldValidationState(n,"invalid"),!0):(console.warn(`[FormFather] Field "${String(t)}" not found`),!1)}validateField(t){return e(this,void 0,void 0,(function*(){const e=this.resolveField(t);if(!e)return console.warn(`[FormFather] Field "${String(t)}" not found`),!1;const{rules:r,messages:i}=this.getFieldValidationConfig(e);return this.runFieldValidation(e,r,i)}))}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const e=this.getValidationSchema(),r=[],i=new WeakSet;let n=!0;this.errors=[];for(const o of this.getValidationFields(t,e)){if(i.has(o))continue;const s=this.getRadioGroup(o,t);s.length>0?s.forEach((t=>i.add(t))):i.add(o);const{rules:a,messages:l}=this.getFieldValidationConfig(o,e);(yield this.runFieldValidation(o,a,l,t))||(n=!1,r.push(o))}const o=yield this.validateFormValidators();o.ok||(n=!1,r.push(...o.erroredInputs));const s=r.filter((t=>!t.hasAttribute("data-no-error-scroll")));return!n&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(s),!n&&this.config.focusFirstErroredInput&&this.focusFirstErroredInput(s),n}))}disableSubmit(){this.$submits.forEach((t=>{t.setAttribute("disabled","")}))}enableSubmit(){this.$submits.forEach((t=>{t.removeAttribute("disabled")}))}clearInputs(){var t;null===(t=this.inputs)||void 0===t||t.forEach((t=>{var e;const r=t;"radio"===r.type||"checkbox"===r.type?r.checked=!1:(r.value="",null===(e=r.parentElement)||void 0===e||e.classList.remove("filled"),r.dispatchEvent(new Event("input")))}))}_prefersReducedMotion(){var t;return"undefined"!=typeof window&&(null===(t=window.matchMedia)||void 0===t?void 0:t.call(window,"(prefers-reduced-motion: reduce)").matches)}_canAnimate(t){return!(!t||!("animate"in t)||this._prefersReducedMotion())}_cancelAnimations(t){var e;const r=t;null===(e=null==r?void 0:r.getAnimations)||void 0===e||e.call(r).forEach((t=>t.cancel()))}showErrorForm(t){var e;const r=this.$el.querySelectorAll(this.config.inputWrapperSelector),i=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||i&&i.nextElementSibling!==n?(n=document.createElement("div"),n.className="error-block-under-input__wrapper",n.innerHTML='\n <div class="error-block-under-input error-block-under-input--warning" style="display:grid;grid-template-columns:auto 1fr;gap:8px;align-items:start;">\n <div class="error-block-under-input__icon-wrapper" style="position:relative;width:20px;height:20px;">\n <span class="error-block-under-input__icon" style="display:block;width:20px;height:20px;"></span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:block;position:absolute;inset:0;"></span>\n </div>\n <p class="error-block-under-input__text" style="margin:0;">\n <span class="error-block-under-input__main-text"></span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>\n ',Object.assign(n.style,{boxSizing:"border-box",overflow:"hidden",height:"0px",paddingTop:"0px",opacity:"0"}),i?i.insertAdjacentElement("afterend",n):this.$el.appendChild(n)):(n.style.boxSizing="border-box",n.style.overflow="hidden"),n.querySelector(".error-block-under-input__main-text").textContent=t;const o=n.querySelector(".error-block-under-input__secondary-text");o&&(o.textContent=""),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const s=n.style.height,a=n.style.paddingTop;n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`;const l=n.clientHeight;if(n.style.height=s||"0px",n.style.paddingTop=a||"0px",parseFloat(n.style.height)===l&&1===parseFloat(n.style.opacity||"1"))this._ringIcon(n);else{if(this._cancelAnimations(n),this._canAnimate(n)){n.animate([{height:`${parseFloat(n.style.height)||0}px`,paddingTop:`${parseFloat(n.style.paddingTop)||0}px`,opacity:parseFloat(n.style.opacity||"0")},{height:`${l}px`,paddingTop:`${this._anim.paddingTopOpened}px`,opacity:1}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1"}))}else n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1";this._ringIcon(n)}this.$el.addEventListener("input",(()=>this.hideErrorForm()),{passive:!0,once:!0})}hideErrorForm(){const t=this.$el.querySelectorAll(this.config.inputWrapperSelector),e=t[t.length-1],r=(null==e?void 0:e.nextElementSibling)||this.$el.querySelector(".error-block-under-input__wrapper");if(!r||!r.classList.contains("error-block-under-input__wrapper"))return;if((""===r.style.height||"auto"===r.style.height)&&(r.style.height=`${r.clientHeight}px`),r.style.paddingTop=r.style.paddingTop||`${this._anim.paddingTopOpened}px`,r.style.opacity=r.style.opacity||"1",this._cancelAnimations(r),this._canAnimate(r)){r.animate([{height:`${parseFloat(r.style.height)||r.clientHeight}px`,paddingTop:`${parseFloat(r.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(r.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{r.remove()}))}else r.remove()}_ringIcon(t){const e=t.querySelector(".error-block-under-input__icon--animated");e&&(this._cancelAnimations(e),this._canAnimate(e)&&e.animate([{transform:"rotate(0deg) scale(1)",opacity:0},{transform:"rotate(-8deg) scale(1.05)",opacity:1,offset:.15},{transform:"rotate(8deg) scale(1.05)",opacity:1,offset:.3},{transform:"rotate(-6deg) scale(1.03)",opacity:1,offset:.45},{transform:"rotate(6deg) scale(1.03)",opacity:1,offset:.6},{transform:"rotate(0deg) scale(1)",opacity:0}],{duration:this._anim.iconDuration,easing:"ease"}))}destroy(){var t;this._onSubmitHandler&&(this.$el.removeEventListener("submit",this._onSubmitHandler),this._onSubmitHandler=void 0),this._onSubmitClickHandler&&(this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this._onSubmitClickHandler=void 0),this.clearFieldValidationEvents(),null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0;const e=this.$el.querySelector(".error-block-under-input__wrapper");e&&(this._cancelAnimations(e),e.remove()),this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&e.remove()}));this.$el.querySelectorAll("input, textarea, select").forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),document.documentElement.removeAttribute("data-now-scrolling"),this.inputs=null,this.$submits=[],this.errors=[],this.waitResponse=!1,T.delete(this.$el)}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):0!==this.$submits.length||(console.warn("В форме нет кнопки с типом submit"),!1)}formDataToObject(t){const e={};return t.forEach(((t,r)=>{Object.prototype.hasOwnProperty.call(e,r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}appendFormDataValue(t,e,r){null!=r&&(Array.isArray(r)?r.forEach(((r,i)=>{this.appendFormDataValue(t,`${e}[${i}]`,r)})):r instanceof Blob?t.append(e,r):t.append(e,"object"==typeof r?JSON.stringify(r):String(r)))}objectToFormData(t){const e=new FormData;return Object.entries(t).forEach((([t,r])=>{this.appendFormDataValue(e,t,r)})),e}stringifyFormDataValue(t){return"string"==typeof t?t:t.name}formDataToUrlSearchParams(t){const e=new URLSearchParams;return t.forEach(((t,r)=>{e.append(r,this.stringifyFormDataValue(t))})),e}formDataToTextBody(t){return Array.from(t).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(this.stringifyFormDataValue(e))}`)).join("\n")}isBodylessMethod(t){return"GET"===t||"HEAD"===t}appendQueryString(t,e){const r=e.toString();if(!r)return t;const i=t.includes("?")?"&":"?";return`${t}${i}${r}`}findFieldByName(t){return Array.from(this.$el.querySelectorAll("input, textarea, select")).find((e=>e.name===t))||null}parseResponseBody(t){return e(this,void 0,void 0,(function*(){try{const e=yield t.json();return e&&"object"==typeof e?e:{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}catch(t){return{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}}))}handleUnsuccessfulResponse(t){if(!0===t.error&&(t["error-msg"]&&this.showErrorForm(t["error-msg"]),t.errors)){const e=[];t.errors.forEach((t=>{const r=this.findFieldByName(t.name);r?(e.push(r),this.setValidationError(r,"server",t["error-msg"],"server"),this.showError(r,t["error-msg"]),r.removeAttribute("aria-busy"),this.setFieldValidationState(r,"invalid")):console.warn(`Не найдено поле с именем: ${t.name}, для вывода ошибки: ${t["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(e),!0===this.config.focusFirstErroredInput&&this.focusFirstErroredInput(e)}}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",r=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",i=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,o={};const s=a(this.$el);let l=this.formDataToObject(s);if(this.config.logging)for(const t of s.keys())console.log(s.get(t));const d="function"==typeof this.config.wrapData;"function"==typeof this.config.wrapData&&(l=this.config.wrapData(l));const c=d?this.objectToFormData(l):s;if(this.isBodylessMethod(r))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(c)),{method:r,headers:o});switch(i){case"application/x-www-form-urlencoded":n=this.formDataToUrlSearchParams(c).toString(),o["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=c;break;case"text/plain":n=this.formDataToTextBody(c),o["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),o["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${i}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(c).toString(),o["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:r,body:n,headers:o})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,r,i,n,o,s,a,d,c,u,h;if(!this.waitResponse){let p=!1;const f=()=>{p||(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),p=!0)};try{if(!1===(yield null===(e=(t=this.config).onBeforeSubmit)||void 0===e?void 0:e.call(t,this)))return{success:!1};!0===this.config.showLoaderButton&&this.showLoader(),null===(i=(r=this.config).onSubmit)||void 0===i||i.call(r,this),this.waitResponse=!0;const u=yield this.sendData(),h=yield this.parseResponseBody(u);return f(),null===(o=(n=this.config).onResponse)||void 0===o||o.call(n,h,this),u.ok&&!0===h.success?null===(a=(s=this.config).onResponseSuccess)||void 0===a||a.call(s,h,this):(null===(c=(d=this.config).onResponseUnsuccess)||void 0===c||c.call(d,h,this),this.handleUnsuccessfulResponse(h)),l(h),{success:u.ok&&!0===h.success,response:u,responseBody:h}}catch(t){throw f(),null===(h=(u=this.config).onSubmitError)||void 0===h||h.call(u,t,this),t}}}))}}return t.FORM_ERROR_FIELD=S,t.blockScrollBody=function(){window.lastBlockScrollPosition=window.pageYOffset,document.body.style.top=-window.lastBlockScrollPosition+"px",document.body.classList.add("body--block-scroll")},t.closest=r,t.createFieldValidator=v,t.createFormValidator=V,t.createLengthValidator=function(t,e){return v((e=>{const r=!1===t.trim?e:e.trim();return 0===r.length&&!0===t.emptyIsValid||!("number"==typeof t.min&&r.length<t.min)&&!("number"==typeof t.max&&r.length>t.max)}),e)},t.createPatternValidator=function(t,e){return v((e=>(t.lastIndex=0,t.test(e))),e)},t.createSchemaValidator=y,t.dateOrder=function(t,e,r="Дата окончания должна быть позже даты начала",i="date-order"){return V((({values:r})=>{const i=F(r,t),n=F(r,e);return!(!A(i)&&!A(n))||Date.parse(String(i))<=Date.parse(String(n))}),{field:e,rule:i,message:r})},t.default=_,t.getAllValidators=function(){return new Map(h)},t.getValidator=f,t.isEmailValid=function(t){return/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(t)},t.isPhoneValid=function(t){const e=[];return Array.from(t).forEach((t=>{(Number(t)||0===Number(t))&&e.push(t)})),!(e.length<11||e.length<12&&"+"===t[1])},t.isUrlValid=s,t.parseCommonResponseProperties=l,t.registerFieldValidator=function(t,e,r,i={}){p(t,v(e),r,i)},t.registerSchemaValidator=function(t,e,r,i={}){const{override:n}=i;p(t,y(e,function(t,e){var r={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(r[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(i=Object.getOwnPropertySymbols(t);n<i.length;n++)e.indexOf(i[n])<0&&Object.prototype.propertyIsEnumerable.call(t,i[n])&&(r[i[n]]=t[i[n]])}return r}(i,["override"])),r,{override:n})},t.registerValidator=p,t.requiredIf=function(t,r,i="Поле обязательно",n="conditional-required"){return V((i=>e(this,void 0,void 0,(function*(){return!(yield r(i))||!A(F(i.values,t))}))),{field:t,rule:n,message:i})},t.sameAsField=function(t,e,r="Значения не совпадают",i="same-as-field"){return V((({values:r})=>F(r,t)===F(r,e)),{field:t,rule:i,message:r})},t.serializeFormToJSON=d,t.serializeToFormData=a,t.unblockScrollBody=function(){document.body.classList.remove("body--block-scroll"),document.body.style.top="",window.scrollTo(0,window.lastBlockScrollPosition||0)},Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
2
2
|
//# sourceMappingURL=FormFather.min.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -48,9 +48,21 @@ interface SchemaValidatorOptions {
|
|
|
48
48
|
interface RegisterSchemaValidatorOptions extends SchemaValidatorOptions {
|
|
49
49
|
override?: boolean;
|
|
50
50
|
}
|
|
51
|
+
interface RegisterFieldValidatorOptions {
|
|
52
|
+
override?: boolean;
|
|
53
|
+
}
|
|
54
|
+
interface LengthValidatorOptions {
|
|
55
|
+
min?: number;
|
|
56
|
+
max?: number;
|
|
57
|
+
trim?: boolean;
|
|
58
|
+
emptyIsValid?: boolean;
|
|
59
|
+
}
|
|
51
60
|
declare function createSchemaValidator(schema: SchemaLike, options?: SchemaValidatorOptions): AdapterValidator;
|
|
52
61
|
declare function registerSchemaValidator(name: string, schema: SchemaLike, defaultMessage: string, options?: RegisterSchemaValidatorOptions): void;
|
|
62
|
+
declare function registerFieldValidator(name: string, predicate: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean>, defaultMessage: string, options?: RegisterFieldValidatorOptions): void;
|
|
53
63
|
declare function createFieldValidator(predicate: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean>, message?: string): AdapterValidator;
|
|
64
|
+
declare function createPatternValidator(pattern: RegExp, message?: string): AdapterValidator;
|
|
65
|
+
declare function createLengthValidator(options: LengthValidatorOptions, message?: string): AdapterValidator;
|
|
54
66
|
|
|
55
67
|
/**
|
|
56
68
|
* Находит родительский элемент по заданому селектору.
|
|
@@ -152,11 +164,17 @@ interface FormValidatorContext {
|
|
|
152
164
|
}
|
|
153
165
|
type FormValidatorResult = boolean | string | void | ValidationIssue | Array<string | ValidationIssue>;
|
|
154
166
|
type FormValidator = (context: FormValidatorContext) => FormValidatorResult | Promise<FormValidatorResult>;
|
|
167
|
+
type FormValidatorPredicate = (context: FormValidatorContext) => boolean | Promise<boolean>;
|
|
168
|
+
type FormValidatorIssueFactory = string | ValidationIssue | ((context: FormValidatorContext) => string | ValidationIssue | Promise<string | ValidationIssue>);
|
|
155
169
|
type ValidationSchema = Record<string, {
|
|
156
170
|
rules: ValidationRule[];
|
|
157
171
|
selector?: string;
|
|
158
172
|
messages?: Record<string, string>;
|
|
159
173
|
}>;
|
|
174
|
+
declare function createFormValidator(predicate: FormValidatorPredicate, issue: FormValidatorIssueFactory): FormValidator;
|
|
175
|
+
declare function sameAsField(field: string, otherField: string, message?: string, rule?: string): FormValidator;
|
|
176
|
+
declare function requiredIf(field: string, condition: FormValidatorPredicate, message?: string, rule?: string): FormValidator;
|
|
177
|
+
declare function dateOrder(startField: string, endField: string, message?: string, rule?: string): FormValidator;
|
|
160
178
|
type FormFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
161
179
|
/** Параметры формы. */
|
|
162
180
|
interface FormOptions {
|
|
@@ -462,4 +480,4 @@ declare class Form {
|
|
|
462
480
|
submit(): Promise<SubmitResult | undefined>;
|
|
463
481
|
}
|
|
464
482
|
|
|
465
|
-
export { type AdapterValidator, type ErrorResponse, FORM_ERROR_FIELD, type FieldReference, type FormFieldElement, type FormOptions, type FormResetOptions, type FormValidator, type FormValidatorContext, type FormValidatorResult, type RegisterSchemaValidatorOptions, type ResponseBody, type SchemaLike, type SchemaParseResult, type SchemaValidatorOptions, type SubmitResult, type ValidationError, type ValidationErrorSource, type ValidationIssue, type ValidationIssueInput, type ValidationRule, type ValidationSchema, type ValidationTrigger, type ValidatorEffectCtx, type ValidatorFieldElement, type ValidatorFn, type ValidatorResult, blockScrollBody, closest, createFieldValidator, createSchemaValidator, Form as default, getAllValidators, getValidator, isEmailValid, isPhoneValid, isUrlValid, parseCommonResponseProperties, registerSchemaValidator, registerValidator, serializeFormToJSON, serializeToFormData, unblockScrollBody };
|
|
483
|
+
export { type AdapterValidator, type ErrorResponse, FORM_ERROR_FIELD, type FieldReference, type FormFieldElement, type FormOptions, type FormResetOptions, type FormValidator, type FormValidatorContext, type FormValidatorIssueFactory, type FormValidatorPredicate, type FormValidatorResult, type LengthValidatorOptions, type RegisterFieldValidatorOptions, type RegisterSchemaValidatorOptions, type ResponseBody, type SchemaLike, type SchemaParseResult, type SchemaValidatorOptions, type SubmitResult, type ValidationError, type ValidationErrorSource, type ValidationIssue, type ValidationIssueInput, type ValidationRule, type ValidationSchema, type ValidationTrigger, type ValidatorEffectCtx, type ValidatorFieldElement, type ValidatorFn, type ValidatorResult, 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 };
|
package/dist/index.js
CHANGED
|
@@ -427,12 +427,33 @@ function registerSchemaValidator(name, schema, defaultMessage, options = {}) {
|
|
|
427
427
|
const { override } = options, adapterOptions = __rest(options, ["override"]);
|
|
428
428
|
registerValidator(name, createSchemaValidator(schema, adapterOptions), defaultMessage, { override });
|
|
429
429
|
}
|
|
430
|
+
function registerFieldValidator(name, predicate, defaultMessage, options = {}) {
|
|
431
|
+
registerValidator(name, createFieldValidator(predicate), defaultMessage, options);
|
|
432
|
+
}
|
|
430
433
|
function createFieldValidator(predicate, message) {
|
|
431
434
|
return (value, $input, $form, params) => __awaiter(this, void 0, void 0, function* () {
|
|
432
435
|
const valid = yield predicate(value, $input, $form, params);
|
|
433
436
|
return valid ? true : { valid: false, message };
|
|
434
437
|
});
|
|
435
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
|
+
}
|
|
436
457
|
|
|
437
458
|
var _a;
|
|
438
459
|
// --- shared cross-file state (singleton via globalThis) ---
|
|
@@ -446,6 +467,48 @@ const __shared =
|
|
|
446
467
|
defaultValidationSchema: undefined,
|
|
447
468
|
});
|
|
448
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
|
+
}
|
|
449
512
|
let loaderIdCounter = 0;
|
|
450
513
|
const FORM_INSTANCES = new WeakMap();
|
|
451
514
|
/** Изначальная дефолтная схема. */
|
|
@@ -1818,5 +1881,5 @@ class Form {
|
|
|
1818
1881
|
}
|
|
1819
1882
|
}
|
|
1820
1883
|
|
|
1821
|
-
export { FORM_ERROR_FIELD, blockScrollBody, closest, createFieldValidator, createSchemaValidator, Form as default, getAllValidators, getValidator, isEmailValid, isPhoneValid, isUrlValid, parseCommonResponseProperties, registerSchemaValidator, registerValidator, serializeFormToJSON, serializeToFormData, unblockScrollBody };
|
|
1884
|
+
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 };
|
|
1822
1885
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/types/adapters.d.ts
CHANGED
|
@@ -22,7 +22,19 @@ export interface SchemaValidatorOptions {
|
|
|
22
22
|
export interface RegisterSchemaValidatorOptions extends SchemaValidatorOptions {
|
|
23
23
|
override?: boolean;
|
|
24
24
|
}
|
|
25
|
+
export interface RegisterFieldValidatorOptions {
|
|
26
|
+
override?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface LengthValidatorOptions {
|
|
29
|
+
min?: number;
|
|
30
|
+
max?: number;
|
|
31
|
+
trim?: boolean;
|
|
32
|
+
emptyIsValid?: boolean;
|
|
33
|
+
}
|
|
25
34
|
export declare function createSchemaValidator(schema: SchemaLike, options?: SchemaValidatorOptions): AdapterValidator;
|
|
26
35
|
export declare function registerSchemaValidator(name: string, schema: SchemaLike, defaultMessage: string, options?: RegisterSchemaValidatorOptions): void;
|
|
36
|
+
export declare function registerFieldValidator(name: string, predicate: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean>, defaultMessage: string, options?: RegisterFieldValidatorOptions): void;
|
|
27
37
|
export declare function createFieldValidator(predicate: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean>, message?: string): AdapterValidator;
|
|
38
|
+
export declare function createPatternValidator(pattern: RegExp, message?: string): AdapterValidator;
|
|
39
|
+
export declare function createLengthValidator(options: LengthValidatorOptions, message?: string): AdapterValidator;
|
|
28
40
|
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -39,11 +39,17 @@ export interface FormValidatorContext {
|
|
|
39
39
|
}
|
|
40
40
|
export type FormValidatorResult = boolean | string | void | ValidationIssue | Array<string | ValidationIssue>;
|
|
41
41
|
export type FormValidator = (context: FormValidatorContext) => FormValidatorResult | Promise<FormValidatorResult>;
|
|
42
|
+
export type FormValidatorPredicate = (context: FormValidatorContext) => boolean | Promise<boolean>;
|
|
43
|
+
export type FormValidatorIssueFactory = string | ValidationIssue | ((context: FormValidatorContext) => string | ValidationIssue | Promise<string | ValidationIssue>);
|
|
42
44
|
export type ValidationSchema = Record<string, {
|
|
43
45
|
rules: ValidationRule[];
|
|
44
46
|
selector?: string;
|
|
45
47
|
messages?: Record<string, string>;
|
|
46
48
|
}>;
|
|
49
|
+
export declare function createFormValidator(predicate: FormValidatorPredicate, issue: FormValidatorIssueFactory): FormValidator;
|
|
50
|
+
export declare function sameAsField(field: string, otherField: string, message?: string, rule?: string): FormValidator;
|
|
51
|
+
export declare function requiredIf(field: string, condition: FormValidatorPredicate, message?: string, rule?: string): FormValidator;
|
|
52
|
+
export declare function dateOrder(startField: string, endField: string, message?: string, rule?: string): FormValidator;
|
|
47
53
|
export type FormFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
48
54
|
/** Параметры формы. */
|
|
49
55
|
export interface FormOptions {
|
package/docs/en/README.md
CHANGED
|
@@ -59,6 +59,7 @@ import Form, {
|
|
|
59
59
|
type FormResetOptions,
|
|
60
60
|
type FormValidator,
|
|
61
61
|
type ValidationIssue,
|
|
62
|
+
type FormValidatorPredicate,
|
|
62
63
|
} from 'form-father';
|
|
63
64
|
```
|
|
64
65
|
|
|
@@ -145,17 +146,13 @@ required → schema.rules → data-validate → data-custom-validate
|
|
|
145
146
|
### Cross-field and form-level validation
|
|
146
147
|
|
|
147
148
|
```ts
|
|
149
|
+
import { dateOrder, requiredIf, sameAsField } from 'form-father';
|
|
150
|
+
|
|
148
151
|
const form = new Form($form, {
|
|
149
152
|
formValidators: [
|
|
150
|
-
(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
: {
|
|
154
|
-
field: 'passwordConfirm',
|
|
155
|
-
rule: 'same-as-password',
|
|
156
|
-
message: 'Passwords do not match',
|
|
157
|
-
},
|
|
158
|
-
({ values }) => (values.start <= values.end ? true : { field: 'end', message: 'End date must be after start' }),
|
|
153
|
+
sameAsField('passwordConfirm', 'password', 'Passwords do not match'),
|
|
154
|
+
requiredIf('email', ({ values }) => Boolean(values.subscribe), 'Email is required for subscription'),
|
|
155
|
+
dateOrder('start', 'end', 'End date must be after start'),
|
|
159
156
|
],
|
|
160
157
|
});
|
|
161
158
|
```
|
|
@@ -170,6 +167,16 @@ form.setErrors({
|
|
|
170
167
|
});
|
|
171
168
|
```
|
|
172
169
|
|
|
170
|
+
### Rule DX helpers
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { createLengthValidator, createPatternValidator, registerFieldValidator } from 'form-father';
|
|
174
|
+
|
|
175
|
+
registerFieldValidator('starts-with-a', value => value.startsWith('A'), 'Must start with A');
|
|
176
|
+
registerValidator('slug', createPatternValidator(/^[a-z0-9-]+$/), 'Slug characters only');
|
|
177
|
+
registerValidator('short-name', createLengthValidator({ min: 2, max: 24 }), 'Use 2-24 characters');
|
|
178
|
+
```
|
|
179
|
+
|
|
173
180
|
## Schema Adapters
|
|
174
181
|
|
|
175
182
|
Form Father has no schema-library runtime dependency, but can wrap Zod/Valibot/Yup-like schemas with `safeParse()` or
|
package/docs/recipes/README.md
CHANGED
|
@@ -52,18 +52,27 @@ form.reset();
|
|
|
52
52
|
## Cross-field validation
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
+
import { dateOrder, requiredIf, sameAsField } from 'form-father';
|
|
56
|
+
|
|
55
57
|
const form = new Form(document.querySelector('form')!, {
|
|
56
|
-
formValidators:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
rule: 'same-as-password',
|
|
62
|
-
message: 'Passwords do not match',
|
|
63
|
-
},
|
|
58
|
+
formValidators: [
|
|
59
|
+
sameAsField('passwordConfirm', 'password', 'Passwords do not match'),
|
|
60
|
+
requiredIf('email', ({ values }) => Boolean(values.subscribe), 'Email is required'),
|
|
61
|
+
dateOrder('start', 'end', 'End date must be after start'),
|
|
62
|
+
],
|
|
64
63
|
});
|
|
65
64
|
```
|
|
66
65
|
|
|
66
|
+
## Field rule helpers
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { createLengthValidator, createPatternValidator, registerFieldValidator, registerValidator } from 'form-father';
|
|
70
|
+
|
|
71
|
+
registerFieldValidator('starts-with-a', value => value.startsWith('A'), 'Must start with A');
|
|
72
|
+
registerValidator('slug', createPatternValidator(/^[a-z0-9-]+$/), 'Slug characters only');
|
|
73
|
+
registerValidator('short-name', createLengthValidator({ min: 2, max: 24 }), 'Use 2-24 characters');
|
|
74
|
+
```
|
|
75
|
+
|
|
67
76
|
## Applying backend errors
|
|
68
77
|
|
|
69
78
|
```ts
|
package/package.json
CHANGED