form-father 0.0.22 → 0.1.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/FormFather.min.js CHANGED
@@ -1,2 +1,2 @@
1
- var FormFather=function(){"use strict";function t(t,e,r,o){return new(r||(r=Promise))((function(n,s){function i(t){try{a(o.next(t))}catch(t){s(t)}}function l(t){try{a(o.throw(t))}catch(t){s(t)}}function a(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(i,l)}a((o=o.apply(t,e||[])).next())}))}function e(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.hasAttribute("data-no-serialize"))return;const{name:e,type:o}=t;if("file"===o)if("true"===t.getAttribute("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 s=t;if("select-multiple"===o){Array.from(s.selectedOptions).map((t=>t.value)).forEach(((t,o)=>{r.append(`${e}[${o}]`,t)}))}else n=s.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()),s=o(r.getMonth()+1),i=r.getFullYear().toString();return e.replace("dd",n).replace("mm",s).replace("yyyy",i)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==o&&"select-multiple"!==o&&r.append(e,n)}})),r}"function"==typeof SuppressedError&&SuppressedError;class r{constructor(t,e){this.$submit=null,this.waitResponse=!1,this.inputs=null,this.$licensesCheckbox=null,this.$el=t,this.options=e;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary"},r.defaultParams,e),this.$el?(this.$submit=this.$el.querySelector('input[type="submit"], button[type="submit"]'),this.isCorrectArguments()&&this.initialization()):console.warn("Empty $el")}getOptions(){return this.options}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}initialization(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector),this.$licensesCheckbox=this.$el.querySelector('input[data-input-name="user-consent"]'),this.$licensesCheckbox&&(this.$licensesCheckbox.checked?this.enableSubmit():this.disableSubmit(),this.$licensesCheckbox.addEventListener("change",(()=>{this.$licensesCheckbox.checked?this.enableSubmit():this.disableSubmit()})));const r=()=>t(this,void 0,void 0,(function*(){var t;const r=this.$el.getAttribute("action")||"",o=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",n=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let s=null,i={};const l=e(this.$el);let a={};if(l.forEach(((t,e)=>{a[e]=t})),this.config.logging)for(const t of l.keys())console.log(l.get(t));switch("function"==typeof this.config.wrapData&&(a=this.config.wrapData(a)),n){case"application/x-www-form-urlencoded":s=new URLSearchParams(e(this.$el)).toString(),i["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":s=e(this.$el);break;case"text/plain":s=Array.from(e(this.$el)).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`)).join("\n"),i["Content-Type"]="text/plain";break;case"application/json":s=JSON.stringify(a),i["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${n}. Используется application/x-www-form-urlencoded.`),s=new URLSearchParams(e(this.$el)).toString(),i["Content-Type"]="application/x-www-form-urlencoded"}return fetch(r,{method:o,body:s,headers:i})})),o=()=>t(this,void 0,void 0,(function*(){var t,e,o,n,s,i,l,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;const c=yield r(),u=yield c.json();if(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),null===(n=(o=this.config).onResponse)||void 0===n||n.call(o,u,this),200===c.status)if(function(t){if(Object.prototype.hasOwnProperty.call(t,"redirect-url")&&(window.location.href=t["redirect-url"]),Object.prototype.hasOwnProperty.call(t,"reload")&&!0===t.reload&&window.location.reload(),Object.prototype.hasOwnProperty.call(t,"error-toast")){const e=document.querySelector('[data-toast-name="formErrorToast"]');if(e){const r=e.querySelector(".toast__text");r&&(r.textContent=t["error-toast"],window.Toast.showToast("formErrorToast",{removeAfterClose:!1}))}}}(u),!0===u.success)null===(i=(s=this.config).onResponseSuccess)||void 0===i||i.call(s,u,this);else if(null===(a=(l=this.config).onResponseUnsuccess)||void 0===a||a.call(l,u,this),!0===u.error&&(u["error-msg"]&&this.showErrorForm(u["error-msg"]),u.errors)){const t=[];u.errors.forEach((e=>{const r=this.$el.querySelector(`input[name="${e.name}"]`);r?(t.push(r),this.showError(r,e["error-msg"])):console.warn(`Не найдено поле с именем: ${e.name}, для вывода ошибки: ${e["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(t)}}}));this.$el.addEventListener("submit",(t=>{t.preventDefault(),this.checkInputs()&&(!this.$licensesCheckbox||this.$licensesCheckbox&&this.$licensesCheckbox.checked)&&o()}))}showError(t,e){(function(t,e){if(null==t)return null;let r=t;for(;r;){if(r.matches(e))return r;r=r.parentElement}return null})(t,this.config.inputWrapperSelector).showError(e)}showLoader(){var t;const e=`\n\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<defs>\n\t\t\t\t<linearGradient id="form-father-loader" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t</linearGradient>\n\t\t\t\t</defs>\n\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#form-father-loader)"></path>\n\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t</g>\n\t\t\t\t</g>\n\t\t\t</svg>\n\t\t`,r=document.createElement("span");r.innerHTML=e,r.className="button__loader",null===(t=this.$submit)||void 0===t||t.insertAdjacentElement("afterbegin",r),requestAnimationFrame((()=>{r.setAttribute("data-showed","")}))}hideLoader(){var t,e;null===(t=this.$submit)||void 0===t||t.classList.remove("button--loading");const r=null===(e=this.$submit)||void 0===e?void 0:e.querySelector(".button__loader");r&&(r.classList.remove("active"),r.removeAttribute("data-showed"),setTimeout((()=>{r.remove()}),250))}hideError(t){const e=t.parentElement;e&&e.classList.contains("input__wrapper")&&(e.getAttribute("data-type-error"),e.classList.remove("input__wrapper--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)}}}checkInputs(){var t;let e=!0;const r={},o=[];if(null===(t=this.inputs)||void 0===t||t.forEach((t=>{var n;const s=t,i=s.getAttribute("data-check-type")||s.getAttribute("type")||"";if("SELECT"===s.tagName){const t=s.selectedOptions[0];t&&""===t.value&&s.hasAttribute("required")&&(this.showError(s,"Пустое значение"),o.push(s),e=!1)}else if("checkbox"===i)s.hasAttribute("required")&&!s.checked&&(this.showError(s,"Необходимо согласие"),o.push(s),e=!1);else if((null===(n=this.config.customTypeError)||void 0===n?void 0:n.name)!==i||"TEXTAREA"===s.tagName&&!this.config.customTypeError)if(s.value.length>0){if("email"===i&&(l=s.value,/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(l)||(this.showError(s,"Неверный формат"),o.push(s),e=!1)),"tel"===i){const t=this.config.validatePhone||(t=>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.value));t(s)||(this.showError(s,"Неверный формат"),o.push(s),e=!1)}if("url"===i&&(function(t){const e=/^https?:\/\//i.test(t)?t:`http://${t}`;try{return new URL(e),!0}catch(t){return!1}}(s.value)||(this.showError(s,"Неверный формат"),o.push(s),e=!1)),"text"===i&&"not-numbers"===s.getAttribute("data-check-type")&&/[0-9]/.test(s.value)&&(this.showError(s,"Неверный формат"),o.push(s),e=!1),"radio"===i){const t=s.getAttribute("name")||"";r[t]||(r[t]={}),r[t][Object.keys(r[t]).length]=s}}else s.hasAttribute("required")&&(this.showError(s,"Пустое значение"),o.push(s),e=!1);var l})),Object.keys(r).length>0)for(const t in r){const e=r[t];let o=!1,n=null;for(const t in e){const r=e[t];if(r.hasAttribute("required")&&(o=!0),r.checked){if(null!==n)return!1;n=r}}if(null===n&&o)return this.showErrorForm("Необходимо указать оценку"),!1}return!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(o),e}disableSubmit(){var t;null===(t=this.$submit)||void 0===t||t.setAttribute("disabled","")}enableSubmit(){var t;null===(t=this.$submit)||void 0===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")))}))}showErrorForm(t){var e;const r=this.$el.querySelectorAll(".input__wrapper"),o=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");n&&o.nextElementSibling===n?(n.classList.add("error-block-under-input--warning"),n.querySelector(".error-block-under-input__icon").innerHTML="\x3c!-- SVG content --\x3e",n.querySelector(".error-block-under-input__main-text").textContent=t):(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">\n <div class="error-block-under-input__icon-wrapper">\n <span class="error-block-under-input__icon">\n \x3c!-- SVG content --\x3e\n </span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:none;"></span>\n </div>\n <p class="error-block-under-input__text">\n <span class="error-block-under-input__main-text">${t}</span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>`,o.insertAdjacentElement("afterend",n)),this.$el.addEventListener("input",(()=>{this.hideErrorForm()}),{passive:!0,once:!0}),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const s=n.querySelector(".error-block-under-input__secondary-text");s&&(s.textContent="");const i=parseFloat(n.style.height)||0,l=parseFloat(window.getComputedStyle(n).paddingTop)||0;i!==n.querySelector(".error-block-under-input").scrollHeight+16&&(n.style.height=`${i}px`,n.style.opacity="0",n.style.paddingTop=`${l}px`)}hideErrorForm(){const t=this.$el.querySelectorAll(".input__wrapper"),e=t[t.length-1].nextElementSibling;e&&e.classList.contains("error-block-under-input__wrapper")}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):!!this.$submit||(console.warn("В форме нет кнопки с типом submit"),!1)}}return r.defaultParams={},r}();
1
+ var FormFather=function(){"use strict";function e(e,t,r,o){return new(r||(r=Promise))((function(n,s){function i(e){try{a(o.next(e))}catch(e){s(e)}}function l(e){try{a(o.throw(e))}catch(e){s(e)}}function a(e){var t;e.done?n(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(i,l)}a((o=o.apply(e,t||[])).next())}))}function t(e){const t="FORM"===e.tagName?Array.from(e.elements):Array.from(e.querySelectorAll("input, select, textarea, button")),r=new FormData;return t.forEach((e=>{if(!(e instanceof HTMLInputElement||e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement||e instanceof HTMLButtonElement))return;if(!e.name||e.hasAttribute("data-no-serialize"))return;const{name:t,type:o}=e;if("file"===o)if("true"===e.getAttribute("multiple")){const o=e.files;if(o)for(let e=0;e<o.length;e++)r.append(`${t}[${e}]`,o[e])}else{const o=e.files;o&&o[0]&&r.append(t,o[0])}else{let n;if("select-one"===o||"select-multiple"===o){const s=e;if("select-multiple"===o){Array.from(s.selectedOptions).map((e=>e.value)).forEach(((e,o)=>{r.append(`${t}[${o}]`,e)}))}else n=s.value,n&&r.append(t,n)}else if("tel"===o)n=e.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===o||"radio"===o){if(!e.checked)return;n=e.value}else if("date"===o){const t=e.getAttribute("data-date-format");n=t?function(e,t){const r=new Date(e),o=e=>e.toString().padStart(2,"0"),n=o(r.getDate()),s=o(r.getMonth()+1),i=r.getFullYear().toString();return t.replace("dd",n).replace("mm",s).replace("yyyy",i)}(e.value,t):e.value}else n=e.value;n&&"select-one"!==o&&"select-multiple"!==o&&r.append(t,n)}})),r}var r;"function"==typeof SuppressedError&&SuppressedError;const o=Symbol.for("FormFather.validators"),n=null!==(r=globalThis[o])&&void 0!==r?r:globalThis[o]=new Map;function s(e,t,r,{override:o=!1}={}){!n.has(e)||o?n.set(e,{fn:t,defaultMessage:r}):console.warn(`[FormFather] Validator "${e}" already exists; pass { override: true } to replace`)}const i=e=>n.get(e);s("required",(e=>e.trim().length>0),"Пустое значение"),s("email",(e=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)),"Неверный формат"),s("tel",(e=>/^\+7\d{10}$/.test(e)),"Неверный формат"),s("url",(e=>/^(https?:\/\/).+\..+/.test(e)),"Неверный формат"),s("not-numbers",(e=>!/[0-9]/.test(e)),"Неверный формат");class l{constructor(e,t){this.$submit=null,this.waitResponse=!1,this.inputs=null,this.$licensesCheckbox=null,this.$el=e,this.options=t;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary"},l.defaultParams,t),this.$el?(this.$submit=this.$el.querySelector('input[type="submit"], button[type="submit"]'),this.isCorrectArguments()&&this.initialization()):console.warn("Empty $el")}getOptions(){return this.options}static setDefaultParams(e){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),e)}initialization(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector),this.$licensesCheckbox=this.$el.querySelector('input[data-input-name="user-consent"]'),this.$licensesCheckbox&&(this.$licensesCheckbox.checked?this.enableSubmit():this.disableSubmit(),this.$licensesCheckbox.addEventListener("change",(()=>{this.$licensesCheckbox.checked?this.enableSubmit():this.disableSubmit()})));const r=()=>e(this,void 0,void 0,(function*(){var e;const r=this.$el.getAttribute("action")||"",o=(null===(e=this.$el.getAttribute("method"))||void 0===e?void 0:e.toUpperCase())||"POST",n=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let s=null,i={};const l=t(this.$el);let a={};if(l.forEach(((e,t)=>{a[t]=e})),this.config.logging)for(const e of l.keys())console.log(l.get(e));switch("function"==typeof this.config.wrapData&&(a=this.config.wrapData(a)),n){case"application/x-www-form-urlencoded":s=new URLSearchParams(t(this.$el)).toString(),i["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":s=t(this.$el);break;case"text/plain":s=Array.from(t(this.$el)).map((([e,t])=>`${encodeURIComponent(e)}=${encodeURIComponent(t)}`)).join("\n"),i["Content-Type"]="text/plain";break;case"application/json":s=JSON.stringify(a),i["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${n}. Используется application/x-www-form-urlencoded.`),s=new URLSearchParams(t(this.$el)).toString(),i["Content-Type"]="application/x-www-form-urlencoded"}return fetch(r,{method:o,body:s,headers:i})})),o=()=>e(this,void 0,void 0,(function*(){var e,t,o,n,s,i,l,a;if(!this.waitResponse){!0===this.config.showLoaderButton&&this.showLoader(),null===(t=(e=this.config).onSubmit)||void 0===t||t.call(e,this),this.waitResponse=!0;const c=yield r(),u=yield c.json();if(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),null===(n=(o=this.config).onResponse)||void 0===n||n.call(o,u,this),200===c.status)if(function(e){if(Object.prototype.hasOwnProperty.call(e,"redirect-url")&&(window.location.href=e["redirect-url"]),Object.prototype.hasOwnProperty.call(e,"reload")&&!0===e.reload&&window.location.reload(),Object.prototype.hasOwnProperty.call(e,"error-toast")){const t=document.querySelector('[data-toast-name="formErrorToast"]');if(t){const r=t.querySelector(".toast__text");r&&(r.textContent=e["error-toast"],window.Toast.showToast("formErrorToast",{removeAfterClose:!1}))}}}(u),!0===u.success)null===(i=(s=this.config).onResponseSuccess)||void 0===i||i.call(s,u,this);else if(null===(a=(l=this.config).onResponseUnsuccess)||void 0===a||a.call(l,u,this),!0===u.error&&(u["error-msg"]&&this.showErrorForm(u["error-msg"]),u.errors)){const e=[];u.errors.forEach((t=>{const r=this.$el.querySelector(`input[name="${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)}}}));this.$el.addEventListener("submit",(t=>e(this,void 0,void 0,(function*(){t.preventDefault();(yield this.validate())&&(!this.$licensesCheckbox||this.$licensesCheckbox&&this.$licensesCheckbox.checked)&&o()}))))}showError(e,t){(function(e,t){if(null==e)return null;let r=e;for(;r;){if(r.matches(t))return r;r=r.parentElement}return null})(e,this.config.inputWrapperSelector).showError(t)}showLoader(){var e;const t=`\n\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<defs>\n\t\t\t\t<linearGradient id="form-father-loader" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t</linearGradient>\n\t\t\t\t</defs>\n\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#form-father-loader)"></path>\n\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t</g>\n\t\t\t\t</g>\n\t\t\t</svg>\n\t\t`,r=document.createElement("span");r.innerHTML=t,r.className="button__loader",null===(e=this.$submit)||void 0===e||e.insertAdjacentElement("afterbegin",r),requestAnimationFrame((()=>{r.setAttribute("data-showed","")}))}hideLoader(){var e,t;null===(e=this.$submit)||void 0===e||e.classList.remove("button--loading");const r=null===(t=this.$submit)||void 0===t?void 0:t.querySelector(".button__loader");r&&(r.classList.remove("active"),r.removeAttribute("data-showed"),setTimeout((()=>{r.remove()}),250))}hideError(e){const t=e.parentElement;t&&t.classList.contains("input__wrapper")&&(t.getAttribute("data-type-error"),t.classList.remove("input__wrapper--error"))}scrollToFirstErroredInput(e){if(e.length>0){const t=Array.from(this.$el.querySelectorAll("input"));let r=null,o=1/0;if(e.forEach((e=>{const n=t.findIndex((t=>t===e));n<o&&(o=n,r=e)})),r){const e=r.getBoundingClientRect(),t=window.pageYOffset||document.documentElement.scrollTop,o=e.top+t-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,void 0,void 0,(function*(){const t=Object.assign(Object.assign({},l.defaultValidationSchema||{}),this.config.validationSchema||{}),r=e=>console.warn(`[FormFather] Unknown validation rule "${e}"`),o=[],n=new WeakSet;let s=!0;const a=(e,t=[])=>{var o;const n=[];e.hasAttribute("required")&&n.push("required"),n.push(...t);const s=(null!==(o=e.getAttribute("data-custom-validate"))&&void 0!==o?o:"").split(",").map((e=>e.trim())).filter(Boolean);n.push(...s);const l=[];for(const e of n){const t="string"==typeof e?e:e.rule;i(t)?l.push(e):r(t)}return l},c=(t,r,...n)=>e(this,[t,r,...n],void 0,(function*(e,t,r={}){var n;if(!("checkbox"===e.type||"radio"===e.type?!e.checked:0===e.value.trim().length)||t.includes("required")){for(const l of t){const{rule:t,params:a}="string"==typeof l?{rule:l,params:void 0}:l,c=i(t);if(!(yield c.fn(e.value,e,this.$el,a))){s=!1;const i=null!==(n=r[t])&&void 0!==n?n:c.defaultMessage;return this.showError(e,i),void o.push(e)}}this.hideError(e)}else this.hideError(e)}));for(const[e,r]of Object.entries(t)){const{selector:t,rules:o=[],messages:s={}}=r,i=t?this.$el.querySelectorAll(t):this.$el.querySelectorAll(`[data-validate="${e}"]`);for(const e of Array.from(i))n.has(e)||(n.add(e),yield c(e,a(e,o),s))}const u=Array.from(this.$el.querySelectorAll("[data-custom-validate]")).filter((e=>!n.has(e)));for(const e of u)n.add(e),yield c(e,a(e,[]));return!s&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(o),s}))}disableSubmit(){var e;null===(e=this.$submit)||void 0===e||e.setAttribute("disabled","")}enableSubmit(){var e;null===(e=this.$submit)||void 0===e||e.removeAttribute("disabled")}clearInputs(){var e;null===(e=this.inputs)||void 0===e||e.forEach((e=>{var t;const r=e;"radio"===r.type||"checkbox"===r.type?r.checked=!1:(r.value="",null===(t=r.parentElement)||void 0===t||t.classList.remove("filled"),r.dispatchEvent(new Event("input")))}))}showErrorForm(e){var t;const r=this.$el.querySelectorAll(".input__wrapper"),o=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");n&&o.nextElementSibling===n?(n.classList.add("error-block-under-input--warning"),n.querySelector(".error-block-under-input__icon").innerHTML="\x3c!-- SVG content --\x3e",n.querySelector(".error-block-under-input__main-text").textContent=e):(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">\n <div class="error-block-under-input__icon-wrapper">\n <span class="error-block-under-input__icon">\n \x3c!-- SVG content --\x3e\n </span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:none;"></span>\n </div>\n <p class="error-block-under-input__text">\n <span class="error-block-under-input__main-text">${e}</span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>`,o.insertAdjacentElement("afterend",n)),this.$el.addEventListener("input",(()=>{this.hideErrorForm()}),{passive:!0,once:!0}),null===(t=n.querySelector(".error-block-under-input"))||void 0===t||t.classList.remove("error-block-under-input--success");const s=n.querySelector(".error-block-under-input__secondary-text");s&&(s.textContent="");const i=parseFloat(n.style.height)||0,l=parseFloat(window.getComputedStyle(n).paddingTop)||0;i!==n.querySelector(".error-block-under-input").scrollHeight+16&&(n.style.height=`${i}px`,n.style.opacity="0",n.style.paddingTop=`${l}px`)}hideErrorForm(){const e=this.$el.querySelectorAll(".input__wrapper"),t=e[e.length-1].nextElementSibling;t&&t.classList.contains("error-block-under-input__wrapper")}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):!!this.$submit||(console.warn("В форме нет кнопки с типом submit"),!1)}}return l.defaultParams={},l.defaultValidationSchema={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"]}},l}();
2
2
  //# sourceMappingURL=FormFather.min.js.map
package/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
+ type ValidationSchema = Record<string, {
2
+ rules: (string | {
3
+ rule: string;
4
+ params?: any;
5
+ })[];
6
+ selector: string;
7
+ messages?: Record<string, string>;
8
+ }>;
1
9
  /** Параметры формы. */
2
10
  interface FormOptions {
3
11
  /**
@@ -51,6 +59,8 @@ interface FormOptions {
51
59
  validatePhone?: (input: HTMLInputElement) => boolean;
52
60
  /** Функция для обёртки отправляемых данных. */
53
61
  wrapData?: (data: Record<string, any>) => Record<string, any>;
62
+ /** Схема валидации: поле → массив правил + override-сообщения */
63
+ validationSchema?: ValidationSchema;
54
64
  }
55
65
  /**
56
66
  * Реализует форму отправки данных.
@@ -66,6 +76,7 @@ declare class Form {
66
76
  private inputs;
67
77
  private $licensesCheckbox;
68
78
  private static defaultParams;
79
+ static defaultValidationSchema: ValidationSchema;
69
80
  /**
70
81
  * Создать форму.
71
82
  *
@@ -107,13 +118,18 @@ declare class Form {
107
118
  */
108
119
  private scrollToFirstErroredInput;
109
120
  /**
110
- * Проверяет все поля ввода на корректность их заполнения.
121
+ * Проверяет все поля ввода.
111
122
  *
112
- * Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
123
+ * Порядок для КАЖДОГО `<input>`:
113
124
  *
114
- * @returns {boolean} Корректно заполнены или нет (true/false).
125
+ * 1. `required` — если атрибут `required`
126
+ * 2. правила из `validationSchema`
127
+ * 3. правила из `data-custom-validate`
128
+ *
129
+ * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
130
+ * валидации, поэтому валидационный цикл больше не проверяет их наличие.
115
131
  */
116
- private checkInputs;
132
+ validate(): Promise<boolean>;
117
133
  /** Блокирует кнопку отправки данных. */
118
134
  private disableSubmit;
119
135
  /** Снимает блокировку с кнопки отправки данных. */
package/index.js CHANGED
@@ -73,50 +73,6 @@ function closest($el, selector) {
73
73
  }
74
74
  return null;
75
75
  }
76
- /**
77
- * Проверяет является ли строка валидным адресом электронной почты.
78
- *
79
- * @param {string} value - Строка, которую нужно проверить.
80
- *
81
- * @returns {boolean} Является ли строка валидным адресом электронной почты true/false.
82
- */
83
- function isEmailValid(value) {
84
- return /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(value);
85
- }
86
- /**
87
- * Проверяет является ли строка валидным url.
88
- *
89
- * @param {string} originalUrlString - Строка, которую нужно проверить.
90
- *
91
- * @returns {boolean} Является ли строка валидным url адресом true/false.
92
- */
93
- function isUrlValid(originalUrlString) {
94
- const urlString = /^https?:\/\//i.test(originalUrlString) ? originalUrlString : `http://${originalUrlString}`;
95
- try {
96
- new URL(urlString);
97
- return true;
98
- }
99
- catch (e) {
100
- return false;
101
- }
102
- }
103
- /**
104
- * Проверяет является ли строка валидным номером телефона.
105
- *
106
- * @param {string} value - Строка, которую нужно проверить.
107
- *
108
- * @returns {boolean} Является ли строка валидным номером телефона true/false.
109
- */
110
- function isPhoneValid(value) {
111
- const numbersArray = [];
112
- Array.from(value).forEach(char => {
113
- if (!!Number(char) || Number(char) === 0)
114
- numbersArray.push(char);
115
- });
116
- if (numbersArray.length < 11 || (numbersArray.length < 12 && value[1] === '+'))
117
- return false;
118
- return true;
119
- }
120
76
  /**
121
77
  * Кастомная сериализация формы в объект FormData.
122
78
  *
@@ -225,6 +181,26 @@ function parseCommonResponseProperties(responseBody) {
225
181
  }
226
182
  }
227
183
 
184
+ var _a;
185
+ /* ---------- singleton-storage на globalThis ---------- */
186
+ const GLOBAL_KEY = Symbol.for('FormFather.validators');
187
+ const validators = (_a = globalThis[GLOBAL_KEY]) !== null && _a !== void 0 ? _a : (globalThis[GLOBAL_KEY] = new Map());
188
+ /* ---------- API ---------- */
189
+ function registerValidator(name, fn, defaultMessage, { override = false } = {}) {
190
+ if (validators.has(name) && !override) {
191
+ console.warn(`[FormFather] Validator "${name}" already exists; pass { override: true } to replace`);
192
+ return;
193
+ }
194
+ validators.set(name, { fn, defaultMessage });
195
+ }
196
+ const getValidator = (name) => validators.get(name);
197
+ /* ---------- built-in rules ---------- */
198
+ registerValidator('required', v => v.trim().length > 0, 'Пустое значение');
199
+ registerValidator('email', v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), 'Неверный формат');
200
+ registerValidator('tel', v => /^\+7\d{10}$/.test(v), 'Неверный формат');
201
+ registerValidator('url', v => /^(https?:\/\/).+\..+/.test(v), 'Неверный формат');
202
+ registerValidator('not-numbers', v => !/[0-9]/.test(v), 'Неверный формат');
203
+
228
204
  /**
229
205
  * Реализует форму отправки данных.
230
206
  *
@@ -391,11 +367,12 @@ class Form {
391
367
  }
392
368
  }
393
369
  });
394
- this.$el.addEventListener('submit', e => {
370
+ this.$el.addEventListener('submit', (e) => __awaiter(this, void 0, void 0, function* () {
395
371
  e.preventDefault();
396
- if (this.checkInputs() && (!this.$licensesCheckbox || (this.$licensesCheckbox && this.$licensesCheckbox.checked)))
372
+ const isValid = yield this.validate();
373
+ if (isValid && (!this.$licensesCheckbox || (this.$licensesCheckbox && this.$licensesCheckbox.checked)))
397
374
  submit();
398
- });
375
+ }));
399
376
  }
400
377
  /**
401
378
  * Показывает ошибку для поля ввода.
@@ -494,115 +471,95 @@ class Form {
494
471
  }
495
472
  }
496
473
  /**
497
- * Проверяет все поля ввода на корректность их заполнения.
474
+ * Проверяет все поля ввода.
498
475
  *
499
- * Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
476
+ * Порядок для КАЖДОГО `<input>`:
500
477
  *
501
- * @returns {boolean} Корректно заполнены или нет (true/false).
478
+ * 1. `required` — если атрибут `required`
479
+ * 2. правила из `validationSchema`
480
+ * 3. правила из `data-custom-validate`
481
+ *
482
+ * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
483
+ * валидации, поэтому валидационный цикл больше не проверяет их наличие.
502
484
  */
503
- checkInputs() {
504
- var _a;
505
- let isCorrect = true;
506
- const radioGroups = {};
507
- const inputsList = [];
508
- (_a = this.inputs) === null || _a === void 0 ? void 0 : _a.forEach($inputEl => {
509
- var _a;
510
- const $input = $inputEl;
511
- const inputType = $input.getAttribute('data-check-type') || $input.getAttribute('type') || '';
512
- if ($input.tagName === 'SELECT') {
513
- const selectedOption = $input.selectedOptions[0];
514
- if (selectedOption && selectedOption.value === '' && $input.hasAttribute('required')) {
515
- this.showError($input, 'Пустое значение');
516
- inputsList.push($input);
517
- isCorrect = false;
485
+ validate() {
486
+ return __awaiter(this, void 0, void 0, function* () {
487
+ /* ---------- финальная схема (defaults + config) ---------- */
488
+ const schema = Object.assign(Object.assign({}, (Form.defaultValidationSchema || {})), (this.config.validationSchema || {}));
489
+ const warnUnknown = (r) => console.warn(`[FormFather] Unknown validation rule "${r}"`);
490
+ const erroredInputs = [];
491
+ const processed = new WeakSet();
492
+ let ok = true;
493
+ /* ---------- helpers ---------- */
494
+ /** Собирает и тут же фильтрует unknown-rules */
495
+ const collectRules = ($input, fromSchema = []) => {
496
+ var _a;
497
+ const list = [];
498
+ if ($input.hasAttribute('required'))
499
+ list.push('required'); // 1
500
+ list.push(...fromSchema); // 2
501
+ const custom = ((_a = $input.getAttribute('data-custom-validate')) !== null && _a !== void 0 ? _a : '')
502
+ .split(',')
503
+ .map(s => s.trim())
504
+ .filter(Boolean);
505
+ list.push(...custom); // 3
506
+ /* —–– предварительная фильтрация unknown ––– */
507
+ const filtered = [];
508
+ for (const r of list) {
509
+ const ruleName = typeof r === 'string' ? r : r.rule;
510
+ if (getValidator(ruleName))
511
+ filtered.push(r);
512
+ else
513
+ warnUnknown(ruleName);
518
514
  }
519
- }
520
- else if (inputType === 'checkbox') {
521
- if ($input.hasAttribute('required') && !$input.checked) {
522
- this.showError($input, 'Необходимо согласие');
523
- inputsList.push($input);
524
- isCorrect = false;
515
+ return filtered;
516
+ };
517
+ const validateInput = ($input_1, rules_1, ...args_1) => __awaiter(this, [$input_1, rules_1, ...args_1], void 0, function* ($input, rules, msgs = {}) {
518
+ var _a;
519
+ const empty = $input.type === 'checkbox' || $input.type === 'radio' ? !$input.checked : $input.value.trim().length === 0;
520
+ if (empty && !rules.includes('required')) {
521
+ this.hideError($input);
522
+ return;
525
523
  }
526
- }
527
- else if (((_a = this.config.customTypeError) === null || _a === void 0 ? void 0 : _a.name) !== inputType ||
528
- ($input.tagName === 'TEXTAREA' && !this.config.customTypeError)) {
529
- if ($input.value.length > 0) {
530
- if (inputType === 'email') {
531
- if (!isEmailValid($input.value)) {
532
- this.showError($input, 'Неверный формат');
533
- inputsList.push($input);
534
- isCorrect = false;
535
- }
536
- }
537
- if (inputType === 'tel') {
538
- const isPhoneValidChecker = this.config.validatePhone ||
539
- (($input) => {
540
- return isPhoneValid($input.value);
541
- });
542
- if (!isPhoneValidChecker($input)) {
543
- this.showError($input, 'Неверный формат');
544
- inputsList.push($input);
545
- isCorrect = false;
546
- }
547
- }
548
- if (inputType === 'url') {
549
- if (!isUrlValid($input.value)) {
550
- this.showError($input, 'Неверный формат');
551
- inputsList.push($input);
552
- isCorrect = false;
553
- }
554
- }
555
- if (inputType === 'text') {
556
- if ($input.getAttribute('data-check-type') === 'not-numbers') {
557
- if (/[0-9]/.test($input.value)) {
558
- this.showError($input, 'Неверный формат');
559
- inputsList.push($input);
560
- isCorrect = false;
561
- }
562
- }
563
- }
564
- if (inputType === 'radio') {
565
- const name = $input.getAttribute('name') || '';
566
- if (!radioGroups[name]) {
567
- radioGroups[name] = {};
568
- }
569
- radioGroups[name][Object.keys(radioGroups[name]).length] = $input;
524
+ for (const r of rules) {
525
+ const { rule, params } = typeof r === 'string' ? { rule: r, params: undefined } : r;
526
+ const vd = getValidator(rule); // гарантированно существует
527
+ const passed = yield vd.fn($input.value, $input, this.$el, params);
528
+ if (!passed) {
529
+ ok = false;
530
+ const msg = (_a = msgs[rule]) !== null && _a !== void 0 ? _a : vd.defaultMessage;
531
+ this.showError($input, msg);
532
+ erroredInputs.push($input);
533
+ return; // первую ошибку отобразили
570
534
  }
571
535
  }
572
- else if ($input.hasAttribute('required')) {
573
- this.showError($input, 'Пустое значение');
574
- inputsList.push($input);
575
- isCorrect = false;
536
+ this.hideError($input);
537
+ });
538
+ /* ---------- 1. inputs из schema (config + defaults) ---------- */
539
+ for (const [key, def] of Object.entries(schema)) {
540
+ const { selector, rules = [], messages = {} } = def;
541
+ const nodeList = selector
542
+ ? this.$el.querySelectorAll(selector) // явный CSS-селектор
543
+ : this.$el.querySelectorAll(`[data-validate="${key}"]`); // fallback по name
544
+ for (const $input of Array.from(nodeList)) {
545
+ if (processed.has($input))
546
+ continue;
547
+ processed.add($input);
548
+ yield validateInput($input, collectRules($input, rules), messages);
576
549
  }
577
550
  }
578
- });
579
- if (Object.keys(radioGroups).length > 0) {
580
- for (const key in radioGroups) {
581
- const group = radioGroups[key];
582
- let isRequired = false;
583
- let $checkedInput = null;
584
- for (const keyGroup in group) {
585
- const $radio = group[keyGroup];
586
- if ($radio.hasAttribute('required'))
587
- isRequired = true;
588
- if ($radio.checked) {
589
- if ($checkedInput === null) {
590
- $checkedInput = $radio;
591
- }
592
- else {
593
- return false;
594
- }
595
- }
596
- }
597
- if ($checkedInput === null && isRequired) {
598
- this.showErrorForm('Необходимо указать оценку');
599
- return false;
600
- }
551
+ /* ---------- 2. inputs только с data-custom ---------- */
552
+ const rest = Array.from(this.$el.querySelectorAll('[data-custom-validate]')).filter($i => !processed.has($i));
553
+ for (const $input of rest) {
554
+ processed.add($input);
555
+ yield validateInput($input, collectRules($input, []));
601
556
  }
602
- }
603
- if (this.config.scrollToFirstErroredInput === true)
604
- this.scrollToFirstErroredInput(inputsList);
605
- return isCorrect;
557
+ /* ---------- scroll ---------- */
558
+ if (!ok && this.config.scrollToFirstErroredInput) {
559
+ this.scrollToFirstErroredInput(erroredInputs);
560
+ }
561
+ return ok;
562
+ });
606
563
  }
607
564
  /** Блокирует кнопку отправки данных. */
608
565
  disableSubmit() {
@@ -704,6 +661,28 @@ class Form {
704
661
  }
705
662
  }
706
663
  Form.defaultParams = {};
664
+ Form.defaultValidationSchema = {
665
+ tel: {
666
+ selector: 'input[type="tel"]',
667
+ rules: ['tel'],
668
+ },
669
+ email: {
670
+ selector: 'input[type="email"]',
671
+ rules: ['email'],
672
+ },
673
+ required: {
674
+ selector: '[required]',
675
+ rules: ['required'],
676
+ },
677
+ url: {
678
+ selector: 'input[type="url"]',
679
+ rules: ['url'],
680
+ },
681
+ 'not-numbers': {
682
+ selector: 'input[data-validate="not-numbers"]',
683
+ rules: ['not-numbers'],
684
+ },
685
+ };
707
686
 
708
687
  export { Form as default };
709
688
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-father",
3
- "version": "0.0.22",
3
+ "version": "0.1.0",
4
4
  "description": "Form Father: Библиотека для обработки форм",
5
5
  "type": "module",
6
6
  "scripts": {
package/types/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
+ type ValidationSchema = Record<string, {
2
+ rules: (string | {
3
+ rule: string;
4
+ params?: any;
5
+ })[];
6
+ selector: string;
7
+ messages?: Record<string, string>;
8
+ }>;
1
9
  /** Параметры формы. */
2
10
  interface FormOptions {
3
11
  /**
@@ -51,6 +59,8 @@ interface FormOptions {
51
59
  validatePhone?: (input: HTMLInputElement) => boolean;
52
60
  /** Функция для обёртки отправляемых данных. */
53
61
  wrapData?: (data: Record<string, any>) => Record<string, any>;
62
+ /** Схема валидации: поле → массив правил + override-сообщения */
63
+ validationSchema?: ValidationSchema;
54
64
  }
55
65
  /**
56
66
  * Реализует форму отправки данных.
@@ -66,6 +76,7 @@ export default class Form {
66
76
  private inputs;
67
77
  private $licensesCheckbox;
68
78
  private static defaultParams;
79
+ static defaultValidationSchema: ValidationSchema;
69
80
  /**
70
81
  * Создать форму.
71
82
  *
@@ -107,13 +118,18 @@ export default class Form {
107
118
  */
108
119
  private scrollToFirstErroredInput;
109
120
  /**
110
- * Проверяет все поля ввода на корректность их заполнения.
121
+ * Проверяет все поля ввода.
111
122
  *
112
- * Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
123
+ * Порядок для КАЖДОГО `<input>`:
113
124
  *
114
- * @returns {boolean} Корректно заполнены или нет (true/false).
125
+ * 1. `required` — если атрибут `required`
126
+ * 2. правила из `validationSchema`
127
+ * 3. правила из `data-custom-validate`
128
+ *
129
+ * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
130
+ * валидации, поэтому валидационный цикл больше не проверяет их наличие.
115
131
  */
116
- private checkInputs;
132
+ validate(): Promise<boolean>;
117
133
  /** Блокирует кнопку отправки данных. */
118
134
  private disableSubmit;
119
135
  /** Снимает блокировку с кнопки отправки данных. */
@@ -0,0 +1,11 @@
1
+ type ValidatorFn = (value: string, $input: HTMLInputElement, $form: HTMLElement, params?: any) => boolean | Promise<boolean>;
2
+ interface Validator {
3
+ fn: ValidatorFn;
4
+ defaultMessage: string;
5
+ }
6
+ export declare function registerValidator(name: string, fn: ValidatorFn, defaultMessage: string, { override }?: {
7
+ override?: boolean;
8
+ }): void;
9
+ export declare const getValidator: (name: string) => Validator | undefined;
10
+ export declare function getAllValidators(): Map<string, Validator>;
11
+ export {};