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.
- package/CHANGELOG.md +79 -0
- package/README.md +77 -2
- package/demos/index.html +141 -0
- package/demos/main.js +92 -0
- package/demos/styles.css +221 -0
- package/dist/FormFather.min.js +1 -1
- package/dist/index.d.ts +158 -7
- package/dist/index.js +641 -130
- package/dist/index.js.map +1 -1
- package/dist/types/adapters.d.ts +28 -0
- package/dist/types/index.d.ts +128 -4
- package/dist/types/validators.d.ts +3 -2
- package/docs/en/README.md +192 -0
- package/docs/recipes/README.md +89 -0
- package/package.json +5 -2
package/dist/FormFather.min.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
* Порядок для
|
|
385
|
+
* Порядок для каждого поля:
|
|
236
386
|
*
|
|
237
387
|
* 1. `required` — если атрибут `required`
|
|
238
388
|
* 2. правила из `validationSchema`
|
|
239
|
-
* 3. правила из `data-
|
|
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
|
-
|
|
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 };
|