form-father 0.0.22 → 0.1.1

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(e){"use strict";function t(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 r(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 o;"function"==typeof SuppressedError&&SuppressedError;const n=Symbol.for("FormFather.validators"),s=null!==(o=globalThis[n])&&void 0!==o?o:globalThis[n]=new Map;function i(e,t,r,{override:o=!1}={}){!s.has(e)||o?s.set(e,{fn:t,defaultMessage:r}):console.warn(`[FormFather] Validator "${e}" already exists; pass { override: true } to replace`)}const l=e=>s.get(e);i("required",(e=>e.trim().length>0),"Пустое значение"),i("email",(e=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)),"Неверный формат"),i("tel",(e=>/^\+7\d{10}$/.test(e)),"Неверный формат"),i("url",(e=>/^(https?:\/\/).+\..+/.test(e)),"Неверный формат"),i("not-numbers",(e=>!/[0-9]/.test(e)),"Неверный формат");class a{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"},a.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 e=()=>t(this,void 0,void 0,(function*(){var e;const t=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=r(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(r(this.$el)).toString(),i["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":s=r(this.$el);break;case"text/plain":s=Array.from(r(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(r(this.$el)).toString(),i["Content-Type"]="application/x-www-form-urlencoded"}return fetch(t,{method:o,body:s,headers:i})})),o=()=>t(this,void 0,void 0,(function*(){var t,r,o,n,s,i,l,a;if(!this.waitResponse){!0===this.config.showLoaderButton&&this.showLoader(),null===(r=(t=this.config).onSubmit)||void 0===r||r.call(t,this),this.waitResponse=!0;const c=yield e(),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",(e=>t(this,void 0,void 0,(function*(){e.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 t(this,void 0,void 0,(function*(){const e=Object.assign(Object.assign({},a.defaultValidationSchema||{}),this.config.validationSchema||{}),r=e=>console.warn(`[FormFather] Unknown validation rule "${e}"`),o=[],n=new WeakSet;let s=!0;const i=(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 i=[];for(const e of n){const t="string"==typeof e?e:e.rule;l(t)?i.push(e):r(t)}return i},c=(e,r,...n)=>t(this,[e,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 i of t){const{rule:t,params:a}="string"==typeof i?{rule:i,params:void 0}:i,c=l(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[t,r]of Object.entries(e)){const{selector:e,rules:o=[],messages:s={}}=r,l=e?this.$el.querySelectorAll(e):this.$el.querySelectorAll(`[data-validate="${t}"]`);for(const e of Array.from(l))n.has(e)||(n.add(e),yield c(e,i(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,i(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 a.defaultParams={},a.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"]}},e.default=a,e.getAllValidators=function(){return new Map(s)},e.getValidator=l,e.registerValidator=i,Object.defineProperty(e,"__esModule",{value:!0}),e}({});
2
2
  //# sourceMappingURL=FormFather.min.js.map
package/index.d.ts CHANGED
@@ -1,3 +1,22 @@
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
+ declare function registerValidator(name: string, fn: ValidatorFn, defaultMessage: string, { override }?: {
7
+ override?: boolean;
8
+ }): void;
9
+ declare const getValidator: (name: string) => Validator | undefined;
10
+ declare function getAllValidators(): Map<string, Validator>;
11
+
12
+ type ValidationSchema = Record<string, {
13
+ rules: (string | {
14
+ rule: string;
15
+ params?: any;
16
+ })[];
17
+ selector: string;
18
+ messages?: Record<string, string>;
19
+ }>;
1
20
  /** Параметры формы. */
2
21
  interface FormOptions {
3
22
  /**
@@ -51,6 +70,8 @@ interface FormOptions {
51
70
  validatePhone?: (input: HTMLInputElement) => boolean;
52
71
  /** Функция для обёртки отправляемых данных. */
53
72
  wrapData?: (data: Record<string, any>) => Record<string, any>;
73
+ /** Схема валидации: поле → массив правил + override-сообщения */
74
+ validationSchema?: ValidationSchema;
54
75
  }
55
76
  /**
56
77
  * Реализует форму отправки данных.
@@ -66,6 +87,7 @@ declare class Form {
66
87
  private inputs;
67
88
  private $licensesCheckbox;
68
89
  private static defaultParams;
90
+ static defaultValidationSchema: ValidationSchema;
69
91
  /**
70
92
  * Создать форму.
71
93
  *
@@ -107,13 +129,18 @@ declare class Form {
107
129
  */
108
130
  private scrollToFirstErroredInput;
109
131
  /**
110
- * Проверяет все поля ввода на корректность их заполнения.
132
+ * Проверяет все поля ввода.
133
+ *
134
+ * Порядок для КАЖДОГО `<input>`:
111
135
  *
112
- * Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
136
+ * 1. `required` — если атрибут `required`
137
+ * 2. правила из `validationSchema`
138
+ * 3. правила из `data-custom-validate`
113
139
  *
114
- * @returns {boolean} Корректно заполнены или нет (true/false).
140
+ * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
141
+ * валидации, поэтому валидационный цикл больше не проверяет их наличие.
115
142
  */
116
- private checkInputs;
143
+ validate(): Promise<boolean>;
117
144
  /** Блокирует кнопку отправки данных. */
118
145
  private disableSubmit;
119
146
  /** Снимает блокировку с кнопки отправки данных. */
@@ -136,4 +163,4 @@ declare class Form {
136
163
  private isCorrectArguments;
137
164
  }
138
165
 
139
- export { Form as default };
166
+ export { Form as default, getAllValidators, getValidator, registerValidator };
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,29 @@ 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
+ function getAllValidators() {
198
+ return new Map(validators); // копию, чтобы никто не мутировал оригинал
199
+ }
200
+ /* ---------- built-in rules ---------- */
201
+ registerValidator('required', v => v.trim().length > 0, 'Пустое значение');
202
+ registerValidator('email', v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), 'Неверный формат');
203
+ registerValidator('tel', v => /^\+7\d{10}$/.test(v), 'Неверный формат');
204
+ registerValidator('url', v => /^(https?:\/\/).+\..+/.test(v), 'Неверный формат');
205
+ registerValidator('not-numbers', v => !/[0-9]/.test(v), 'Неверный формат');
206
+
228
207
  /**
229
208
  * Реализует форму отправки данных.
230
209
  *
@@ -391,11 +370,12 @@ class Form {
391
370
  }
392
371
  }
393
372
  });
394
- this.$el.addEventListener('submit', e => {
373
+ this.$el.addEventListener('submit', (e) => __awaiter(this, void 0, void 0, function* () {
395
374
  e.preventDefault();
396
- if (this.checkInputs() && (!this.$licensesCheckbox || (this.$licensesCheckbox && this.$licensesCheckbox.checked)))
375
+ const isValid = yield this.validate();
376
+ if (isValid && (!this.$licensesCheckbox || (this.$licensesCheckbox && this.$licensesCheckbox.checked)))
397
377
  submit();
398
- });
378
+ }));
399
379
  }
400
380
  /**
401
381
  * Показывает ошибку для поля ввода.
@@ -494,115 +474,95 @@ class Form {
494
474
  }
495
475
  }
496
476
  /**
497
- * Проверяет все поля ввода на корректность их заполнения.
477
+ * Проверяет все поля ввода.
498
478
  *
499
- * Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
479
+ * Порядок для КАЖДОГО `<input>`:
500
480
  *
501
- * @returns {boolean} Корректно заполнены или нет (true/false).
481
+ * 1. `required` — если атрибут `required`
482
+ * 2. правила из `validationSchema`
483
+ * 3. правила из `data-custom-validate`
484
+ *
485
+ * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
486
+ * валидации, поэтому валидационный цикл больше не проверяет их наличие.
502
487
  */
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;
488
+ validate() {
489
+ return __awaiter(this, void 0, void 0, function* () {
490
+ /* ---------- финальная схема (defaults + config) ---------- */
491
+ const schema = Object.assign(Object.assign({}, (Form.defaultValidationSchema || {})), (this.config.validationSchema || {}));
492
+ const warnUnknown = (r) => console.warn(`[FormFather] Unknown validation rule "${r}"`);
493
+ const erroredInputs = [];
494
+ const processed = new WeakSet();
495
+ let ok = true;
496
+ /* ---------- helpers ---------- */
497
+ /** Собирает и тут же фильтрует unknown-rules */
498
+ const collectRules = ($input, fromSchema = []) => {
499
+ var _a;
500
+ const list = [];
501
+ if ($input.hasAttribute('required'))
502
+ list.push('required'); // 1
503
+ list.push(...fromSchema); // 2
504
+ const custom = ((_a = $input.getAttribute('data-custom-validate')) !== null && _a !== void 0 ? _a : '')
505
+ .split(',')
506
+ .map(s => s.trim())
507
+ .filter(Boolean);
508
+ list.push(...custom); // 3
509
+ /* —–– предварительная фильтрация unknown ––– */
510
+ const filtered = [];
511
+ for (const r of list) {
512
+ const ruleName = typeof r === 'string' ? r : r.rule;
513
+ if (getValidator(ruleName))
514
+ filtered.push(r);
515
+ else
516
+ warnUnknown(ruleName);
518
517
  }
519
- }
520
- else if (inputType === 'checkbox') {
521
- if ($input.hasAttribute('required') && !$input.checked) {
522
- this.showError($input, 'Необходимо согласие');
523
- inputsList.push($input);
524
- isCorrect = false;
518
+ return filtered;
519
+ };
520
+ const validateInput = ($input_1, rules_1, ...args_1) => __awaiter(this, [$input_1, rules_1, ...args_1], void 0, function* ($input, rules, msgs = {}) {
521
+ var _a;
522
+ const empty = $input.type === 'checkbox' || $input.type === 'radio' ? !$input.checked : $input.value.trim().length === 0;
523
+ if (empty && !rules.includes('required')) {
524
+ this.hideError($input);
525
+ return;
525
526
  }
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;
527
+ for (const r of rules) {
528
+ const { rule, params } = typeof r === 'string' ? { rule: r, params: undefined } : r;
529
+ const vd = getValidator(rule); // гарантированно существует
530
+ const passed = yield vd.fn($input.value, $input, this.$el, params);
531
+ if (!passed) {
532
+ ok = false;
533
+ const msg = (_a = msgs[rule]) !== null && _a !== void 0 ? _a : vd.defaultMessage;
534
+ this.showError($input, msg);
535
+ erroredInputs.push($input);
536
+ return; // первую ошибку отобразили
570
537
  }
571
538
  }
572
- else if ($input.hasAttribute('required')) {
573
- this.showError($input, 'Пустое значение');
574
- inputsList.push($input);
575
- isCorrect = false;
539
+ this.hideError($input);
540
+ });
541
+ /* ---------- 1. inputs из schema (config + defaults) ---------- */
542
+ for (const [key, def] of Object.entries(schema)) {
543
+ const { selector, rules = [], messages = {} } = def;
544
+ const nodeList = selector
545
+ ? this.$el.querySelectorAll(selector) // явный CSS-селектор
546
+ : this.$el.querySelectorAll(`[data-validate="${key}"]`); // fallback по name
547
+ for (const $input of Array.from(nodeList)) {
548
+ if (processed.has($input))
549
+ continue;
550
+ processed.add($input);
551
+ yield validateInput($input, collectRules($input, rules), messages);
576
552
  }
577
553
  }
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
- }
554
+ /* ---------- 2. inputs только с data-custom ---------- */
555
+ const rest = Array.from(this.$el.querySelectorAll('[data-custom-validate]')).filter($i => !processed.has($i));
556
+ for (const $input of rest) {
557
+ processed.add($input);
558
+ yield validateInput($input, collectRules($input, []));
601
559
  }
602
- }
603
- if (this.config.scrollToFirstErroredInput === true)
604
- this.scrollToFirstErroredInput(inputsList);
605
- return isCorrect;
560
+ /* ---------- scroll ---------- */
561
+ if (!ok && this.config.scrollToFirstErroredInput) {
562
+ this.scrollToFirstErroredInput(erroredInputs);
563
+ }
564
+ return ok;
565
+ });
606
566
  }
607
567
  /** Блокирует кнопку отправки данных. */
608
568
  disableSubmit() {
@@ -704,6 +664,28 @@ class Form {
704
664
  }
705
665
  }
706
666
  Form.defaultParams = {};
667
+ Form.defaultValidationSchema = {
668
+ tel: {
669
+ selector: 'input[type="tel"]',
670
+ rules: ['tel'],
671
+ },
672
+ email: {
673
+ selector: 'input[type="email"]',
674
+ rules: ['email'],
675
+ },
676
+ required: {
677
+ selector: '[required]',
678
+ rules: ['required'],
679
+ },
680
+ url: {
681
+ selector: 'input[type="url"]',
682
+ rules: ['url'],
683
+ },
684
+ 'not-numbers': {
685
+ selector: 'input[data-validate="not-numbers"]',
686
+ rules: ['not-numbers'],
687
+ },
688
+ };
707
689
 
708
- export { Form as default };
690
+ export { Form as default, getAllValidators, getValidator, registerValidator };
709
691
  //# 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.1",
4
4
  "description": "Form Father: Библиотека для обработки форм",
5
5
  "type": "module",
6
6
  "scripts": {
package/types/index.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ export * from './validators';
2
+ type ValidationSchema = Record<string, {
3
+ rules: (string | {
4
+ rule: string;
5
+ params?: any;
6
+ })[];
7
+ selector: string;
8
+ messages?: Record<string, string>;
9
+ }>;
1
10
  /** Параметры формы. */
2
11
  interface FormOptions {
3
12
  /**
@@ -51,6 +60,8 @@ interface FormOptions {
51
60
  validatePhone?: (input: HTMLInputElement) => boolean;
52
61
  /** Функция для обёртки отправляемых данных. */
53
62
  wrapData?: (data: Record<string, any>) => Record<string, any>;
63
+ /** Схема валидации: поле → массив правил + override-сообщения */
64
+ validationSchema?: ValidationSchema;
54
65
  }
55
66
  /**
56
67
  * Реализует форму отправки данных.
@@ -66,6 +77,7 @@ export default class Form {
66
77
  private inputs;
67
78
  private $licensesCheckbox;
68
79
  private static defaultParams;
80
+ static defaultValidationSchema: ValidationSchema;
69
81
  /**
70
82
  * Создать форму.
71
83
  *
@@ -107,13 +119,18 @@ export default class Form {
107
119
  */
108
120
  private scrollToFirstErroredInput;
109
121
  /**
110
- * Проверяет все поля ввода на корректность их заполнения.
122
+ * Проверяет все поля ввода.
111
123
  *
112
- * Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
124
+ * Порядок для КАЖДОГО `<input>`:
113
125
  *
114
- * @returns {boolean} Корректно заполнены или нет (true/false).
126
+ * 1. `required` — если атрибут `required`
127
+ * 2. правила из `validationSchema`
128
+ * 3. правила из `data-custom-validate`
129
+ *
130
+ * Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
131
+ * валидации, поэтому валидационный цикл больше не проверяет их наличие.
115
132
  */
116
- private checkInputs;
133
+ validate(): Promise<boolean>;
117
134
  /** Блокирует кнопку отправки данных. */
118
135
  private disableSubmit;
119
136
  /** Снимает блокировку с кнопки отправки данных. */
@@ -135,4 +152,3 @@ export default class Form {
135
152
  */
136
153
  private isCorrectArguments;
137
154
  }
138
- export {};
@@ -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 {};