form-father 0.2.10 → 0.3.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.
@@ -1,2 +1,2 @@
1
- var FormFather=function(t){"use strict";function e(t,e,r,o){return new(r||(r=Promise))((function(n,i){function s(t){try{l(o.next(t))}catch(t){i(t)}}function a(t){try{l(o.throw(t))}catch(t){i(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((o=o.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 o=["http:","https:"];function n(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function i(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 o.includes(t.protocol)&&i(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:o}=t;if("file"===o)if(t.multiple){const o=t.files;if(o)for(let t=0;t<o.length;t++)r.append(`${e}[${t}]`,o[t])}else{const o=t.files;o&&o[0]&&r.append(e,o[0])}else{let n;if("select-one"===o||"select-multiple"===o){const i=t;if("select-multiple"===o){Array.from(i.selectedOptions).map((t=>t.value)).forEach(((t,o)=>{r.append(`${e}[${o}]`,t)}))}else n=i.value,n&&r.append(e,n)}else if("tel"===o)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===o||"radio"===o){if(!t.checked)return;n=t.value}else if("date"===o){const e=t.getAttribute("data-date-format");n=e?function(t,e){const r=new Date(t),o=t=>t.toString().padStart(2,"0"),n=o(r.getDate()),i=o(r.getMonth()+1),s=r.getFullYear().toString();return e.replace("dd",n).replace("mm",i).replace("yyyy",s)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==o&&"select-multiple"!==o&&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"])}var c;const d=Symbol.for("FormFather.validators"),u=null!==(c=globalThis[d])&&void 0!==c?c:globalThis[d]=new Map;const p=t=>u.get(t);function h(t,e,r){u.has(t)||u.set(t,{fn:e,defaultMessage:r})}var f;h("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}),"Пустое значение"),h("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),h("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),h("url",(t=>s(t)),"Неверный формат"),h("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const m=Symbol.for("formfather.shared"),y=null!==(f=globalThis[m])&&void 0!==f?f:globalThis[m]={defaultParams:{},defaultValidationSchema:void 0};let g=0;const b={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 y.defaultParams}static set defaultParams(t){y.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=y.defaultValidationSchema)&&void 0!==t?t:y.defaultValidationSchema=b}static set defaultValidationSchema(t){y.defaultValidationSchema=t}constructor(t,r={}){this.$submits=[],this.waitResponse=!1,this.inputs=null,this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=r;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary"},v.defaultParams,r),this.$el?(this.$submits=this.findSubmitElements(),this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){var e,r,o,n;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const i=yield this.validate();null===(n=(o=this.config).onAfterValidate)||void 0===n||n.call(o,i,this),i&&(yield this.submit())})),this.isCorrectArguments()&&(this.$submits.forEach((t=>{t.addEventListener("click",this._onSubmitClickHandler)})),this.initialization())):console.warn("Empty $el")}getOptions(){return this.options}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}findSubmitElements(){return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'))}initialization(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){var e,r,o,n;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const i=yield this.validate();null===(n=(o=this.config).onAfterValidate)||void 0===n||n.call(o,i,this),i&&this.submit()})),this.$el.addEventListener("submit",this._onSubmitHandler)}showError(t,e){const o=r(t,this.config.inputWrapperSelector);if(!o)return void console.warn(`Не найдена обёртка для поля с именем: ${t.name||t.type}`);const n=o.showError;if("function"==typeof n)return void n.call(o,e);t.setAttribute("aria-invalid","true"),o.classList.add("input__wrapper--error");let i=o.querySelector("[data-form-father-error]");i||(i=document.createElement("div"),i.setAttribute("data-form-father-error",""),i.setAttribute("role","alert"),t.insertAdjacentElement("afterend",i)),i.textContent=e}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+g++,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`,o=document.createElement("span");o.innerHTML=r,o.className="button__loader",t.insertAdjacentElement("afterbegin",o),requestAnimationFrame((()=>{o.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 o=r(t,this.config.inputWrapperSelector)||t.parentElement;t.removeAttribute("aria-invalid"),null===(e=null==o?void 0:o.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==o||o.classList.remove("input__wrapper--error"),o&&o.classList.contains("input__wrapper")&&o.getAttribute("data-type-error")}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input"));let r=null,o=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<o&&(o=n,r=t)})),r){const t=r.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,o=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:o,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const r=Object.assign(Object.assign({},v.defaultValidationSchema||{}),this.config.validationSchema||{}),o=t=>console.warn(`[FormFather] Unknown validation rule "${t}"`),n=[],i=new WeakSet;let s=!0;const a=(t,e=[])=>{var r;const n=[];t.hasAttribute("required")&&n.push("required"),n.push(...e);const i=(null!==(r=t.getAttribute("data-custom-validate"))&&void 0!==r?r:"").split(",").map((t=>t.trim())).filter(Boolean);n.push(...i.map((t=>{const[e,...r]=t.split(":").map((t=>t.trim()));return r.length>0?{rule:e,params:1===r.length?r[0]:r}:e})));const s=[];for(const t of n){const e="string"==typeof t?t:t.rule;p(e)?s.push(t):o(e)}return s},l=(r,o,...i)=>e(this,[r,o,...i],void 0,(function*(e,r,o={}){if(e.hasAttribute("data-no-validate"))return;const i="checkbox"===e.type||"radio"===e.type?!e.checked:0===e.value.trim().length,a=r.some((t=>"string"==typeof t?"required"===t:"required"===t.rule));if(!i||a){for(const i of r){const{rule:r,params:a}="string"==typeof i?{rule:i,params:void 0}:i,l=p(r),c=yield l.fn(e.value,e,t,a),d="object"==typeof c&&null!==c,u=d?!0===c.valid:!!c;if(d&&c.effect&&(yield c.effect({value:e.value,$input:e,$form:t,params:a})),!u){s=!1;const t=c,i=d&&"string"==typeof t.message&&t.message||o[r]||l.defaultMessage;return this.showError(e,i),void n.push(e)}if(d&&c.stopOthers)return}this.hideError(e)}else this.hideError(e)})),c=e=>"radio"===e.type&&e.name?Array.from(t.querySelectorAll('input[type="radio"]')).filter((t=>t.name===e.name)):[];for(const[e,o]of Object.entries(r)){const{selector:r,rules:n=[],messages:s={}}=o,d=r?t.querySelectorAll(r):t.querySelectorAll(`[data-validate="${e}"]`);for(const t of Array.from(d)){if(i.has(t))continue;const e=c(t);e.length>0?(e.forEach((t=>i.add(t))),yield l(t,a(t,n),s)):(i.add(t),yield l(t,a(t,n),s))}}const d=Array.from(t.querySelectorAll("[data-custom-validate]")).filter((t=>!i.has(t)));for(const t of d)i.add(t),yield l(t,a(t,[]));return!s&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(n.filter((t=>!t.hasAttribute("data-no-error-scroll")))),s}))}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),o=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||o&&o.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"}),o?o.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 i=n.querySelector(".error-block-under-input__secondary-text");i&&(i.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(){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);const t=this.$el.querySelector(".error-block-under-input__wrapper");t&&(this._cancelAnimations(t),t.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))),document.documentElement.removeAttribute("data-now-scrolling"),this.inputs=null,this.$submits=[],this.waitResponse=!1}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,o)=>{this.appendFormDataValue(t,`${e}[${o}]`,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 o=t.includes("?")?"&":"?";return`${t}${o}${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.showError(r,t["error-msg"])):console.warn(`Не найдено поле с именем: ${t.name}, для вывода ошибки: ${t["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(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",o=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,i={};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 c="function"==typeof this.config.wrapData;"function"==typeof this.config.wrapData&&(l=this.config.wrapData(l));const d=c?this.objectToFormData(l):s;if(this.isBodylessMethod(r))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(d)),{method:r,headers:i});switch(o){case"application/x-www-form-urlencoded":n=this.formDataToUrlSearchParams(d).toString(),i["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=d;break;case"text/plain":n=this.formDataToTextBody(d),i["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),i["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${o}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(d).toString(),i["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:r,body:n,headers:i})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,r,o,n,i,s,a;if(!this.waitResponse){!0===this.config.showLoaderButton&&this.showLoader(),null===(e=(t=this.config).onSubmit)||void 0===e||e.call(t,this),this.waitResponse=!0;let c=!1;const d=()=>{c||(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),c=!0)};try{const t=yield this.sendData(),e=yield this.parseResponseBody(t);d(),null===(o=(r=this.config).onResponse)||void 0===o||o.call(r,e,this),t.ok&&!0===e.success?null===(i=(n=this.config).onResponseSuccess)||void 0===i||i.call(n,e,this):(null===(a=(s=this.config).onResponseUnsuccess)||void 0===a||a.call(s,e,this),this.handleUnsuccessfulResponse(e)),l(e)}catch(t){throw d(),t}}}))}}return 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.default=v,t.getAllValidators=function(){return new Map(u)},t.getValidator=p,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.registerValidator=function(t,e,r,{override:o=!1}={}){!u.has(t)||o?u.set(t,{fn:e,defaultMessage:r}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)},t.serializeFormToJSON=function(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},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,i,r){return new(i||(i=Promise))((function(n,o){function s(t){try{l(r.next(t))}catch(t){o(t)}}function a(t){try{l(r.throw(t))}catch(t){o(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(s,a)}l((r=r.apply(t,e||[])).next())}))}function i(t,e){if(null==t)return null;let i=t;for(;i;){if(i.matches(e))return i;i=i.parentElement}return null}"function"==typeof SuppressedError&&SuppressedError;const r=["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 r.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")),i=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:r}=t;if("file"===r)if(t.multiple){const r=t.files;if(r)for(let t=0;t<r.length;t++)i.append(`${e}[${t}]`,r[t])}else{const r=t.files;r&&r[0]&&i.append(e,r[0])}else{let n;if("select-one"===r||"select-multiple"===r){const o=t;if("select-multiple"===r){Array.from(o.selectedOptions).map((t=>t.value)).forEach(((t,r)=>{i.append(`${e}[${r}]`,t)}))}else n=o.value,n&&i.append(e,n)}else if("tel"===r)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===r||"radio"===r){if(!t.checked)return;n=t.value}else if("date"===r){const e=t.getAttribute("data-date-format");n=e?function(t,e){const i=new Date(t),r=t=>t.toString().padStart(2,"0"),n=r(i.getDate()),o=r(i.getMonth()+1),s=i.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"!==r&&"select-multiple"!==r&&i.append(e,n)}})),i}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,i)=>{e.hasOwnProperty(i)?(Array.isArray(e[i])||(e[i]=[e[i]]),e[i].push(t)):e[i]=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,i,{override:r=!1}={}){!h.has(t)||r?h.set(t,{fn:e,defaultMessage:i}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const f=t=>h.get(t);function m(t,e,i){h.has(t)||h.set(t,{fn:e,defaultMessage:i})}function g(t,e){return"function"==typeof e?e(t):e||function(t){var e;if(!t||"object"!=typeof t)return;const i=t.issues||t.errors;if(Array.isArray(i)){const t=null===(e=i.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,i={}){return(r,n,o,s)=>e(this,void 0,void 0,(function*(){const e=i.mapValue?i.mapValue(r,n,o,s):r;try{if("function"==typeof t)return a=yield t(e),l=g(void 0,i.message),"object"==typeof a&&null!==a?a:void 0===a||!!a||{valid:!1,message:l};if("function"==typeof t.safeParse){const r=yield t.safeParse(e);return!!r.success||{valid:!1,message:g(r.error||r,i.message)}}if("function"==typeof t.parse)return yield t.parse(e),!0}catch(t){return{valid:!1,message:g(t,i.message)}}var a,l;return{valid:!1,message:g(void 0,i.message)}}))}var b;m("required",((t,e,i)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=i.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 v=Symbol.for("formfather.shared"),E=null!==(b=globalThis[v])&&void 0!==b?b:globalThis[v]={defaultParams:{},defaultValidationSchema:void 0};let w=0;const S=new WeakMap,F={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 A{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=F}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},A.defaultParams,e),this.$el?(this.$submits=this.findSubmitElements(),this.isCorrectArguments()&&(this.bindSubmitHandlers(),this.initialization(),this.bindFieldValidationEvents(),this.setupMutationObserver(),S.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 i=S.get(t);return i?(i.updateOptions(e),i):new A(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 i=t[e.name];if(!(e instanceof HTMLInputElement&&"file"===e.type)){if(e instanceof HTMLInputElement&&"checkbox"===e.type)e.checked="boolean"==typeof i?i:this.normalizeValueList(i).includes(e.value);else if(e instanceof HTMLInputElement&&"radio"===e.type)e.checked=String(i)===e.value;else if(e instanceof HTMLSelectElement&&e.multiple){const t=this.normalizeValueList(i);Array.from(e.options).forEach((e=>{e.selected=t.includes(e.value)}))}else e.value=null==i?"":String(i);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}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,i,r,n,o,s;t.preventDefault(),null===(i=(e=this.config).onBeforeValidate)||void 0===i||i.call(e,this);const a=yield this.validate();return null===(n=(r=this.config).onAfterValidate)||void 0===n||n.call(r,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 i=null==t?e:Array.isArray(t)?t:[t];return Array.from(new Set(i))}clearFieldValidationEvents(){this.fieldEventHandlers.forEach((({$input:t,event:e,handler:i})=>{t.removeEventListener(e,i)})),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)),i=Array.from(new Set([...t,...e]));if(0===i.length)return;this.getAllFields().forEach((e=>{i.forEach((i=>{const r=()=>{(t.includes(i)||this.hasFieldError(e))&&this.scheduleFieldValidation(e)};e.addEventListener(i,r),this.fieldEventHandlers.push({$input:e,event:i,handler:r})}))}))}scheduleFieldValidation(t){const e=this.liveValidationTimers.get(t);e&&clearTimeout(e);const i=Math.max(0,Number(this.config.validationDebounce)||0),r=()=>{this.validateField(t)};if(0===i)return void r();const n=setTimeout(r,i);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",i=t.getAttribute(e);if(!i)return null;try{return document.querySelector(i)}catch(t){return console.warn(`[FormFather] Invalid error container selector "${i}"`),null}}showError(t,e){t.setAttribute("aria-invalid","true");const r=this.getErrorContainer(t);if(r)return r.textContent=e,r.hasAttribute("role")||r.setAttribute("role","alert"),void r.removeAttribute("hidden");const n=i(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-"+w++,i=`\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`,r=document.createElement("span");r.innerHTML=i,r.className="button__loader",t.insertAdjacentElement("afterbegin",r),requestAnimationFrame((()=>{r.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 r=i(t,this.config.inputWrapperSelector)||t.parentElement,n=this.getErrorContainer(t);t.removeAttribute("aria-invalid"),n&&(n.textContent="",n.setAttribute("hidden","")),null===(e=null==r?void 0:r.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==r||r.classList.remove("input__wrapper--error"),r&&r.classList.contains("input__wrapper")&&r.getAttribute("data-type-error")}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input, textarea, select"));let i=null,r=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<r&&(r=n,i=t)})),i){const t=i.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,r=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:r,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}focusFirstErroredInput(t){var e;if(0===t.length)return;const i=Array.from(this.$el.querySelectorAll("input, textarea, select")),r=t.slice().sort(((t,e)=>i.indexOf(t)-i.indexOf(e)))[0];null===(e=null==r?void 0:r.focus)||void 0===e||e.call(r,{preventScroll:!0})}getValidationSchema(){return Object.assign(Object.assign({},A.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))}getValidationStateAttribute(){return this.config.validationStateAttribute||"data-form-father-state"}setFieldValidationState(t,e){const i=this.getValidationStateAttribute();e?t.setAttribute(i,e):t.removeAttribute(i)}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,i){this.isLatestFieldValidation(t,e)&&(t.removeAttribute("aria-busy"),this.setFieldValidationState(t,i?"valid":"invalid"))}clearFieldValidationState(t){this.fieldValidationTokens.delete(t),t.removeAttribute("aria-busy"),this.setFieldValidationState(t,null)}setValidationError(t,e,i,r){this.clearFieldErrorRecord(t),this.errors.push({field:this.getFieldKey(t),rule:e,message:i,source:r})}resolveField(t){return"string"!=typeof t?t:this.findFieldByName(t)}parseRuleString(t){const[e,...i]=t.split(":").map((t=>t.trim()));return i.length>0?{rule:e,params:1===i.length?i[0]:i}: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=[],i=new Set){const r=[];t.hasAttribute("required")&&r.push("required"),r.push(...e);const n=this.parseValidationAttribute(t.getAttribute("data-validate")).filter((t=>{const e=this.getRuleName(t);return f(e)||!i.has(e)}));r.push(...n),r.push(...this.parseValidationAttribute(t.getAttribute("data-custom-validate")));const o=[],s=new Set;for(const t of r){const e=this.getRuleName(t);if(!f(e)){console.warn(`[FormFather] Unknown validation rule "${e}"`);continue}const i=this.getRuleIdentity(t);s.has(i)||(s.add(i),o.push(t))}return o}getRuleMessage(t,e,i,r,n){const o=t.getAttribute(`data-error-${e}`);return n||o||i[e]||r}fieldMatchesSchemaDefinition(t,e,i){if(i.selector)try{return t.matches(i.selector)}catch(t){return console.warn(`[FormFather] Invalid validation selector "${i.selector}"`),!1}return this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===e))}getFieldValidationConfig(t,e=this.getValidationSchema()){const i=[],r={};for(const[n,o]of Object.entries(e))this.fieldMatchesSchemaDefinition(t,n,o)&&(i.push(...o.rules),Object.assign(r,o.messages||{}));return{rules:this.collectRules(t,i,new Set(Object.keys(e))),messages:r}}getValidationFields(t,e){const i=new Set,r=t=>{Array.from(t).forEach((t=>{(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&i.add(t)}))};for(const[n,o]of Object.entries(e))if(o.selector)try{r(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=>i.add(t)));return r(t.querySelectorAll("[required], [data-validate], [data-custom-validate]")),Array.from(i)}validateInput(t,i){return e(this,arguments,void 0,(function*(t,e,i={},r=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,r,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:r,params:s})),!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(!c){const r=l,n=this.getRuleMessage(t,e,i,a.defaultMessage,d&&"string"==typeof r.message?r.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,i){return e(this,arguments,void 0,(function*(t,e,i={},r=this.$el){const n=this.startFieldValidation(t);try{const o=yield this.validateInput(t,e,i,r,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,i="manual"){const r=this.resolveField(t);return r?(this.setValidationError(r,i,e,i),this.showError(r,e),r.removeAttribute("aria-busy"),this.setFieldValidationState(r,"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:i,messages:r}=this.getFieldValidationConfig(e);return this.runFieldValidation(e,i,r)}))}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const e=this.getValidationSchema(),i=[],r=new WeakSet;let n=!0;this.errors=[];for(const o of this.getValidationFields(t,e)){if(r.has(o))continue;const s=this.getRadioGroup(o,t);s.length>0?s.forEach((t=>r.add(t))):r.add(o);const{rules:a,messages:l}=this.getFieldValidationConfig(o,e);(yield this.runFieldValidation(o,a,l,t))||(n=!1,i.push(o))}const o=i.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 i=t;"radio"===i.type||"checkbox"===i.type?i.checked=!1:(i.value="",null===(e=i.parentElement)||void 0===e||e.classList.remove("filled"),i.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 i=t;null===(e=null==i?void 0:i.getAnimations)||void 0===e||e.call(i).forEach((t=>t.cancel()))}showErrorForm(t){var e;const i=this.$el.querySelectorAll(this.config.inputWrapperSelector),r=i[i.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||r&&r.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"}),r?r.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],i=(null==e?void 0:e.nextElementSibling)||this.$el.querySelector(".error-block-under-input__wrapper");if(!i||!i.classList.contains("error-block-under-input__wrapper"))return;if((""===i.style.height||"auto"===i.style.height)&&(i.style.height=`${i.clientHeight}px`),i.style.paddingTop=i.style.paddingTop||`${this._anim.paddingTopOpened}px`,i.style.opacity=i.style.opacity||"1",this._cancelAnimations(i),this._canAnimate(i)){i.animate([{height:`${parseFloat(i.style.height)||i.clientHeight}px`,paddingTop:`${parseFloat(i.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(i.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{i.remove()}))}else i.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,S.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,i)=>{Object.prototype.hasOwnProperty.call(e,i)?(Array.isArray(e[i])||(e[i]=[e[i]]),e[i].push(t)):e[i]=t})),e}appendFormDataValue(t,e,i){null!=i&&(Array.isArray(i)?i.forEach(((i,r)=>{this.appendFormDataValue(t,`${e}[${r}]`,i)})):i instanceof Blob?t.append(e,i):t.append(e,"object"==typeof i?JSON.stringify(i):String(i)))}objectToFormData(t){const e=new FormData;return Object.entries(t).forEach((([t,i])=>{this.appendFormDataValue(e,t,i)})),e}stringifyFormDataValue(t){return"string"==typeof t?t:t.name}formDataToUrlSearchParams(t){const e=new URLSearchParams;return t.forEach(((t,i)=>{e.append(i,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 i=e.toString();if(!i)return t;const r=t.includes("?")?"&":"?";return`${t}${r}${i}`}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 i=this.findFieldByName(t.name);i?(e.push(i),this.setValidationError(i,"server",t["error-msg"],"server"),this.showError(i,t["error-msg"]),i.removeAttribute("aria-busy"),this.setFieldValidationState(i,"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")||"",i=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",r=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(i))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(c)),{method:i,headers:o});switch(r){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: ${r}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(c).toString(),o["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:i,body:n,headers:o})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,i,r,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===(r=(i=this.config).onSubmit)||void 0===r||r.call(i,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.blockScrollBody=function(){window.lastBlockScrollPosition=window.pageYOffset,document.body.style.top=-window.lastBlockScrollPosition+"px",document.body.classList.add("body--block-scroll")},t.closest=i,t.createFieldValidator=function(t,i){return(r,n,o,s)=>e(this,void 0,void 0,(function*(){return!!(yield t(r,n,o,s))||{valid:!1,message:i}}))},t.createSchemaValidator=y,t.default=A,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.registerSchemaValidator=function(t,e,i,r={}){const{override:n}=r;p(t,y(e,function(t,e){var i={};for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&e.indexOf(r)<0&&(i[r]=t[r]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(r=Object.getOwnPropertySymbols(t);n<r.length;n++)e.indexOf(r[n])<0&&Object.prototype.propertyIsEnumerable.call(t,r[n])&&(i[r[n]]=t[r[n]])}return i}(r,["override"])),i,{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}({});
2
2
  //# sourceMappingURL=FormFather.min.js.map
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  type ValidatorEffectCtx = {
2
2
  value: string;
3
- $input: HTMLInputElement | HTMLTextAreaElement;
3
+ $input: ValidatorFieldElement;
4
4
  $form: HTMLElement;
5
5
  params?: any;
6
6
  };
7
+ type ValidatorFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
7
8
  type ValidatorResult = {
8
9
  valid: boolean;
9
10
  /** Сообщение об ошибке, если нужно переопределить дефолт */
@@ -13,7 +14,7 @@ type ValidatorResult = {
13
14
  /** Остановить дальнейшие правила для поля (даже если valid=true). Полезно, если валидатор полностью «ведёт» поле. */
14
15
  stopOthers?: boolean;
15
16
  };
16
- type ValidatorFn = (value: string, $input: HTMLInputElement | HTMLTextAreaElement, $form: HTMLElement, params?: any) => boolean | Promise<boolean> | ValidatorResult | Promise<ValidatorResult>;
17
+ type ValidatorFn = (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => boolean | ValidatorResult | Promise<boolean | ValidatorResult>;
17
18
  interface Validator {
18
19
  fn: ValidatorFn;
19
20
  defaultMessage: string;
@@ -24,6 +25,33 @@ declare function registerValidator(name: string, fn: ValidatorFn, defaultMessage
24
25
  declare const getValidator: (name: string) => Validator | undefined;
25
26
  declare function getAllValidators(): Map<string, Validator>;
26
27
 
28
+ type MaybePromise<T> = T | Promise<T>;
29
+ type SchemaParseResult = {
30
+ success: true;
31
+ data?: unknown;
32
+ } | {
33
+ success: false;
34
+ error?: unknown;
35
+ issues?: Array<{
36
+ message?: string;
37
+ }>;
38
+ };
39
+ type SchemaLike = {
40
+ safeParse?: (value: unknown) => MaybePromise<SchemaParseResult>;
41
+ parse?: (value: unknown) => MaybePromise<unknown>;
42
+ } | ((value: unknown) => MaybePromise<boolean | ValidatorResult | void>);
43
+ type AdapterValidator = (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean | ValidatorResult>;
44
+ interface SchemaValidatorOptions {
45
+ message?: string | ((error: unknown) => string);
46
+ mapValue?: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => unknown;
47
+ }
48
+ interface RegisterSchemaValidatorOptions extends SchemaValidatorOptions {
49
+ override?: boolean;
50
+ }
51
+ declare function createSchemaValidator(schema: SchemaLike, options?: SchemaValidatorOptions): AdapterValidator;
52
+ declare function registerSchemaValidator(name: string, schema: SchemaLike, defaultMessage: string, options?: RegisterSchemaValidatorOptions): void;
53
+ declare function createFieldValidator(predicate: (value: string, $input: ValidatorFieldElement, $form: HTMLElement, params?: any) => MaybePromise<boolean>, message?: string): AdapterValidator;
54
+
27
55
  /**
28
56
  * Находит родительский элемент по заданому селектору.
29
57
  *
@@ -90,11 +118,30 @@ type ValidationRule = string | {
90
118
  rule: string;
91
119
  params?: any;
92
120
  };
121
+ type ValidationTrigger = 'submit' | 'input' | 'blur' | 'change';
122
+ type ValidationErrorSource = 'client' | 'server' | 'manual';
123
+ type FieldReference = string | FormFieldElement;
124
+ interface ValidationError {
125
+ field: string;
126
+ rule: string;
127
+ message: string;
128
+ source: ValidationErrorSource;
129
+ }
130
+ interface SubmitResult {
131
+ success: boolean;
132
+ response?: Response;
133
+ responseBody?: ResponseBody;
134
+ error?: unknown;
135
+ }
136
+ interface FormResetOptions {
137
+ clearErrors?: boolean;
138
+ }
93
139
  type ValidationSchema = Record<string, {
94
140
  rules: ValidationRule[];
95
141
  selector?: string;
96
142
  messages?: Record<string, string>;
97
143
  }>;
144
+ type FormFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
98
145
  /** Параметры формы. */
99
146
  interface FormOptions {
100
147
  /**
@@ -110,6 +157,20 @@ interface FormOptions {
110
157
  * @param formInstance - Инстанс формы.
111
158
  */
112
159
  onAfterValidate?: (isValid: boolean, formInstance: Form) => void;
160
+ /**
161
+ * Функция обратного вызова. Запускается после неуспешной клиентской валидации.
162
+ *
163
+ * @param errors - Список ошибок валидации.
164
+ * @param formInstance - Инстанс формы.
165
+ */
166
+ onValidationError?: (errors: ValidationError[], formInstance: Form) => void;
167
+ /**
168
+ * Функция обратного вызова. Запускается перед отправкой уже валидной формы. Если вернуть `false`, отправка будет
169
+ * отменена.
170
+ *
171
+ * @param formInstance - Инстанс формы.
172
+ */
173
+ onBeforeSubmit?: (formInstance: Form) => void | boolean | Promise<void | boolean>;
113
174
  /**
114
175
  * Функция обратного вызова. Запускается, когда форма отправляется.
115
176
  *
@@ -137,10 +198,19 @@ interface FormOptions {
137
198
  * @param formInstance - Инстанс формы.
138
199
  */
139
200
  onResponseUnsuccess?: (responseBody: ResponseBody, formInstance: Form) => void;
201
+ /**
202
+ * Функция обратного вызова. Запускается при исключении во время отправки формы.
203
+ *
204
+ * @param error - Ошибка отправки.
205
+ * @param formInstance - Инстанс формы.
206
+ */
207
+ onSubmitError?: (error: unknown, formInstance: Form) => void;
140
208
  /** Нужно ли показывать loader в кнопке. По умолчанию `true`. */
141
209
  showLoaderButton?: boolean;
142
210
  /** Нужно ли проскроливать до первого по порядку элемента с ошибкой. По умолчанию `true`. */
143
211
  scrollToFirstErroredInput?: boolean;
212
+ /** Нужно ли переводить фокус в первое ошибочное поле. По умолчанию `false`. */
213
+ focusFirstErroredInput?: boolean;
144
214
  /** Кастомный тип ошибки. */
145
215
  customTypeError?: any;
146
216
  /** Цвет лоадера. */
@@ -154,6 +224,21 @@ interface FormOptions {
154
224
  * `.input-primary`.
155
225
  */
156
226
  inputWrapperSelector?: string;
227
+ /**
228
+ * События, на которых нужно запускать валидацию поля. `submit` всегда обрабатывается отдельно. По умолчанию
229
+ * `submit`.
230
+ */
231
+ validateOn?: ValidationTrigger | ValidationTrigger[];
232
+ /** События, на которых нужно повторно проверять уже ошибочные поля. По умолчанию `input` и `change`. */
233
+ revalidateOn?: ValidationTrigger | ValidationTrigger[];
234
+ /** Задержка live-валидации в миллисекундах. По умолчанию `0`. */
235
+ validationDebounce?: number;
236
+ /** Атрибут с CSS-селектором контейнера для ошибки поля. По умолчанию `data-error-container`. */
237
+ errorContainerAttribute?: string;
238
+ /** Атрибут, куда пишется состояние поля: `validating`, `valid` или `invalid`. */
239
+ validationStateAttribute?: string;
240
+ /** Следить за динамически добавленными/удалёнными полями и submit-кнопками. По умолчанию `false`. */
241
+ observeMutations?: boolean;
157
242
  /** Функция для обёртки отправляемых данных. */
158
243
  wrapData?: (data: Record<string, any>) => Record<string, any>;
159
244
  /** Схема валидации: поле → массив правил + override-сообщения */
@@ -184,6 +269,12 @@ declare class Form {
184
269
  private inputs;
185
270
  private _onSubmitHandler?;
186
271
  private _onSubmitClickHandler?;
272
+ private fieldEventHandlers;
273
+ private liveValidationTimers;
274
+ private fieldValidationTokens;
275
+ private fieldValidationTokenCounter;
276
+ private mutationObserver?;
277
+ private errors;
187
278
  private static get defaultParams();
188
279
  private static set defaultParams(value);
189
280
  static get defaultValidationSchema(): ValidationSchema;
@@ -196,6 +287,26 @@ declare class Form {
196
287
  */
197
288
  constructor($el: HTMLElement, options?: FormOptions);
198
289
  getOptions(): FormOptions;
290
+ /**
291
+ * Инициализирует сразу несколько форм по CSS-селектору. Если форма уже инициализирована, её настройки обновятся.
292
+ *
293
+ * @param selector - CSS-селектор форм. По умолчанию `form[data-form-father]`.
294
+ * @param options - Общие параметры для найденных форм.
295
+ */
296
+ static initAll(selector?: string, options?: FormOptions): Form[];
297
+ /** Обновляет настройки конкретного инстанса формы без пересоздания обработчиков вручную. */
298
+ updateOptions(options: Partial<FormOptions>): this;
299
+ /** Возвращает текущий список ошибок формы. */
300
+ getErrors(): ValidationError[];
301
+ /** Возвращает значения формы обычным объектом. */
302
+ getValues(): Record<string, any>;
303
+ private normalizeValueList;
304
+ /** Проставляет значения полей по `name` и уведомляет UI через `input`/`change` события. */
305
+ setValues(values: Record<string, any>): this;
306
+ /** Снимает все клиентские и серверные ошибки с формы, не меняя значения полей. */
307
+ clearErrors(): this;
308
+ /** Сбрасывает форму через native `reset()` и опционально очищает ошибки. */
309
+ reset(options?: FormResetOptions): this;
199
310
  /**
200
311
  * Обновляет параметры по умолчанию для настроек формы. Метод объединяет переданные параметры с уже существующими
201
312
  * параметрами по умолчанию.
@@ -204,8 +315,17 @@ declare class Form {
204
315
  * свойства, которые необходимо обновить; остальные сохранятся без изменений.
205
316
  */
206
317
  static setDefaultParams(params: Partial<FormOptions>): void;
318
+ private refreshControls;
207
319
  private findSubmitElements;
320
+ private bindSubmitHandlers;
208
321
  private initialization;
322
+ private handleSubmitEvent;
323
+ private normalizeTriggers;
324
+ private clearFieldValidationEvents;
325
+ private bindFieldValidationEvents;
326
+ private scheduleFieldValidation;
327
+ private setupMutationObserver;
328
+ private getErrorContainer;
209
329
  /**
210
330
  * Показывает ошибку для поля ввода.
211
331
  *
@@ -229,17 +349,48 @@ declare class Form {
229
349
  * @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
230
350
  */
231
351
  private scrollToFirstErroredInput;
352
+ private focusFirstErroredInput;
353
+ private getValidationSchema;
354
+ private getAllFields;
355
+ private getFieldKey;
356
+ private hasFieldError;
357
+ private clearFieldErrorRecord;
358
+ private getValidationStateAttribute;
359
+ private setFieldValidationState;
360
+ private startFieldValidation;
361
+ private isLatestFieldValidation;
362
+ private finishFieldValidation;
363
+ private clearFieldValidationState;
364
+ private setValidationError;
365
+ private resolveField;
366
+ private parseRuleString;
367
+ private parseValidationAttribute;
368
+ private getRuleName;
369
+ private getRuleIdentity;
370
+ private collectRules;
371
+ private getRuleMessage;
372
+ private fieldMatchesSchemaDefinition;
373
+ private getFieldValidationConfig;
374
+ private getValidationFields;
375
+ private validateInput;
376
+ private runFieldValidation;
377
+ private getRadioGroup;
378
+ /** Показывает ошибку конкретного поля по имени или DOM-элементу. */
379
+ showFieldError(field: FieldReference, message: string, source?: ValidationErrorSource): boolean;
380
+ /** Проверяет одно поле по имени или DOM-элементу. */
381
+ validateField(field: FieldReference): Promise<boolean>;
232
382
  /**
233
383
  * Проверяет все поля ввода.
234
384
  *
235
- * Порядок для КАЖДОГО `<input>`:
385
+ * Порядок для каждого поля:
236
386
  *
237
387
  * 1. `required` — если атрибут `required`
238
388
  * 2. правила из `validationSchema`
239
- * 3. правила из `data-custom-validate`
389
+ * 3. правила из `data-validate`
390
+ * 4. правила из `data-custom-validate`
240
391
  *
241
392
  * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
242
- * валидации, поэтому валидационный цикл больше не проверяет их наличие.
393
+ * валидации.
243
394
  */
244
395
  validate($block?: HTMLElement): Promise<boolean>;
245
396
  /** Блокирует кнопку отправки данных. */
@@ -283,7 +434,7 @@ declare class Form {
283
434
  private parseResponseBody;
284
435
  private handleUnsuccessfulResponse;
285
436
  private sendData;
286
- private submit;
437
+ submit(): Promise<SubmitResult | undefined>;
287
438
  }
288
439
 
289
- export { type ErrorResponse, type FormOptions, type ResponseBody, type ValidationRule, type ValidationSchema, type ValidatorEffectCtx, type ValidatorResult, blockScrollBody, closest, Form as default, getAllValidators, getValidator, isEmailValid, isPhoneValid, isUrlValid, parseCommonResponseProperties, registerValidator, serializeFormToJSON, serializeToFormData, unblockScrollBody };
440
+ export { type AdapterValidator, type ErrorResponse, type FieldReference, type FormFieldElement, type FormOptions, type FormResetOptions, type RegisterSchemaValidatorOptions, type ResponseBody, type SchemaLike, type SchemaParseResult, type SchemaValidatorOptions, type SubmitResult, type ValidationError, type ValidationErrorSource, 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 };