form-father 0.2.11 → 0.4.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 +98 -0
- package/README.md +110 -2
- package/demos/index.html +153 -0
- package/demos/main.js +103 -0
- package/demos/styles.css +221 -0
- package/dist/FormFather.min.js +1 -1
- package/dist/index.d.ts +183 -7
- package/dist/index.js +742 -129
- package/dist/index.js.map +1 -1
- package/dist/types/adapters.d.ts +28 -0
- package/dist/types/index.d.ts +153 -4
- package/dist/types/validators.d.ts +3 -2
- package/docs/en/README.md +225 -0
- package/docs/recipes/README.md +115 -0
- package/package.json +2 -2
package/demos/styles.css
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light;
|
|
3
|
+
--bg: #f5f7fb;
|
|
4
|
+
--panel: #ffffff;
|
|
5
|
+
--text: #172033;
|
|
6
|
+
--muted: #637083;
|
|
7
|
+
--line: #d9e0ea;
|
|
8
|
+
--accent: #176b87;
|
|
9
|
+
--accent-strong: #0f5268;
|
|
10
|
+
--danger: #b3261e;
|
|
11
|
+
--success: #176b3a;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
margin: 0;
|
|
20
|
+
background: var(--bg);
|
|
21
|
+
color: var(--text);
|
|
22
|
+
font: 16px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.demo-header {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: flex-end;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
gap: 24px;
|
|
30
|
+
padding: 40px min(6vw, 72px) 24px;
|
|
31
|
+
border-bottom: 1px solid var(--line);
|
|
32
|
+
background: #ffffff;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.demo-header h1,
|
|
36
|
+
.panel-title h2 {
|
|
37
|
+
margin: 0;
|
|
38
|
+
letter-spacing: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.demo-header h1 {
|
|
42
|
+
max-width: 760px;
|
|
43
|
+
font-size: clamp(32px, 4vw, 54px);
|
|
44
|
+
line-height: 1.05;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.eyebrow {
|
|
48
|
+
margin: 0 0 8px;
|
|
49
|
+
color: var(--accent);
|
|
50
|
+
font-size: 13px;
|
|
51
|
+
font-weight: 700;
|
|
52
|
+
letter-spacing: 0;
|
|
53
|
+
text-transform: uppercase;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.github-link,
|
|
57
|
+
button {
|
|
58
|
+
display: inline-flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
min-height: 42px;
|
|
62
|
+
padding: 0 16px;
|
|
63
|
+
border: 1px solid var(--accent);
|
|
64
|
+
border-radius: 6px;
|
|
65
|
+
background: var(--accent);
|
|
66
|
+
color: #ffffff;
|
|
67
|
+
font: inherit;
|
|
68
|
+
font-weight: 700;
|
|
69
|
+
text-decoration: none;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.github-link {
|
|
74
|
+
background: #ffffff;
|
|
75
|
+
color: var(--accent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
button:hover,
|
|
79
|
+
button:focus-visible {
|
|
80
|
+
background: var(--accent-strong);
|
|
81
|
+
border-color: var(--accent-strong);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.github-link:hover,
|
|
85
|
+
.github-link:focus-visible {
|
|
86
|
+
border-color: var(--accent-strong);
|
|
87
|
+
color: var(--accent-strong);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.demo-grid {
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
93
|
+
gap: 18px;
|
|
94
|
+
padding: 24px min(6vw, 72px);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.demo-panel,
|
|
98
|
+
.demo-output {
|
|
99
|
+
border: 1px solid var(--line);
|
|
100
|
+
border-radius: 8px;
|
|
101
|
+
background: var(--panel);
|
|
102
|
+
padding: 22px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.panel-title {
|
|
106
|
+
margin-bottom: 18px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.panel-title h2 {
|
|
110
|
+
font-size: 22px;
|
|
111
|
+
line-height: 1.2;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
form {
|
|
115
|
+
display: grid;
|
|
116
|
+
gap: 14px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.field {
|
|
120
|
+
display: grid;
|
|
121
|
+
gap: 6px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.field span {
|
|
125
|
+
color: var(--muted);
|
|
126
|
+
font-size: 14px;
|
|
127
|
+
font-weight: 700;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.input {
|
|
131
|
+
width: 100%;
|
|
132
|
+
min-height: 42px;
|
|
133
|
+
padding: 8px 10px;
|
|
134
|
+
border: 1px solid var(--line);
|
|
135
|
+
border-radius: 6px;
|
|
136
|
+
background: #ffffff;
|
|
137
|
+
color: var(--text);
|
|
138
|
+
font: inherit;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.input:focus {
|
|
142
|
+
border-color: var(--accent);
|
|
143
|
+
outline: 3px solid rgba(23, 107, 135, 0.16);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.input[aria-invalid="true"] {
|
|
147
|
+
border-color: var(--danger);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.input[aria-busy="true"] {
|
|
151
|
+
opacity: 0.72;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.input[data-form-father-state="valid"] {
|
|
155
|
+
border-color: var(--success);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.input[data-form-father-state="invalid"] {
|
|
159
|
+
border-color: var(--danger);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
[data-form-father-error],
|
|
163
|
+
.field-error {
|
|
164
|
+
color: var(--danger);
|
|
165
|
+
font-size: 13px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.input__wrapper--error .input {
|
|
169
|
+
border-color: var(--danger);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.button--loading {
|
|
173
|
+
opacity: 0.74;
|
|
174
|
+
pointer-events: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.button__loader {
|
|
178
|
+
display: inline-flex;
|
|
179
|
+
width: 18px;
|
|
180
|
+
height: 18px;
|
|
181
|
+
margin-right: 8px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.button__loader svg {
|
|
185
|
+
width: 18px;
|
|
186
|
+
height: 18px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.error-block-under-input__wrapper {
|
|
190
|
+
color: var(--danger);
|
|
191
|
+
font-size: 14px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.demo-output {
|
|
195
|
+
margin: 0 min(6vw, 72px) 40px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pre {
|
|
199
|
+
min-height: 130px;
|
|
200
|
+
margin: 0;
|
|
201
|
+
overflow: auto;
|
|
202
|
+
padding: 16px;
|
|
203
|
+
border: 1px solid var(--line);
|
|
204
|
+
border-radius: 8px;
|
|
205
|
+
background: #101820;
|
|
206
|
+
color: #d8f3dc;
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
white-space: pre-wrap;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@media (max-width: 760px) {
|
|
212
|
+
.demo-header {
|
|
213
|
+
display: grid;
|
|
214
|
+
align-items: start;
|
|
215
|
+
padding-top: 28px;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.demo-grid {
|
|
219
|
+
grid-template-columns: 1fr;
|
|
220
|
+
}
|
|
221
|
+
}
|
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,r,i){return new(r||(r=Promise))((function(n,s){function o(t){try{l(i.next(t))}catch(t){s(t)}}function a(t){try{l(i.throw(t))}catch(t){s(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(o,a)}l((i=i.apply(t,e||[])).next())}))}function r(t,e){if(null==t)return null;let r=t;for(;r;){if(r.matches(e))return r;r=r.parentElement}return null}"function"==typeof SuppressedError&&SuppressedError;const i=["http:","https:"];function n(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function s(t){return!!t&&("localhost"===t||function(t){const e=t.split(".");return 4===e.length&&e.every((t=>{if(!/^\d{1,3}$/.test(t))return!1;const e=Number(t);return e>=0&&e<=255}))}(t)||function(t){if(!t.startsWith("[")||!t.endsWith("]"))return!1;const e=t.slice(1,-1);return e.includes(":")&&/^[\da-f:.]+$/i.test(e)}(t)||function(t){const e=t.split(".");return e.length>=2&&e.every(n)}(t))}function o(t){const e=function(t){const e=t.trim();return 0===e.length?"":e.startsWith("//")?`https:${e}`:/^[a-z][a-z\d+.-]*:\/\//i.test(e)?e:`https://${e}`}(t);if(0===e.length)return!1;try{const t=new URL(e);return i.includes(t.protocol)&&s(t.hostname)}catch(t){return!1}}function a(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),r=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.disabled||t.hasAttribute("data-no-serialize"))return;if(t instanceof HTMLButtonElement)return;const{name:e,type:i}=t;if("file"===i)if(t.multiple){const i=t.files;if(i)for(let t=0;t<i.length;t++)r.append(`${e}[${t}]`,i[t])}else{const i=t.files;i&&i[0]&&r.append(e,i[0])}else{let n;if("select-one"===i||"select-multiple"===i){const s=t;if("select-multiple"===i){Array.from(s.selectedOptions).map((t=>t.value)).forEach(((t,i)=>{r.append(`${e}[${i}]`,t)}))}else n=s.value,n&&r.append(e,n)}else if("tel"===i)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===i||"radio"===i){if(!t.checked)return;n=t.value}else if("date"===i){const e=t.getAttribute("data-date-format");n=e?function(t,e){const r=new Date(t),i=t=>t.toString().padStart(2,"0"),n=i(r.getDate()),s=i(r.getMonth()+1),o=r.getFullYear().toString();return e.replace("dd",n).replace("mm",s).replace("yyyy",o)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==i&&"select-multiple"!==i&&r.append(e,n)}})),r}function l(t){if(Object.prototype.hasOwnProperty.call(t,"redirect-url"))if(t["redirect-url-delay"]){const e=Number(t["redirect-url-delay"]);setTimeout((()=>{window.location.href=t["redirect-url"]}),e)}else window.location.href=t["redirect-url"];else if(Object.prototype.hasOwnProperty.call(t,"reload")){if(!0===t.reload)if(t["reload-delay"]){const e=Number(t["reload-delay"]);setTimeout((()=>{window.location.reload()}),e)}else window.location.reload()}else if(Object.prototype.hasOwnProperty.call(t,"toast")){const e=t.toast;(Array.isArray(e)?e:[e]).forEach((t=>{window.showToast(t)}))}else Object.prototype.hasOwnProperty.call(t,"error-toast")&&window.showToast(t["error-toast"])}function d(t){const e={};return new FormData(t).forEach(((t,r)=>{e.hasOwnProperty(r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}var c;const u=Symbol.for("FormFather.validators"),h=null!==(c=globalThis[u])&&void 0!==c?c:globalThis[u]=new Map;function p(t,e,r,{override:i=!1}={}){!h.has(t)||i?h.set(t,{fn:e,defaultMessage:r}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const f=t=>h.get(t);function m(t,e,r){h.has(t)||h.set(t,{fn:e,defaultMessage:r})}function g(t,e){return"function"==typeof e?e(t):e||function(t){var e;if(!t||"object"!=typeof t)return;const r=t.issues||t.errors;if(Array.isArray(r)){const t=null===(e=r.find((t=>"string"==typeof(null==t?void 0:t.message))))||void 0===e?void 0:e.message;if(t)return t}return"string"==typeof t.message?t.message:void 0}(t)}function y(t,r={}){return(i,n,s,o)=>e(this,void 0,void 0,(function*(){const e=r.mapValue?r.mapValue(i,n,s,o):i;try{if("function"==typeof t)return a=yield t(e),l=g(void 0,r.message),"object"==typeof a&&null!==a?a:void 0===a||!!a||{valid:!1,message:l};if("function"==typeof t.safeParse){const i=yield t.safeParse(e);return!!i.success||{valid:!1,message:g(i.error||i,r.message)}}if("function"==typeof t.parse)return yield t.parse(e),!0}catch(t){return{valid:!1,message:g(t,r.message)}}var a,l;return{valid:!1,message:g(void 0,r.message)}}))}var b;m("required",((t,e,r)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=r.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),m("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),m("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),m("url",(t=>o(t)),"Неверный формат"),m("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const v=Symbol.for("formfather.shared"),E=null!==(b=globalThis[v])&&void 0!==b?b:globalThis[v]={defaultParams:{},defaultValidationSchema:void 0},w="_form";let S=0;const F=new WeakMap,A={tel:{selector:'input[type="tel"]',rules:["tel"]},email:{selector:'input[type="email"]',rules:["email"]},required:{selector:"[required]",rules:["required"]},url:{selector:'input[type="url"]',rules:["url"]},"not-numbers":{selector:'input[data-validate="not-numbers"]',rules:["not-numbers"]}};class V{static get defaultParams(){return E.defaultParams}static set defaultParams(t){E.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=E.defaultValidationSchema)&&void 0!==t?t:E.defaultValidationSchema=A}static set defaultValidationSchema(t){E.defaultValidationSchema=t}constructor(t,e={}){this.$submits=[],this.waitResponse=!1,this.inputs=null,this.fieldEventHandlers=[],this.liveValidationTimers=new WeakMap,this.fieldValidationTokens=new WeakMap,this.fieldValidationTokenCounter=0,this.errors=[],this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=e;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,focusFirstErroredInput:!1,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary",validateOn:"submit",revalidateOn:["input","change"],validationDebounce:0,errorContainerAttribute:"data-error-container",validationStateAttribute:"data-form-father-state",observeMutations:!1},V.defaultParams,e),this.$el?(this.$submits=this.findSubmitElements(),this.isCorrectArguments()&&(this.bindSubmitHandlers(),this.initialization(),this.bindFieldValidationEvents(),this.setupMutationObserver(),F.set(this.$el,this))):console.warn("Empty $el")}getOptions(){return this.options}static initAll(t="form[data-form-father]",e={}){return"undefined"==typeof document?[]:Array.from(document.querySelectorAll(t)).filter((t=>"FORM"===t.tagName)).map((t=>{const r=F.get(t);return r?(r.updateOptions(e),r):new V(t,e)}))}updateOptions(t){return this.options=Object.assign(Object.assign({},this.options),t),this.config=Object.assign(Object.assign({},this.config),t),this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents(),this.setupMutationObserver(),this}getErrors(){return this.errors.map((t=>Object.assign({},t)))}getValues(){return d(this.$el)}normalizeValueList(t){return Array.isArray(t)?t.map(String):[String(t)]}setValues(t){return this.getAllFields().forEach((e=>{if(!e.name||!Object.prototype.hasOwnProperty.call(t,e.name))return;const r=t[e.name];if(!(e instanceof HTMLInputElement&&"file"===e.type)){if(e instanceof HTMLInputElement&&"checkbox"===e.type)e.checked="boolean"==typeof r?r:this.normalizeValueList(r).includes(e.value);else if(e instanceof HTMLInputElement&&"radio"===e.type)e.checked=String(r)===e.value;else if(e instanceof HTMLSelectElement&&e.multiple){const t=this.normalizeValueList(r);Array.from(e.options).forEach((e=>{e.selected=t.includes(e.value)}))}else e.value=null==r?"":String(r);e.dispatchEvent(new Event("input",{bubbles:!0})),e.dispatchEvent(new Event("change",{bubbles:!0}))}})),this}clearErrors(){return this.errors=[],this.getAllFields().forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),this.hideErrorForm(),this}isErrorResponse(t){return!!t&&"object"==typeof t&&"string"==typeof t.name&&"string"==typeof t["error-msg"]}normalizeValidationIssues(t,e){return null==t||!0===t?[]:!1===t?[{message:"Некорректные данные формы",source:e}]:"string"==typeof t?[{message:t,source:e}]:Array.isArray(t)?t.flatMap((t=>this.normalizeValidationIssues(t,e))):this.isErrorResponse(t)?[{field:t.name,rule:e,message:t["error-msg"],source:e}]:"object"==typeof t&&"message"in t&&"string"==typeof t.message?[Object.assign(Object.assign({},t),{source:t.source||e})]:"object"==typeof t?Object.entries(t).map((([t,r])=>({field:t,rule:e,message:r,source:e}))):[]}applyValidationIssue(t,e){const r=t.source||e,i=t.rule||r,n=t.field||w;if(n===w)return this.setErrorRecord(w,i,t.message,r),this.showErrorForm(t.message),null;const s=this.findFieldByName(n);return s?(this.showFieldError(s,t.message,r,i),s):(this.setErrorRecord(n,i,t.message,r),console.warn(`Не найдено поле с именем: ${n}, для вывода ошибки: ${t.message}`),null)}setErrors(t,e="server"){return this.normalizeValidationIssues(t,e).forEach((t=>{this.applyValidationIssue(t,e)})),this}validateFormValidators(){return e(this,void 0,void 0,(function*(){const t=this.config.formValidators?Array.isArray(this.config.formValidators)?this.config.formValidators:[this.config.formValidators]:[],e=[];let r=!0;for(const i of t){const t=yield i({form:this,formElement:this.$el,values:this.getValues(),errors:this.getErrors()}),n=this.normalizeValidationIssues(t,"client");0!==n.length&&(r=!1,n.forEach((t=>{const r=this.applyValidationIssue(t,"client");r&&e.push(r)})))}return{ok:r,erroredInputs:e}}))}reset(t={}){return this.$el.reset(),this.refreshControls(),!1!==t.clearErrors&&this.clearErrors(),this}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}refreshControls(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector)}findSubmitElements(){return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'))}bindSubmitHandlers(){this._onSubmitClickHandler||(this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)}))),this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this.$submits=this.findSubmitElements(),this.$submits.forEach((t=>{t.addEventListener("click",this._onSubmitClickHandler)}))}initialization(){this.refreshControls(),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){yield this.handleSubmitEvent(t)})),this.$el.addEventListener("submit",this._onSubmitHandler)}handleSubmitEvent(t){return e(this,void 0,void 0,(function*(){var e,r,i,n,s,o;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const a=yield this.validate();return null===(n=(i=this.config).onAfterValidate)||void 0===n||n.call(i,a,this),a?this.submit():(null===(o=(s=this.config).onValidationError)||void 0===o||o.call(s,this.getErrors(),this),{success:!1,error:this.getErrors()})}))}normalizeTriggers(t,e){const r=null==t?e:Array.isArray(t)?t:[t];return Array.from(new Set(r))}clearFieldValidationEvents(){this.fieldEventHandlers.forEach((({$input:t,event:e,handler:r})=>{t.removeEventListener(e,r)})),this.fieldEventHandlers=[]}bindFieldValidationEvents(){this.clearFieldValidationEvents();const t=this.normalizeTriggers(this.config.validateOn,["submit"]).filter((t=>"submit"!==t)),e=this.normalizeTriggers(this.config.revalidateOn,["input","change"]).filter((t=>"submit"!==t)),r=Array.from(new Set([...t,...e]));if(0===r.length)return;this.getAllFields().forEach((e=>{r.forEach((r=>{const i=()=>{(t.includes(r)||this.hasFieldError(e))&&this.scheduleFieldValidation(e)};e.addEventListener(r,i),this.fieldEventHandlers.push({$input:e,event:r,handler:i})}))}))}scheduleFieldValidation(t){const e=this.liveValidationTimers.get(t);e&&clearTimeout(e);const r=Math.max(0,Number(this.config.validationDebounce)||0),i=()=>{this.validateField(t)};if(0===r)return void i();const n=setTimeout(i,r);this.liveValidationTimers.set(t,n)}setupMutationObserver(){var t;null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0,this.config.observeMutations&&"undefined"!=typeof MutationObserver&&(this.mutationObserver=new MutationObserver((()=>{this.refreshControls(),this.bindSubmitHandlers(),this.bindFieldValidationEvents()})),this.mutationObserver.observe(this.$el,{childList:!0,subtree:!0}))}getErrorContainer(t){const e=this.config.errorContainerAttribute||"data-error-container",r=t.getAttribute(e);if(!r)return null;try{return document.querySelector(r)}catch(t){return console.warn(`[FormFather] Invalid error container selector "${r}"`),null}}showError(t,e){t.setAttribute("aria-invalid","true");const i=this.getErrorContainer(t);if(i)return i.textContent=e,i.hasAttribute("role")||i.setAttribute("role","alert"),void i.removeAttribute("hidden");const n=r(t,this.config.inputWrapperSelector);if(!n)return void console.warn(`Не найдена обёртка для поля с именем: ${t.name||t.type}`);const s=n.showError;if("function"==typeof s)return void s.call(n,e);n.classList.add("input__wrapper--error");let o=n.querySelector("[data-form-father-error]");o||(o=document.createElement("div"),o.setAttribute("data-form-father-error",""),o.setAttribute("role","alert"),t.insertAdjacentElement("afterend",o)),o.textContent=e}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+S++,r=`\n\t\t\t\t<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t<defs>\n\t\t\t\t\t<linearGradient id="${e}" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t\t</linearGradient>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#${e})"></path>\n\t\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t\t</g>\n\t\t\t\t\t</g>\n\t\t\t\t</svg>\n\t\t\t`,i=document.createElement("span");i.innerHTML=r,i.className="button__loader",t.insertAdjacentElement("afterbegin",i),requestAnimationFrame((()=>{i.setAttribute("data-showed","")}))}))}hideLoader(){this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&(e.classList.remove("active"),e.removeAttribute("data-showed"),setTimeout((()=>{e.remove()}),250))}))}hideError(t){var e;const i=r(t,this.config.inputWrapperSelector)||t.parentElement,n=this.getErrorContainer(t);t.removeAttribute("aria-invalid"),n&&(n.textContent="",n.setAttribute("hidden","")),null===(e=null==i?void 0:i.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==i||i.classList.remove("input__wrapper--error"),i&&i.classList.contains("input__wrapper")&&i.getAttribute("data-type-error")}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input, textarea, select"));let r=null,i=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<i&&(i=n,r=t)})),r){const t=r.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,i=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:i,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}focusFirstErroredInput(t){var e;if(0===t.length)return;const r=Array.from(this.$el.querySelectorAll("input, textarea, select")),i=t.slice().sort(((t,e)=>r.indexOf(t)-r.indexOf(e)))[0];null===(e=null==i?void 0:i.focus)||void 0===e||e.call(i,{preventScroll:!0})}getValidationSchema(){return Object.assign(Object.assign({},V.defaultValidationSchema||{}),this.config.validationSchema||{})}getAllFields(t=this.$el){return Array.from(t.querySelectorAll("input, textarea, select"))}getFieldKey(t){return t.name||t.id||t.getAttribute("data-field")||t.type||"field"}hasFieldError(t){const e=this.getFieldKey(t);return"true"===t.getAttribute("aria-invalid")||this.errors.some((t=>t.field===e))}clearFieldErrorRecord(t){const e=this.getFieldKey(t);this.errors=this.errors.filter((t=>t.field!==e))}setErrorRecord(t,e,r,i){this.errors.push({field:t,rule:e,message:r,source:i})}getValidationStateAttribute(){return this.config.validationStateAttribute||"data-form-father-state"}setFieldValidationState(t,e){const r=this.getValidationStateAttribute();e?t.setAttribute(r,e):t.removeAttribute(r)}startFieldValidation(t){const e=++this.fieldValidationTokenCounter;return this.fieldValidationTokens.set(t,e),t.setAttribute("aria-busy","true"),this.setFieldValidationState(t,"validating"),e}isLatestFieldValidation(t,e){return null==e||this.fieldValidationTokens.get(t)===e}finishFieldValidation(t,e,r){this.isLatestFieldValidation(t,e)&&(t.removeAttribute("aria-busy"),this.setFieldValidationState(t,r?"valid":"invalid"))}clearFieldValidationState(t){this.fieldValidationTokens.delete(t),t.removeAttribute("aria-busy"),this.setFieldValidationState(t,null)}setValidationError(t,e,r,i){this.clearFieldErrorRecord(t),this.setErrorRecord(this.getFieldKey(t),e,r,i)}resolveField(t){return"string"!=typeof t?t:this.findFieldByName(t)}parseRuleString(t){const[e,...r]=t.split(":").map((t=>t.trim()));return r.length>0?{rule:e,params:1===r.length?r[0]:r}:e}parseValidationAttribute(t){return(null!=t?t:"").split(/[,\s|]+/).map((t=>t.trim())).filter(Boolean).map((t=>this.parseRuleString(t)))}getRuleName(t){return"string"==typeof t?t:t.rule}getRuleIdentity(t){return JSON.stringify(t)}collectRules(t,e=[],r=new Set){const i=[];t.hasAttribute("required")&&i.push("required"),i.push(...e);const n=this.parseValidationAttribute(t.getAttribute("data-validate")).filter((t=>{const e=this.getRuleName(t);return f(e)||!r.has(e)}));i.push(...n),i.push(...this.parseValidationAttribute(t.getAttribute("data-custom-validate")));const s=[],o=new Set;for(const t of i){const e=this.getRuleName(t);if(!f(e)){console.warn(`[FormFather] Unknown validation rule "${e}"`);continue}const r=this.getRuleIdentity(t);o.has(r)||(o.add(r),s.push(t))}return s}getRuleMessage(t,e,r,i,n){const s=t.getAttribute(`data-error-${e}`);return n||s||r[e]||i}fieldMatchesSchemaDefinition(t,e,r){if(r.selector)try{return t.matches(r.selector)}catch(t){return console.warn(`[FormFather] Invalid validation selector "${r.selector}"`),!1}return this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===e))}getFieldValidationConfig(t,e=this.getValidationSchema()){const r=[],i={};for(const[n,s]of Object.entries(e))this.fieldMatchesSchemaDefinition(t,n,s)&&(r.push(...s.rules),Object.assign(i,s.messages||{}));return{rules:this.collectRules(t,r,new Set(Object.keys(e))),messages:i}}getValidationFields(t,e){const r=new Set,i=t=>{Array.from(t).forEach((t=>{(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&r.add(t)}))};for(const[n,s]of Object.entries(e))if(s.selector)try{i(t.querySelectorAll(s.selector))}catch(t){console.warn(`[FormFather] Invalid validation selector "${s.selector}"`)}else this.getAllFields(t).filter((t=>this.parseValidationAttribute(t.getAttribute("data-validate")).some((t=>this.getRuleName(t)===n)))).forEach((t=>r.add(t)));return i(t.querySelectorAll("[required], [data-validate], [data-custom-validate]")),Array.from(r)}validateInput(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el,n){if(t.hasAttribute("data-no-validate"))return!0;const s=t instanceof HTMLInputElement&&("checkbox"===t.type||"radio"===t.type)?!t.checked:0===t.value.trim().length,o=e.some((t=>"required"===this.getRuleName(t)));if(s&&!o)return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t);for(const s of e){const{rule:e,params:o}="string"==typeof s?{rule:s,params:void 0}:{rule:s.rule,params:s.params},a=f(e);if(!a)continue;const l=yield a.fn(t.value,t,i,o),d="object"==typeof l&&null!==l,c=d?!0===l.valid:!!l;if(!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(d&&l.effect&&(yield l.effect({value:t.value,$input:t,$form:i,params:o})),!this.isLatestFieldValidation(t,n))return!this.hasFieldError(t);if(!c){const i=l,n=this.getRuleMessage(t,e,r,a.defaultMessage,d&&"string"==typeof i.message?i.message:void 0);return this.setValidationError(t,e,n,"client"),this.showError(t,n),!1}if(d&&l.stopOthers)return!0}return this.isLatestFieldValidation(t,n)?(this.clearFieldErrorRecord(t),this.hideError(t),!0):!this.hasFieldError(t)}))}runFieldValidation(t,r){return e(this,arguments,void 0,(function*(t,e,r={},i=this.$el){const n=this.startFieldValidation(t);try{const s=yield this.validateInput(t,e,r,i,n);return this.finishFieldValidation(t,n,s),s}catch(e){throw this.isLatestFieldValidation(t,n)&&this.clearFieldValidationState(t),e}}))}getRadioGroup(t,e){return t instanceof HTMLInputElement&&"radio"===t.type&&t.name?Array.from(e.querySelectorAll('input[type="radio"]')).filter((e=>e.name===t.name)):[]}showFieldError(t,e,r="manual",i=r){const n=this.resolveField(t);return n?(this.setValidationError(n,i,e,r),this.showError(n,e),n.removeAttribute("aria-busy"),this.setFieldValidationState(n,"invalid"),!0):(console.warn(`[FormFather] Field "${String(t)}" not found`),!1)}validateField(t){return e(this,void 0,void 0,(function*(){const e=this.resolveField(t);if(!e)return console.warn(`[FormFather] Field "${String(t)}" not found`),!1;const{rules:r,messages:i}=this.getFieldValidationConfig(e);return this.runFieldValidation(e,r,i)}))}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const e=this.getValidationSchema(),r=[],i=new WeakSet;let n=!0;this.errors=[];for(const s of this.getValidationFields(t,e)){if(i.has(s))continue;const o=this.getRadioGroup(s,t);o.length>0?o.forEach((t=>i.add(t))):i.add(s);const{rules:a,messages:l}=this.getFieldValidationConfig(s,e);(yield this.runFieldValidation(s,a,l,t))||(n=!1,r.push(s))}const s=yield this.validateFormValidators();s.ok||(n=!1,r.push(...s.erroredInputs));const o=r.filter((t=>!t.hasAttribute("data-no-error-scroll")));return!n&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(o),!n&&this.config.focusFirstErroredInput&&this.focusFirstErroredInput(o),n}))}disableSubmit(){this.$submits.forEach((t=>{t.setAttribute("disabled","")}))}enableSubmit(){this.$submits.forEach((t=>{t.removeAttribute("disabled")}))}clearInputs(){var t;null===(t=this.inputs)||void 0===t||t.forEach((t=>{var e;const r=t;"radio"===r.type||"checkbox"===r.type?r.checked=!1:(r.value="",null===(e=r.parentElement)||void 0===e||e.classList.remove("filled"),r.dispatchEvent(new Event("input")))}))}_prefersReducedMotion(){var t;return"undefined"!=typeof window&&(null===(t=window.matchMedia)||void 0===t?void 0:t.call(window,"(prefers-reduced-motion: reduce)").matches)}_canAnimate(t){return!(!t||!("animate"in t)||this._prefersReducedMotion())}_cancelAnimations(t){var e;const r=t;null===(e=null==r?void 0:r.getAnimations)||void 0===e||e.call(r).forEach((t=>t.cancel()))}showErrorForm(t){var e;const r=this.$el.querySelectorAll(this.config.inputWrapperSelector),i=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||i&&i.nextElementSibling!==n?(n=document.createElement("div"),n.className="error-block-under-input__wrapper",n.innerHTML='\n <div class="error-block-under-input error-block-under-input--warning" style="display:grid;grid-template-columns:auto 1fr;gap:8px;align-items:start;">\n <div class="error-block-under-input__icon-wrapper" style="position:relative;width:20px;height:20px;">\n <span class="error-block-under-input__icon" style="display:block;width:20px;height:20px;"></span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:block;position:absolute;inset:0;"></span>\n </div>\n <p class="error-block-under-input__text" style="margin:0;">\n <span class="error-block-under-input__main-text"></span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>\n ',Object.assign(n.style,{boxSizing:"border-box",overflow:"hidden",height:"0px",paddingTop:"0px",opacity:"0"}),i?i.insertAdjacentElement("afterend",n):this.$el.appendChild(n)):(n.style.boxSizing="border-box",n.style.overflow="hidden"),n.querySelector(".error-block-under-input__main-text").textContent=t;const s=n.querySelector(".error-block-under-input__secondary-text");s&&(s.textContent=""),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const o=n.style.height,a=n.style.paddingTop;n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`;const l=n.clientHeight;if(n.style.height=o||"0px",n.style.paddingTop=a||"0px",parseFloat(n.style.height)===l&&1===parseFloat(n.style.opacity||"1"))this._ringIcon(n);else{if(this._cancelAnimations(n),this._canAnimate(n)){n.animate([{height:`${parseFloat(n.style.height)||0}px`,paddingTop:`${parseFloat(n.style.paddingTop)||0}px`,opacity:parseFloat(n.style.opacity||"0")},{height:`${l}px`,paddingTop:`${this._anim.paddingTopOpened}px`,opacity:1}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1"}))}else n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1";this._ringIcon(n)}this.$el.addEventListener("input",(()=>this.hideErrorForm()),{passive:!0,once:!0})}hideErrorForm(){const t=this.$el.querySelectorAll(this.config.inputWrapperSelector),e=t[t.length-1],r=(null==e?void 0:e.nextElementSibling)||this.$el.querySelector(".error-block-under-input__wrapper");if(!r||!r.classList.contains("error-block-under-input__wrapper"))return;if((""===r.style.height||"auto"===r.style.height)&&(r.style.height=`${r.clientHeight}px`),r.style.paddingTop=r.style.paddingTop||`${this._anim.paddingTopOpened}px`,r.style.opacity=r.style.opacity||"1",this._cancelAnimations(r),this._canAnimate(r)){r.animate([{height:`${parseFloat(r.style.height)||r.clientHeight}px`,paddingTop:`${parseFloat(r.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(r.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{r.remove()}))}else r.remove()}_ringIcon(t){const e=t.querySelector(".error-block-under-input__icon--animated");e&&(this._cancelAnimations(e),this._canAnimate(e)&&e.animate([{transform:"rotate(0deg) scale(1)",opacity:0},{transform:"rotate(-8deg) scale(1.05)",opacity:1,offset:.15},{transform:"rotate(8deg) scale(1.05)",opacity:1,offset:.3},{transform:"rotate(-6deg) scale(1.03)",opacity:1,offset:.45},{transform:"rotate(6deg) scale(1.03)",opacity:1,offset:.6},{transform:"rotate(0deg) scale(1)",opacity:0}],{duration:this._anim.iconDuration,easing:"ease"}))}destroy(){var t;this._onSubmitHandler&&(this.$el.removeEventListener("submit",this._onSubmitHandler),this._onSubmitHandler=void 0),this._onSubmitClickHandler&&(this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this._onSubmitClickHandler=void 0),this.clearFieldValidationEvents(),null===(t=this.mutationObserver)||void 0===t||t.disconnect(),this.mutationObserver=void 0;const e=this.$el.querySelector(".error-block-under-input__wrapper");e&&(this._cancelAnimations(e),e.remove()),this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&e.remove()}));this.$el.querySelectorAll("input, textarea, select").forEach((t=>{this.hideError(t),this.clearFieldValidationState(t)})),document.documentElement.removeAttribute("data-now-scrolling"),this.inputs=null,this.$submits=[],this.errors=[],this.waitResponse=!1,F.delete(this.$el)}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):0!==this.$submits.length||(console.warn("В форме нет кнопки с типом submit"),!1)}formDataToObject(t){const e={};return t.forEach(((t,r)=>{Object.prototype.hasOwnProperty.call(e,r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}appendFormDataValue(t,e,r){null!=r&&(Array.isArray(r)?r.forEach(((r,i)=>{this.appendFormDataValue(t,`${e}[${i}]`,r)})):r instanceof Blob?t.append(e,r):t.append(e,"object"==typeof r?JSON.stringify(r):String(r)))}objectToFormData(t){const e=new FormData;return Object.entries(t).forEach((([t,r])=>{this.appendFormDataValue(e,t,r)})),e}stringifyFormDataValue(t){return"string"==typeof t?t:t.name}formDataToUrlSearchParams(t){const e=new URLSearchParams;return t.forEach(((t,r)=>{e.append(r,this.stringifyFormDataValue(t))})),e}formDataToTextBody(t){return Array.from(t).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(this.stringifyFormDataValue(e))}`)).join("\n")}isBodylessMethod(t){return"GET"===t||"HEAD"===t}appendQueryString(t,e){const r=e.toString();if(!r)return t;const i=t.includes("?")?"&":"?";return`${t}${i}${r}`}findFieldByName(t){return Array.from(this.$el.querySelectorAll("input, textarea, select")).find((e=>e.name===t))||null}parseResponseBody(t){return e(this,void 0,void 0,(function*(){try{const e=yield t.json();return e&&"object"==typeof e?e:{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}catch(t){return{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}}))}handleUnsuccessfulResponse(t){if(!0===t.error&&(t["error-msg"]&&this.showErrorForm(t["error-msg"]),t.errors)){const e=[];t.errors.forEach((t=>{const r=this.findFieldByName(t.name);r?(e.push(r),this.setValidationError(r,"server",t["error-msg"],"server"),this.showError(r,t["error-msg"]),r.removeAttribute("aria-busy"),this.setFieldValidationState(r,"invalid")):console.warn(`Не найдено поле с именем: ${t.name}, для вывода ошибки: ${t["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(e),!0===this.config.focusFirstErroredInput&&this.focusFirstErroredInput(e)}}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",r=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",i=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,s={};const o=a(this.$el);let l=this.formDataToObject(o);if(this.config.logging)for(const t of o.keys())console.log(o.get(t));const d="function"==typeof this.config.wrapData;"function"==typeof this.config.wrapData&&(l=this.config.wrapData(l));const c=d?this.objectToFormData(l):o;if(this.isBodylessMethod(r))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(c)),{method:r,headers:s});switch(i){case"application/x-www-form-urlencoded":n=this.formDataToUrlSearchParams(c).toString(),s["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=c;break;case"text/plain":n=this.formDataToTextBody(c),s["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),s["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${i}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(c).toString(),s["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:r,body:n,headers:s})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,r,i,n,s,o,a,d,c,u,h;if(!this.waitResponse){let p=!1;const f=()=>{p||(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),p=!0)};try{if(!1===(yield null===(e=(t=this.config).onBeforeSubmit)||void 0===e?void 0:e.call(t,this)))return{success:!1};!0===this.config.showLoaderButton&&this.showLoader(),null===(i=(r=this.config).onSubmit)||void 0===i||i.call(r,this),this.waitResponse=!0;const u=yield this.sendData(),h=yield this.parseResponseBody(u);return f(),null===(s=(n=this.config).onResponse)||void 0===s||s.call(n,h,this),u.ok&&!0===h.success?null===(a=(o=this.config).onResponseSuccess)||void 0===a||a.call(o,h,this):(null===(c=(d=this.config).onResponseUnsuccess)||void 0===c||c.call(d,h,this),this.handleUnsuccessfulResponse(h)),l(h),{success:u.ok&&!0===h.success,response:u,responseBody:h}}catch(t){throw f(),null===(h=(u=this.config).onSubmitError)||void 0===h||h.call(u,t,this),t}}}))}}return t.FORM_ERROR_FIELD=w,t.blockScrollBody=function(){window.lastBlockScrollPosition=window.pageYOffset,document.body.style.top=-window.lastBlockScrollPosition+"px",document.body.classList.add("body--block-scroll")},t.closest=r,t.createFieldValidator=function(t,r){return(i,n,s,o)=>e(this,void 0,void 0,(function*(){return!!(yield t(i,n,s,o))||{valid:!1,message:r}}))},t.createSchemaValidator=y,t.default=V,t.getAllValidators=function(){return new Map(h)},t.getValidator=f,t.isEmailValid=function(t){return/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(t)},t.isPhoneValid=function(t){const e=[];return Array.from(t).forEach((t=>{(Number(t)||0===Number(t))&&e.push(t)})),!(e.length<11||e.length<12&&"+"===t[1])},t.isUrlValid=o,t.parseCommonResponseProperties=l,t.registerSchemaValidator=function(t,e,r,i={}){const{override:n}=i;p(t,y(e,function(t,e){var r={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&e.indexOf(i)<0&&(r[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var n=0;for(i=Object.getOwnPropertySymbols(t);n<i.length;n++)e.indexOf(i[n])<0&&Object.prototype.propertyIsEnumerable.call(t,i[n])&&(r[i[n]]=t[i[n]])}return r}(i,["override"])),r,{override:n})},t.registerValidator=p,t.serializeFormToJSON=d,t.serializeToFormData=a,t.unblockScrollBody=function(){document.body.classList.remove("body--block-scroll"),document.body.style.top="",window.scrollTo(0,window.lastBlockScrollPosition||0)},Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
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,46 @@ 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
|
+
declare const FORM_ERROR_FIELD = "_form";
|
|
125
|
+
interface ValidationError {
|
|
126
|
+
field: string;
|
|
127
|
+
rule: string;
|
|
128
|
+
message: string;
|
|
129
|
+
source: ValidationErrorSource;
|
|
130
|
+
}
|
|
131
|
+
interface SubmitResult {
|
|
132
|
+
success: boolean;
|
|
133
|
+
response?: Response;
|
|
134
|
+
responseBody?: ResponseBody;
|
|
135
|
+
error?: unknown;
|
|
136
|
+
}
|
|
137
|
+
interface FormResetOptions {
|
|
138
|
+
clearErrors?: boolean;
|
|
139
|
+
}
|
|
140
|
+
interface ValidationIssue {
|
|
141
|
+
field?: string;
|
|
142
|
+
rule?: string;
|
|
143
|
+
message: string;
|
|
144
|
+
source?: ValidationErrorSource;
|
|
145
|
+
}
|
|
146
|
+
type ValidationIssueInput = string | ValidationIssue | ErrorResponse | Record<string, string> | Array<string | ValidationIssue | ErrorResponse>;
|
|
147
|
+
interface FormValidatorContext {
|
|
148
|
+
form: Form;
|
|
149
|
+
formElement: HTMLFormElement;
|
|
150
|
+
values: Record<string, any>;
|
|
151
|
+
errors: ValidationError[];
|
|
152
|
+
}
|
|
153
|
+
type FormValidatorResult = boolean | string | void | ValidationIssue | Array<string | ValidationIssue>;
|
|
154
|
+
type FormValidator = (context: FormValidatorContext) => FormValidatorResult | Promise<FormValidatorResult>;
|
|
93
155
|
type ValidationSchema = Record<string, {
|
|
94
156
|
rules: ValidationRule[];
|
|
95
157
|
selector?: string;
|
|
96
158
|
messages?: Record<string, string>;
|
|
97
159
|
}>;
|
|
160
|
+
type FormFieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
98
161
|
/** Параметры формы. */
|
|
99
162
|
interface FormOptions {
|
|
100
163
|
/**
|
|
@@ -110,6 +173,20 @@ interface FormOptions {
|
|
|
110
173
|
* @param formInstance - Инстанс формы.
|
|
111
174
|
*/
|
|
112
175
|
onAfterValidate?: (isValid: boolean, formInstance: Form) => void;
|
|
176
|
+
/**
|
|
177
|
+
* Функция обратного вызова. Запускается после неуспешной клиентской валидации.
|
|
178
|
+
*
|
|
179
|
+
* @param errors - Список ошибок валидации.
|
|
180
|
+
* @param formInstance - Инстанс формы.
|
|
181
|
+
*/
|
|
182
|
+
onValidationError?: (errors: ValidationError[], formInstance: Form) => void;
|
|
183
|
+
/**
|
|
184
|
+
* Функция обратного вызова. Запускается перед отправкой уже валидной формы. Если вернуть `false`, отправка будет
|
|
185
|
+
* отменена.
|
|
186
|
+
*
|
|
187
|
+
* @param formInstance - Инстанс формы.
|
|
188
|
+
*/
|
|
189
|
+
onBeforeSubmit?: (formInstance: Form) => void | boolean | Promise<void | boolean>;
|
|
113
190
|
/**
|
|
114
191
|
* Функция обратного вызова. Запускается, когда форма отправляется.
|
|
115
192
|
*
|
|
@@ -137,10 +214,19 @@ interface FormOptions {
|
|
|
137
214
|
* @param formInstance - Инстанс формы.
|
|
138
215
|
*/
|
|
139
216
|
onResponseUnsuccess?: (responseBody: ResponseBody, formInstance: Form) => void;
|
|
217
|
+
/**
|
|
218
|
+
* Функция обратного вызова. Запускается при исключении во время отправки формы.
|
|
219
|
+
*
|
|
220
|
+
* @param error - Ошибка отправки.
|
|
221
|
+
* @param formInstance - Инстанс формы.
|
|
222
|
+
*/
|
|
223
|
+
onSubmitError?: (error: unknown, formInstance: Form) => void;
|
|
140
224
|
/** Нужно ли показывать loader в кнопке. По умолчанию `true`. */
|
|
141
225
|
showLoaderButton?: boolean;
|
|
142
226
|
/** Нужно ли проскроливать до первого по порядку элемента с ошибкой. По умолчанию `true`. */
|
|
143
227
|
scrollToFirstErroredInput?: boolean;
|
|
228
|
+
/** Нужно ли переводить фокус в первое ошибочное поле. По умолчанию `false`. */
|
|
229
|
+
focusFirstErroredInput?: boolean;
|
|
144
230
|
/** Кастомный тип ошибки. */
|
|
145
231
|
customTypeError?: any;
|
|
146
232
|
/** Цвет лоадера. */
|
|
@@ -154,10 +240,27 @@ interface FormOptions {
|
|
|
154
240
|
* `.input-primary`.
|
|
155
241
|
*/
|
|
156
242
|
inputWrapperSelector?: string;
|
|
243
|
+
/**
|
|
244
|
+
* События, на которых нужно запускать валидацию поля. `submit` всегда обрабатывается отдельно. По умолчанию
|
|
245
|
+
* `submit`.
|
|
246
|
+
*/
|
|
247
|
+
validateOn?: ValidationTrigger | ValidationTrigger[];
|
|
248
|
+
/** События, на которых нужно повторно проверять уже ошибочные поля. По умолчанию `input` и `change`. */
|
|
249
|
+
revalidateOn?: ValidationTrigger | ValidationTrigger[];
|
|
250
|
+
/** Задержка live-валидации в миллисекундах. По умолчанию `0`. */
|
|
251
|
+
validationDebounce?: number;
|
|
252
|
+
/** Атрибут с CSS-селектором контейнера для ошибки поля. По умолчанию `data-error-container`. */
|
|
253
|
+
errorContainerAttribute?: string;
|
|
254
|
+
/** Атрибут, куда пишется состояние поля: `validating`, `valid` или `invalid`. */
|
|
255
|
+
validationStateAttribute?: string;
|
|
256
|
+
/** Следить за динамически добавленными/удалёнными полями и submit-кнопками. По умолчанию `false`. */
|
|
257
|
+
observeMutations?: boolean;
|
|
157
258
|
/** Функция для обёртки отправляемых данных. */
|
|
158
259
|
wrapData?: (data: Record<string, any>) => Record<string, any>;
|
|
159
260
|
/** Схема валидации: поле → массив правил + override-сообщения */
|
|
160
261
|
validationSchema?: ValidationSchema;
|
|
262
|
+
/** Валидаторы всей формы: cross-field, conditional required, date ranges и другие проверки над `getValues()`. */
|
|
263
|
+
formValidators?: FormValidator | FormValidator[];
|
|
161
264
|
}
|
|
162
265
|
interface ErrorResponse {
|
|
163
266
|
name: string;
|
|
@@ -184,6 +287,12 @@ declare class Form {
|
|
|
184
287
|
private inputs;
|
|
185
288
|
private _onSubmitHandler?;
|
|
186
289
|
private _onSubmitClickHandler?;
|
|
290
|
+
private fieldEventHandlers;
|
|
291
|
+
private liveValidationTimers;
|
|
292
|
+
private fieldValidationTokens;
|
|
293
|
+
private fieldValidationTokenCounter;
|
|
294
|
+
private mutationObserver?;
|
|
295
|
+
private errors;
|
|
187
296
|
private static get defaultParams();
|
|
188
297
|
private static set defaultParams(value);
|
|
189
298
|
static get defaultValidationSchema(): ValidationSchema;
|
|
@@ -196,6 +305,32 @@ declare class Form {
|
|
|
196
305
|
*/
|
|
197
306
|
constructor($el: HTMLElement, options?: FormOptions);
|
|
198
307
|
getOptions(): FormOptions;
|
|
308
|
+
/**
|
|
309
|
+
* Инициализирует сразу несколько форм по CSS-селектору. Если форма уже инициализирована, её настройки обновятся.
|
|
310
|
+
*
|
|
311
|
+
* @param selector - CSS-селектор форм. По умолчанию `form[data-form-father]`.
|
|
312
|
+
* @param options - Общие параметры для найденных форм.
|
|
313
|
+
*/
|
|
314
|
+
static initAll(selector?: string, options?: FormOptions): Form[];
|
|
315
|
+
/** Обновляет настройки конкретного инстанса формы без пересоздания обработчиков вручную. */
|
|
316
|
+
updateOptions(options: Partial<FormOptions>): this;
|
|
317
|
+
/** Возвращает текущий список ошибок формы. */
|
|
318
|
+
getErrors(): ValidationError[];
|
|
319
|
+
/** Возвращает значения формы обычным объектом. */
|
|
320
|
+
getValues(): Record<string, any>;
|
|
321
|
+
private normalizeValueList;
|
|
322
|
+
/** Проставляет значения полей по `name` и уведомляет UI через `input`/`change` события. */
|
|
323
|
+
setValues(values: Record<string, any>): this;
|
|
324
|
+
/** Снимает все клиентские и серверные ошибки с формы, не меняя значения полей. */
|
|
325
|
+
clearErrors(): this;
|
|
326
|
+
private isErrorResponse;
|
|
327
|
+
private normalizeValidationIssues;
|
|
328
|
+
private applyValidationIssue;
|
|
329
|
+
/** Показывает несколько ошибок формы или полей: из backend map/array, `ErrorResponse[]` или form validators. */
|
|
330
|
+
setErrors(issues: ValidationIssueInput, source?: ValidationErrorSource): this;
|
|
331
|
+
private validateFormValidators;
|
|
332
|
+
/** Сбрасывает форму через native `reset()` и опционально очищает ошибки. */
|
|
333
|
+
reset(options?: FormResetOptions): this;
|
|
199
334
|
/**
|
|
200
335
|
* Обновляет параметры по умолчанию для настроек формы. Метод объединяет переданные параметры с уже существующими
|
|
201
336
|
* параметрами по умолчанию.
|
|
@@ -204,8 +339,17 @@ declare class Form {
|
|
|
204
339
|
* свойства, которые необходимо обновить; остальные сохранятся без изменений.
|
|
205
340
|
*/
|
|
206
341
|
static setDefaultParams(params: Partial<FormOptions>): void;
|
|
342
|
+
private refreshControls;
|
|
207
343
|
private findSubmitElements;
|
|
344
|
+
private bindSubmitHandlers;
|
|
208
345
|
private initialization;
|
|
346
|
+
private handleSubmitEvent;
|
|
347
|
+
private normalizeTriggers;
|
|
348
|
+
private clearFieldValidationEvents;
|
|
349
|
+
private bindFieldValidationEvents;
|
|
350
|
+
private scheduleFieldValidation;
|
|
351
|
+
private setupMutationObserver;
|
|
352
|
+
private getErrorContainer;
|
|
209
353
|
/**
|
|
210
354
|
* Показывает ошибку для поля ввода.
|
|
211
355
|
*
|
|
@@ -229,17 +373,49 @@ declare class Form {
|
|
|
229
373
|
* @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
|
|
230
374
|
*/
|
|
231
375
|
private scrollToFirstErroredInput;
|
|
376
|
+
private focusFirstErroredInput;
|
|
377
|
+
private getValidationSchema;
|
|
378
|
+
private getAllFields;
|
|
379
|
+
private getFieldKey;
|
|
380
|
+
private hasFieldError;
|
|
381
|
+
private clearFieldErrorRecord;
|
|
382
|
+
private setErrorRecord;
|
|
383
|
+
private getValidationStateAttribute;
|
|
384
|
+
private setFieldValidationState;
|
|
385
|
+
private startFieldValidation;
|
|
386
|
+
private isLatestFieldValidation;
|
|
387
|
+
private finishFieldValidation;
|
|
388
|
+
private clearFieldValidationState;
|
|
389
|
+
private setValidationError;
|
|
390
|
+
private resolveField;
|
|
391
|
+
private parseRuleString;
|
|
392
|
+
private parseValidationAttribute;
|
|
393
|
+
private getRuleName;
|
|
394
|
+
private getRuleIdentity;
|
|
395
|
+
private collectRules;
|
|
396
|
+
private getRuleMessage;
|
|
397
|
+
private fieldMatchesSchemaDefinition;
|
|
398
|
+
private getFieldValidationConfig;
|
|
399
|
+
private getValidationFields;
|
|
400
|
+
private validateInput;
|
|
401
|
+
private runFieldValidation;
|
|
402
|
+
private getRadioGroup;
|
|
403
|
+
/** Показывает ошибку конкретного поля по имени или DOM-элементу. */
|
|
404
|
+
showFieldError(field: FieldReference, message: string, source?: ValidationErrorSource, rule?: string): boolean;
|
|
405
|
+
/** Проверяет одно поле по имени или DOM-элементу. */
|
|
406
|
+
validateField(field: FieldReference): Promise<boolean>;
|
|
232
407
|
/**
|
|
233
408
|
* Проверяет все поля ввода.
|
|
234
409
|
*
|
|
235
|
-
* Порядок для
|
|
410
|
+
* Порядок для каждого поля:
|
|
236
411
|
*
|
|
237
412
|
* 1. `required` — если атрибут `required`
|
|
238
413
|
* 2. правила из `validationSchema`
|
|
239
|
-
* 3. правила из `data-
|
|
414
|
+
* 3. правила из `data-validate`
|
|
415
|
+
* 4. правила из `data-custom-validate`
|
|
240
416
|
*
|
|
241
417
|
* Для одного поля показывается только первая ошибка. Неизвестные правила фиксируются `console.warn` и исключаются ДО
|
|
242
|
-
*
|
|
418
|
+
* валидации.
|
|
243
419
|
*/
|
|
244
420
|
validate($block?: HTMLElement): Promise<boolean>;
|
|
245
421
|
/** Блокирует кнопку отправки данных. */
|
|
@@ -283,7 +459,7 @@ declare class Form {
|
|
|
283
459
|
private parseResponseBody;
|
|
284
460
|
private handleUnsuccessfulResponse;
|
|
285
461
|
private sendData;
|
|
286
|
-
|
|
462
|
+
submit(): Promise<SubmitResult | undefined>;
|
|
287
463
|
}
|
|
288
464
|
|
|
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 };
|
|
465
|
+
export { type AdapterValidator, type ErrorResponse, FORM_ERROR_FIELD, type FieldReference, type FormFieldElement, type FormOptions, type FormResetOptions, type FormValidator, type FormValidatorContext, type FormValidatorResult, type RegisterSchemaValidatorOptions, type ResponseBody, type SchemaLike, type SchemaParseResult, type SchemaValidatorOptions, type SubmitResult, type ValidationError, type ValidationErrorSource, type ValidationIssue, type ValidationIssueInput, type ValidationRule, type ValidationSchema, type ValidationTrigger, type ValidatorEffectCtx, type ValidatorFieldElement, type ValidatorFn, type ValidatorResult, blockScrollBody, closest, createFieldValidator, createSchemaValidator, Form as default, getAllValidators, getValidator, isEmailValid, isPhoneValid, isUrlValid, parseCommonResponseProperties, registerSchemaValidator, registerValidator, serializeFormToJSON, serializeToFormData, unblockScrollBody };
|