form-father 0.0.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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/FormFather.min.js +2 -0
- package/dist/FormFather.min.js.map +1 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.js +606 -0
- package/dist/index.js.map +1 -0
- package/dist/types/helpers.d.ts +69 -0
- package/dist/types/index.d.ts +112 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Poliklot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Form Father
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/form-father)
|
|
4
|
+
[](https://www.npmjs.com/package/form-father)
|
|
5
|
+
|
|
6
|
+
- [🇬🇧 English documentation](https://github.com/Poliklot/form-father/blob/master/docs/en/README.md)
|
|
7
|
+
|
|
8
|
+
**Form Father** — это библиотека для обработки форм на чистом JavaScript, обеспечивающая удобную валидацию и отправку
|
|
9
|
+
форм с использованием TypeScript.
|
|
10
|
+
|
|
11
|
+
## Установка
|
|
12
|
+
|
|
13
|
+
Установите библиотеку с помощью npm:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install form-father
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Использование
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import Form from 'form-father';
|
|
23
|
+
|
|
24
|
+
const formElement = document.querySelector('#myForm');
|
|
25
|
+
const options = {
|
|
26
|
+
onSubmit: formInstance => {
|
|
27
|
+
// Действия при отправке формы
|
|
28
|
+
},
|
|
29
|
+
onResponse: (responseBody, formInstance) => {
|
|
30
|
+
// Действия при получении ответа от сервера
|
|
31
|
+
},
|
|
32
|
+
// Другие опции...
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const form = new Form(formElement, options);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Опции
|
|
39
|
+
|
|
40
|
+
- **onSubmit**: Функция обратного вызова, вызываемая при отправке формы.
|
|
41
|
+
- **onResponse**: Функция обратного вызова при получении ответа от сервера.
|
|
42
|
+
- **onResponseSuccess**: Функция вызывается при успешном ответе сервера (статус 200).
|
|
43
|
+
- **onResponseUnsuccess**: Функция вызывается при неуспешном ответе сервера (статус не 200).
|
|
44
|
+
- **showLoaderButton**: Показывать ли лоадер в кнопке отправки. По умолчанию `true`.
|
|
45
|
+
- **scrollToFirstErroredInput**: Прокручивать ли к первому полю с ошибкой. По умолчанию `true`.
|
|
46
|
+
- **customTypeError**: Кастомный тип ошибки.
|
|
47
|
+
- **loaderColor**: Цвет лоадера в кнопке отправки.
|
|
48
|
+
|
|
49
|
+
## Методы
|
|
50
|
+
|
|
51
|
+
- **clearInputs()**: Очищает все поля ввода формы.
|
|
52
|
+
|
|
53
|
+
## Хелперы
|
|
54
|
+
|
|
55
|
+
Библиотека предоставляет ряд вспомогательных функций:
|
|
56
|
+
|
|
57
|
+
- **serializeToFormData($element)**: Сериализует данные формы в объект `FormData`.
|
|
58
|
+
- **isEmailValid(value)**: Проверяет, является ли строка валидным адресом электронной почты.
|
|
59
|
+
- **isUrlValid(value)**: Проверяет, является ли строка валидным URL.
|
|
60
|
+
- **isPhoneValid(value)**: Проверяет, является ли строка валидным номером телефона.
|
|
61
|
+
- **closest($el, selector)**: Находит ближайший родительский элемент по заданному селектору.
|
|
62
|
+
- **blockScrollBody()**: Блокирует прокрутку страницы.
|
|
63
|
+
- **unblockScrollBody()**: Разблокирует прокрутку страницы.
|
|
64
|
+
- **parseCommonResponseProperties(responseBody)**: Обрабатывает общие свойства ответа сервера.
|
|
65
|
+
|
|
66
|
+
## Сборка и разработка
|
|
67
|
+
|
|
68
|
+
Для сборки проекта используется **Rollup**. Основные команды:
|
|
69
|
+
|
|
70
|
+
- **npm run build**: Сборка проекта.
|
|
71
|
+
- **npm run watch**: Сборка в режиме наблюдения.
|
|
72
|
+
- **npm run demos**: Запуск демо-версии с использованием Vite.
|
|
73
|
+
|
|
74
|
+
## Вклад
|
|
75
|
+
|
|
76
|
+
Будем рады вашим предложениям и улучшениям! Пожалуйста, создавайте
|
|
77
|
+
[issues](https://github.com/poliklot/form-father/issues) и отправляйте
|
|
78
|
+
[pull requests](https://github.com/poliklot/form-father/pulls).
|
|
79
|
+
|
|
80
|
+
## Лицензия
|
|
81
|
+
|
|
82
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var FormFather=function(){"use strict";function t(t,e,r,n){return new(r||(r=Promise))((function(o,s){function i(t){try{c(n.next(t))}catch(t){s(t)}}function l(t){try{c(n.throw(t))}catch(t){s(t)}}function c(t){var e;t.done?o(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(i,l)}c((n=n.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:n}=t;if("file"===n)if("true"===t.getAttribute("multiple")){const n=t.files;if(n)for(let t=0;t<n.length;t++)r.append(`${e}[${t}]`,n[t])}else{const n=t.files;n&&n[0]&&r.append(e,n[0])}else{let o;if("tel"===n)o=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===n||"radio"===n){if(!t.checked)return;o=t.value}else if("date"===n){const e=t.getAttribute("data-date-format");o=e?function(t,e){const r=new Date(t),n=t=>t.toString().padStart(2,"0"),o=n(r.getDate()),s=n(r.getMonth()+1),i=r.getFullYear().toString();return e.replace("dd",o).replace("mm",s).replace("yyyy",i)}(t.value,e):t.value}else o=t.value;r.append(e,o)}})),r}"function"==typeof SuppressedError&&SuppressedError;return class{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},this.options),this.$el?(this.$submit=this.$el.querySelector('input[type="submit"], button[type="submit"]'),this.isCorrectArguments()&&this.initialization()):console.warn("Empty $el")}initialization(){this.inputs=this.$el.querySelectorAll(".input"),this.$licensesCheckbox=this.$el.querySelector('input[data-input-name="user-consent"]'),this.$licensesCheckbox&&(this.$licensesCheckbox.checked?this.enableSubmit():this.disableSubmit(),this.$licensesCheckbox.addEventListener("change",(()=>{this.$licensesCheckbox.checked?this.enableSubmit():this.disableSubmit()})));const r=e=>t(this,void 0,void 0,(function*(){return fetch(this.$el.getAttribute("action")||"",{method:"POST",body:e})})),n=()=>t(this,void 0,void 0,(function*(){var t,n,o,s,i,l,c,a;if(!this.waitResponse){const u=e(this.$el);!0===this.config.showLoaderButton&&this.showLoader(),null===(n=(t=this.config).onSubmit)||void 0===n||n.call(t,this),this.waitResponse=!0;const h=yield r(u),d=yield h.json();if(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),null===(s=(o=this.config).onResponse)||void 0===s||s.call(o,d,this),200===h.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}))}}}(d),!0===d.success)null===(l=(i=this.config).onResponseSuccess)||void 0===l||l.call(i,d,this);else if(null===(a=(c=this.config).onResponseUnsuccess)||void 0===a||a.call(c,d,this),!0===d.error&&(d["error-msg"]&&this.showErrorForm(d["error-msg"]),d.errors)){const t=[];d.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)&&n()}))}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,"input-primary").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="a" 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(#a)"></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,n=1/0;t.forEach((t=>{const o=e.findIndex((e=>e===t));o<n&&(n=o,r=t)})),r&&(document.documentElement.setAttribute("data-now-scrolling",""),r.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800))}}checkInputs(){var t;let e=!0;const r={},n=[];if(null===(t=this.inputs)||void 0===t||t.forEach((t=>{var o;const s=t,i=s.getAttribute("data-check-type")||s.getAttribute("type")||"";if((null===(o=this.config.customTypeError)||void 0===o?void 0:o.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,"Неверный формат"),n.push(s),e=!1)),"tel"===i&&(s.intl.isValidNumber()||(this.showError(s,"Неверный формат"),n.push(s),e=!1)),"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,"Неверный формат"),n.push(s),e=!1)),"text"===i&&"not-numbers"===s.getAttribute("data-check-type")&&/[0-9]/.test(s.value)&&(this.showError(s,"Неверный формат"),n.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,"Пустое значение"),n.push(s),e=!1);var l})),Object.keys(r).length>0)for(const t in r){const e=r[t];let n=!1,o=null;for(const t in e){const r=e[t];if(r.hasAttribute("required")&&(n=!0),r.checked){if(null!==o)return!1;o=r}}if(null===o&&n)return this.showErrorForm("Необходимо указать оценку"),!1}return!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(n),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?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"),n=r[r.length-1];let o=this.$el.querySelector(".error-block-under-input__wrapper");o&&n.nextElementSibling===o?(o.classList.add("error-block-under-input--warning"),o.querySelector(".error-block-under-input__icon").innerHTML="\x3c!-- SVG content --\x3e",o.querySelector(".error-block-under-input__main-text").textContent=t):(o=document.createElement("div"),o.className="error-block-under-input__wrapper",o.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>`,n.insertAdjacentElement("afterend",o)),this.$el.addEventListener("input",(()=>{this.hideErrorForm()}),{passive:!0,once:!0}),null===(e=o.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const s=o.querySelector(".error-block-under-input__secondary-text");s&&(s.textContent="");const i=parseFloat(o.style.height)||0,l=parseFloat(window.getComputedStyle(o).paddingTop)||0;i!==o.querySelector(".error-block-under-input").scrollHeight+16&&(o.style.height=`${i}px`,o.style.opacity="0",o.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)}}}();
|
|
2
|
+
//# sourceMappingURL=FormFather.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormFather.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Параметры формы. */
|
|
2
|
+
interface FormOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Функция обратного вызова. Запускается, когда форма отправляется.
|
|
5
|
+
*
|
|
6
|
+
* @param formInstance - Инстанс формы.
|
|
7
|
+
*/
|
|
8
|
+
onSubmit?: (formInstance: Form) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Функция обратного вызова.
|
|
11
|
+
*
|
|
12
|
+
* @param responseBody - Тело ответа.
|
|
13
|
+
* @param formInstance - Инстанс формы.
|
|
14
|
+
*/
|
|
15
|
+
onResponse?: (responseBody: any, formInstance: Form) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Функция обратного вызова. Запускается после ответа сервера 200.
|
|
18
|
+
*
|
|
19
|
+
* @param responseBody - Тело ответа.
|
|
20
|
+
* @param formInstance - Инстанс формы.
|
|
21
|
+
*/
|
|
22
|
+
onResponseSuccess?: (responseBody: any, formInstance: Form) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Функция обратного вызова. Запускается после ответа сервера не 200.
|
|
25
|
+
*
|
|
26
|
+
* @param responseBody - Тело ответа.
|
|
27
|
+
* @param formInstance - Инстанс формы.
|
|
28
|
+
*/
|
|
29
|
+
onResponseUnsuccess?: (responseBody: any, formInstance: Form) => void;
|
|
30
|
+
/** Нужно ли показывать loader в кнопке. По умолчанию `true`. */
|
|
31
|
+
showLoaderButton?: boolean;
|
|
32
|
+
/** Нужно ли проскроливать до первого по порядку элемента с ошибкой. По умолчанию `true`. */
|
|
33
|
+
scrollToFirstErroredInput?: boolean;
|
|
34
|
+
/** Кастомный тип ошибки. */
|
|
35
|
+
customTypeError?: any;
|
|
36
|
+
/** Цвет лоадера. */
|
|
37
|
+
loaderColor?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Реализует форму отправки данных.
|
|
41
|
+
*
|
|
42
|
+
* @class
|
|
43
|
+
*/
|
|
44
|
+
declare class Form {
|
|
45
|
+
private $el;
|
|
46
|
+
private options;
|
|
47
|
+
private config;
|
|
48
|
+
private $submit;
|
|
49
|
+
private waitResponse;
|
|
50
|
+
private inputs;
|
|
51
|
+
private $licensesCheckbox;
|
|
52
|
+
/**
|
|
53
|
+
* Создать форму.
|
|
54
|
+
*
|
|
55
|
+
* @param {HTMLElement} $el - Элемент формы (тег form!).
|
|
56
|
+
* @param {Object} options - Параметры формы.
|
|
57
|
+
*/
|
|
58
|
+
constructor($el: HTMLElement, options: FormOptions);
|
|
59
|
+
private initialization;
|
|
60
|
+
/**
|
|
61
|
+
* Показывает ошибку для поля ввода.
|
|
62
|
+
*
|
|
63
|
+
* @param {HTMLInputElement} $input - Поле ввода.
|
|
64
|
+
* @param {String} text - Текст ошибки.
|
|
65
|
+
*/
|
|
66
|
+
private showError;
|
|
67
|
+
/** Показывает лоадер */
|
|
68
|
+
private showLoader;
|
|
69
|
+
/** Скрывает лоадер */
|
|
70
|
+
private hideLoader;
|
|
71
|
+
/**
|
|
72
|
+
* Скрывает ошибку.
|
|
73
|
+
*
|
|
74
|
+
* @param {HTMLInputElement} $input - Поле ввода.
|
|
75
|
+
*/
|
|
76
|
+
private hideError;
|
|
77
|
+
/**
|
|
78
|
+
* Проскроливает до первого ошибочного поля.
|
|
79
|
+
*
|
|
80
|
+
* @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
|
|
81
|
+
*/
|
|
82
|
+
private scrollToFirstErroredInput;
|
|
83
|
+
/**
|
|
84
|
+
* Проверяет все поля ввода на корректность их заполнения.
|
|
85
|
+
*
|
|
86
|
+
* Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
|
|
87
|
+
*
|
|
88
|
+
* @returns {boolean} Корректно заполнены или нет (true/false).
|
|
89
|
+
*/
|
|
90
|
+
private checkInputs;
|
|
91
|
+
/** Блокирует кнопку отправки данных. */
|
|
92
|
+
private disableSubmit;
|
|
93
|
+
/** Снимает блокировку с кнопки отправки данных. */
|
|
94
|
+
private enableSubmit;
|
|
95
|
+
/** Очищает поля ввода формы. */
|
|
96
|
+
clearInputs(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Показывает ошибку под полями ввода - ошибку, относящуюся ко всей форме.
|
|
99
|
+
*
|
|
100
|
+
* @param {String} text - Текст ошибки.
|
|
101
|
+
*/
|
|
102
|
+
private showErrorForm;
|
|
103
|
+
/** Скрывает ошибку под полями ввода - ошибку, относящуюся ко всей форме. */
|
|
104
|
+
private hideErrorForm;
|
|
105
|
+
/**
|
|
106
|
+
* Проверяет входные данные на их корректность.
|
|
107
|
+
*
|
|
108
|
+
* @returns {boolean} Корректны данные или нет (true/false).
|
|
109
|
+
*/
|
|
110
|
+
private isCorrectArguments;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { Form as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/******************************************************************************
|
|
2
|
+
Copyright (c) Microsoft Corporation.
|
|
3
|
+
|
|
4
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
5
|
+
purpose with or without fee is hereby granted.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
9
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
11
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
12
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
13
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
14
|
+
***************************************************************************** */
|
|
15
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
19
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
29
|
+
var e = new Error(message);
|
|
30
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Возвращает форматированную дату.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} dateValue
|
|
37
|
+
* @param {string} format
|
|
38
|
+
*
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
function formatDate(dateValue, format) {
|
|
42
|
+
const date = new Date(dateValue);
|
|
43
|
+
/**
|
|
44
|
+
* Добавляет ведущие нули.
|
|
45
|
+
*
|
|
46
|
+
* @param {number} num
|
|
47
|
+
*
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
const pad = (num) => num.toString().padStart(2, '0');
|
|
51
|
+
const day = pad(date.getDate());
|
|
52
|
+
const month = pad(date.getMonth() + 1);
|
|
53
|
+
const year = date.getFullYear().toString();
|
|
54
|
+
return format.replace('dd', day).replace('mm', month).replace('yyyy', year);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Находит родительский элемент по заданому селектору.
|
|
58
|
+
*
|
|
59
|
+
* @param {HTMLElement | null} $el - Элемент, чего родителя нужно найти.
|
|
60
|
+
* @param {string} selector - Селектор родителя.
|
|
61
|
+
*
|
|
62
|
+
* @returns {HTMLElement | null} Элемент родителя. Но если родителя с таким селектори не было найдено возвращается null.
|
|
63
|
+
*/
|
|
64
|
+
function closest($el, selector) {
|
|
65
|
+
if ($el == null)
|
|
66
|
+
return null;
|
|
67
|
+
let parent = $el;
|
|
68
|
+
while (parent) {
|
|
69
|
+
if (parent.matches(selector)) {
|
|
70
|
+
return parent;
|
|
71
|
+
}
|
|
72
|
+
parent = parent.parentElement;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
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 {HTMLInputElement} $inputTel.
|
|
107
|
+
*
|
|
108
|
+
* @returns {boolean} Является ли строка валидным номером телефона true/false.
|
|
109
|
+
*/
|
|
110
|
+
function isPhoneValidOnlyThisProject($inputTel) {
|
|
111
|
+
const isValid = $inputTel.intl.isValidNumber();
|
|
112
|
+
return isValid;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Кастомная сериализация формы в объект FormData.
|
|
116
|
+
*
|
|
117
|
+
* @param {HTMLElement} $element - DOM элемент для сериализации.
|
|
118
|
+
*
|
|
119
|
+
* @returns {FormData} Сериализованные данные формы в объекте FormData.
|
|
120
|
+
*/
|
|
121
|
+
function serializeToFormData($element) {
|
|
122
|
+
const elements = $element.tagName === 'FORM'
|
|
123
|
+
? Array.from($element.elements)
|
|
124
|
+
: Array.from($element.querySelectorAll('input, select, textarea, button'));
|
|
125
|
+
const data = new FormData();
|
|
126
|
+
elements.forEach(element => {
|
|
127
|
+
if (!(element instanceof HTMLInputElement ||
|
|
128
|
+
element instanceof HTMLSelectElement ||
|
|
129
|
+
element instanceof HTMLTextAreaElement ||
|
|
130
|
+
element instanceof HTMLButtonElement)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!element.name || element.hasAttribute('data-no-serialize')) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const { name, type } = element;
|
|
137
|
+
if (type === 'file') {
|
|
138
|
+
if (element.getAttribute('multiple') === 'true') {
|
|
139
|
+
const files = element.files;
|
|
140
|
+
if (files) {
|
|
141
|
+
for (let i = 0; i < files.length; i++) {
|
|
142
|
+
// Добавляем каждый файл с уникальным ключом
|
|
143
|
+
data.append(`${name}[${i}]`, files[i]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const files = element.files;
|
|
149
|
+
if (files && files[0])
|
|
150
|
+
data.append(name, files[0]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
let value;
|
|
155
|
+
if (type === 'tel') {
|
|
156
|
+
value = element.value
|
|
157
|
+
.split(/[\s()-]/)
|
|
158
|
+
.join('')
|
|
159
|
+
.replace(/^8/, '+7');
|
|
160
|
+
}
|
|
161
|
+
else if (type === 'checkbox' || type === 'radio') {
|
|
162
|
+
if (!element.checked)
|
|
163
|
+
return;
|
|
164
|
+
value = element.value;
|
|
165
|
+
}
|
|
166
|
+
else if (type === 'date') {
|
|
167
|
+
const format = element.getAttribute('data-date-format');
|
|
168
|
+
value = format ? formatDate(element.value, format) : element.value;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
value = element.value;
|
|
172
|
+
}
|
|
173
|
+
data.append(name, value);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return data;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Функция для разбора общих свойств ответа.
|
|
180
|
+
*
|
|
181
|
+
* @param {any} responseBody - Тело ответа.
|
|
182
|
+
*/
|
|
183
|
+
function parseCommonResponseProperties(responseBody) {
|
|
184
|
+
if (Object.prototype.hasOwnProperty.call(responseBody, 'redirect-url')) {
|
|
185
|
+
window.location.href = responseBody['redirect-url'];
|
|
186
|
+
}
|
|
187
|
+
if (Object.prototype.hasOwnProperty.call(responseBody, 'reload')) {
|
|
188
|
+
if (responseBody.reload === true)
|
|
189
|
+
window.location.reload();
|
|
190
|
+
}
|
|
191
|
+
if (Object.prototype.hasOwnProperty.call(responseBody, 'error-toast')) {
|
|
192
|
+
const $formErrorToast = document.querySelector('[data-toast-name="formErrorToast"]');
|
|
193
|
+
if ($formErrorToast) {
|
|
194
|
+
const $toastText = $formErrorToast.querySelector('.toast__text');
|
|
195
|
+
if ($toastText) {
|
|
196
|
+
$toastText.textContent = responseBody['error-toast'];
|
|
197
|
+
window.Toast.showToast('formErrorToast', {
|
|
198
|
+
removeAfterClose: false,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Реализует форму отправки данных.
|
|
207
|
+
*
|
|
208
|
+
* @class
|
|
209
|
+
*/
|
|
210
|
+
class Form {
|
|
211
|
+
/**
|
|
212
|
+
* Создать форму.
|
|
213
|
+
*
|
|
214
|
+
* @param {HTMLElement} $el - Элемент формы (тег form!).
|
|
215
|
+
* @param {Object} options - Параметры формы.
|
|
216
|
+
*/
|
|
217
|
+
constructor($el, options) {
|
|
218
|
+
this.$submit = null;
|
|
219
|
+
this.waitResponse = false;
|
|
220
|
+
this.inputs = null;
|
|
221
|
+
this.$licensesCheckbox = null;
|
|
222
|
+
this.$el = $el;
|
|
223
|
+
this.options = options;
|
|
224
|
+
const defaultConfig = {
|
|
225
|
+
onSubmit: () => { },
|
|
226
|
+
onResponse: () => { },
|
|
227
|
+
onResponseSuccess: () => { },
|
|
228
|
+
onResponseUnsuccess: () => { },
|
|
229
|
+
customTypeError: null,
|
|
230
|
+
loaderColor: '#fff',
|
|
231
|
+
showLoaderButton: true,
|
|
232
|
+
scrollToFirstErroredInput: true,
|
|
233
|
+
};
|
|
234
|
+
this.config = Object.assign({}, defaultConfig, this.options);
|
|
235
|
+
if (this.$el) {
|
|
236
|
+
this.$submit = this.$el.querySelector('input[type="submit"], button[type="submit"]');
|
|
237
|
+
if (this.isCorrectArguments())
|
|
238
|
+
this.initialization();
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
console.warn('Empty $el');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
initialization() {
|
|
245
|
+
this.inputs = this.$el.querySelectorAll('.input');
|
|
246
|
+
this.$licensesCheckbox = this.$el.querySelector('input[data-input-name="user-consent"]');
|
|
247
|
+
if (this.$licensesCheckbox) {
|
|
248
|
+
if (this.$licensesCheckbox.checked)
|
|
249
|
+
this.enableSubmit();
|
|
250
|
+
else
|
|
251
|
+
this.disableSubmit();
|
|
252
|
+
this.$licensesCheckbox.addEventListener('change', () => {
|
|
253
|
+
if (this.$licensesCheckbox.checked)
|
|
254
|
+
this.enableSubmit();
|
|
255
|
+
else
|
|
256
|
+
this.disableSubmit();
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const sendData = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
260
|
+
return fetch(this.$el.getAttribute('action') || '', {
|
|
261
|
+
method: 'POST',
|
|
262
|
+
body: data,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
const submit = () => __awaiter(this, void 0, void 0, function* () {
|
|
266
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
267
|
+
if (!this.waitResponse) {
|
|
268
|
+
const data = serializeToFormData(this.$el);
|
|
269
|
+
if (this.config.showLoaderButton === true)
|
|
270
|
+
this.showLoader();
|
|
271
|
+
(_b = (_a = this.config).onSubmit) === null || _b === void 0 ? void 0 : _b.call(_a, this);
|
|
272
|
+
this.waitResponse = true;
|
|
273
|
+
// Этот код для тестирования
|
|
274
|
+
// for (const key of data.keys()) console.log(data.get(key));
|
|
275
|
+
// END Этот код для тестирования
|
|
276
|
+
const response = yield sendData(data);
|
|
277
|
+
const responseBody = yield response.json();
|
|
278
|
+
this.waitResponse = false;
|
|
279
|
+
if (this.config.showLoaderButton === true)
|
|
280
|
+
this.hideLoader();
|
|
281
|
+
(_d = (_c = this.config).onResponse) === null || _d === void 0 ? void 0 : _d.call(_c, responseBody, this);
|
|
282
|
+
if (response.status === 200) {
|
|
283
|
+
parseCommonResponseProperties(responseBody);
|
|
284
|
+
if (responseBody.success === true) {
|
|
285
|
+
(_f = (_e = this.config).onResponseSuccess) === null || _f === void 0 ? void 0 : _f.call(_e, responseBody, this);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
(_h = (_g = this.config).onResponseUnsuccess) === null || _h === void 0 ? void 0 : _h.call(_g, responseBody, this);
|
|
289
|
+
if (responseBody.error === true) {
|
|
290
|
+
if (responseBody['error-msg']) {
|
|
291
|
+
this.showErrorForm(responseBody['error-msg']);
|
|
292
|
+
}
|
|
293
|
+
if (responseBody.errors) {
|
|
294
|
+
const inputsList = [];
|
|
295
|
+
responseBody.errors.forEach(error => {
|
|
296
|
+
const $input = this.$el.querySelector(`input[name="${error.name}"]`);
|
|
297
|
+
if ($input) {
|
|
298
|
+
inputsList.push($input);
|
|
299
|
+
this.showError($input, error['error-msg']);
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.warn(`Не найдено поле с именем: ${error.name}, для вывода ошибки: ${error['error-msg']}`);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
if (this.config.scrollToFirstErroredInput === true)
|
|
306
|
+
this.scrollToFirstErroredInput(inputsList);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
this.$el.addEventListener('submit', e => {
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
if (this.checkInputs() && (!this.$licensesCheckbox || (this.$licensesCheckbox && this.$licensesCheckbox.checked)))
|
|
316
|
+
submit();
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Показывает ошибку для поля ввода.
|
|
321
|
+
*
|
|
322
|
+
* @param {HTMLInputElement} $input - Поле ввода.
|
|
323
|
+
* @param {String} text - Текст ошибки.
|
|
324
|
+
*/
|
|
325
|
+
showError($input, text) {
|
|
326
|
+
closest($input, 'input-primary').showError(text);
|
|
327
|
+
}
|
|
328
|
+
/** Показывает лоадер */
|
|
329
|
+
showLoader() {
|
|
330
|
+
var _a;
|
|
331
|
+
// Создаем SVG элемент
|
|
332
|
+
const loaderSvg = `
|
|
333
|
+
<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">
|
|
334
|
+
<defs>
|
|
335
|
+
<linearGradient id="a" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">
|
|
336
|
+
<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>
|
|
337
|
+
<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>
|
|
338
|
+
<stop offset="100%" stop-color="${this.config.loaderColor}"/>
|
|
339
|
+
</linearGradient>
|
|
340
|
+
</defs>
|
|
341
|
+
<g fill-rule="evenodd" fill="none">
|
|
342
|
+
<g transform="translate(1 1)">
|
|
343
|
+
<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#a)"></path>
|
|
344
|
+
<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>
|
|
345
|
+
</g>
|
|
346
|
+
</g>
|
|
347
|
+
</svg>
|
|
348
|
+
`;
|
|
349
|
+
// Создаем элемент загрузчика и добавляем SVG внутрь
|
|
350
|
+
const $loaderEl = document.createElement('span');
|
|
351
|
+
$loaderEl.innerHTML = loaderSvg;
|
|
352
|
+
$loaderEl.className = 'button__loader';
|
|
353
|
+
(_a = this.$submit) === null || _a === void 0 ? void 0 : _a.insertAdjacentElement('afterbegin', $loaderEl);
|
|
354
|
+
requestAnimationFrame(() => {
|
|
355
|
+
$loaderEl.setAttribute('data-showed', '');
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
/** Скрывает лоадер */
|
|
359
|
+
hideLoader() {
|
|
360
|
+
var _a, _b;
|
|
361
|
+
(_a = this.$submit) === null || _a === void 0 ? void 0 : _a.classList.remove('button--loading');
|
|
362
|
+
const $loader = (_b = this.$submit) === null || _b === void 0 ? void 0 : _b.querySelector('.button__loader');
|
|
363
|
+
if ($loader) {
|
|
364
|
+
$loader.classList.remove('active');
|
|
365
|
+
$loader.removeAttribute('data-showed');
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
$loader.remove();
|
|
368
|
+
}, 250);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Скрывает ошибку.
|
|
373
|
+
*
|
|
374
|
+
* @param {HTMLInputElement} $input - Поле ввода.
|
|
375
|
+
*/
|
|
376
|
+
hideError($input) {
|
|
377
|
+
const $inputWrapper = $input.parentElement;
|
|
378
|
+
if ($inputWrapper && $inputWrapper.classList.contains('input__wrapper')) {
|
|
379
|
+
$inputWrapper.getAttribute('data-type-error') || 'default';
|
|
380
|
+
$inputWrapper.classList.remove('input__wrapper--error');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Проскроливает до первого ошибочного поля.
|
|
385
|
+
*
|
|
386
|
+
* @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
|
|
387
|
+
*/
|
|
388
|
+
scrollToFirstErroredInput(inputsList) {
|
|
389
|
+
if (inputsList.length > 0) {
|
|
390
|
+
const allInputInFormList = Array.from(this.$el.querySelectorAll('input'));
|
|
391
|
+
let $firstInputOnForm = null;
|
|
392
|
+
let minIndex = Infinity;
|
|
393
|
+
inputsList.forEach($input => {
|
|
394
|
+
const findIndex = allInputInFormList.findIndex($item => $item === $input);
|
|
395
|
+
if (findIndex < minIndex) {
|
|
396
|
+
minIndex = findIndex;
|
|
397
|
+
$firstInputOnForm = $input;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
if ($firstInputOnForm) {
|
|
401
|
+
document.documentElement.setAttribute('data-now-scrolling', '');
|
|
402
|
+
$firstInputOnForm.scrollIntoView({
|
|
403
|
+
behavior: 'smooth',
|
|
404
|
+
block: 'center',
|
|
405
|
+
inline: 'center',
|
|
406
|
+
});
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
document.documentElement.removeAttribute('data-now-scrolling');
|
|
409
|
+
}, 800);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Проверяет все поля ввода на корректность их заполнения.
|
|
415
|
+
*
|
|
416
|
+
* Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
|
|
417
|
+
*
|
|
418
|
+
* @returns {boolean} Корректно заполнены или нет (true/false).
|
|
419
|
+
*/
|
|
420
|
+
checkInputs() {
|
|
421
|
+
var _a;
|
|
422
|
+
let isCorrect = true;
|
|
423
|
+
const radioGroups = {};
|
|
424
|
+
const inputsList = [];
|
|
425
|
+
(_a = this.inputs) === null || _a === void 0 ? void 0 : _a.forEach($inputEl => {
|
|
426
|
+
var _a;
|
|
427
|
+
const $input = $inputEl;
|
|
428
|
+
const inputType = $input.getAttribute('data-check-type') || $input.getAttribute('type') || '';
|
|
429
|
+
if (((_a = this.config.customTypeError) === null || _a === void 0 ? void 0 : _a.name) !== inputType ||
|
|
430
|
+
($input.tagName === 'TEXTAREA' && !this.config.customTypeError)) {
|
|
431
|
+
if ($input.value.length > 0) {
|
|
432
|
+
if (inputType === 'email') {
|
|
433
|
+
if (!isEmailValid($input.value)) {
|
|
434
|
+
this.showError($input, 'Неверный формат');
|
|
435
|
+
inputsList.push($input);
|
|
436
|
+
isCorrect = false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (inputType === 'tel') {
|
|
440
|
+
if (!isPhoneValidOnlyThisProject($input)) {
|
|
441
|
+
this.showError($input, 'Неверный формат');
|
|
442
|
+
inputsList.push($input);
|
|
443
|
+
isCorrect = false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (inputType === 'url') {
|
|
447
|
+
if (!isUrlValid($input.value)) {
|
|
448
|
+
this.showError($input, 'Неверный формат');
|
|
449
|
+
inputsList.push($input);
|
|
450
|
+
isCorrect = false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (inputType === 'text') {
|
|
454
|
+
if ($input.getAttribute('data-check-type') === 'not-numbers') {
|
|
455
|
+
if (/[0-9]/.test($input.value)) {
|
|
456
|
+
this.showError($input, 'Неверный формат');
|
|
457
|
+
inputsList.push($input);
|
|
458
|
+
isCorrect = false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (inputType === 'radio') {
|
|
463
|
+
const name = $input.getAttribute('name') || '';
|
|
464
|
+
if (!radioGroups[name]) {
|
|
465
|
+
radioGroups[name] = {};
|
|
466
|
+
}
|
|
467
|
+
radioGroups[name][Object.keys(radioGroups[name]).length] = $input;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else if ($input.hasAttribute('required')) {
|
|
471
|
+
this.showError($input, 'Пустое значение');
|
|
472
|
+
inputsList.push($input);
|
|
473
|
+
isCorrect = false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
if (Object.keys(radioGroups).length > 0) {
|
|
478
|
+
for (const key in radioGroups) {
|
|
479
|
+
const group = radioGroups[key];
|
|
480
|
+
let isRequired = false;
|
|
481
|
+
let $checkedInput = null;
|
|
482
|
+
for (const keyGroup in group) {
|
|
483
|
+
const $radio = group[keyGroup];
|
|
484
|
+
if ($radio.hasAttribute('required'))
|
|
485
|
+
isRequired = true;
|
|
486
|
+
if ($radio.checked) {
|
|
487
|
+
if ($checkedInput === null) {
|
|
488
|
+
$checkedInput = $radio;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if ($checkedInput === null && isRequired) {
|
|
496
|
+
this.showErrorForm('Необходимо указать оценку');
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (this.config.scrollToFirstErroredInput === true)
|
|
502
|
+
this.scrollToFirstErroredInput(inputsList);
|
|
503
|
+
return isCorrect;
|
|
504
|
+
}
|
|
505
|
+
/** Блокирует кнопку отправки данных. */
|
|
506
|
+
disableSubmit() {
|
|
507
|
+
var _a;
|
|
508
|
+
(_a = this.$submit) === null || _a === void 0 ? void 0 : _a.setAttribute('disabled', '');
|
|
509
|
+
}
|
|
510
|
+
/** Снимает блокировку с кнопки отправки данных. */
|
|
511
|
+
enableSubmit() {
|
|
512
|
+
var _a;
|
|
513
|
+
(_a = this.$submit) === null || _a === void 0 ? void 0 : _a.removeAttribute('disabled');
|
|
514
|
+
}
|
|
515
|
+
/** Очищает поля ввода формы. */
|
|
516
|
+
clearInputs() {
|
|
517
|
+
var _a;
|
|
518
|
+
(_a = this.inputs) === null || _a === void 0 ? void 0 : _a.forEach($inputEl => {
|
|
519
|
+
var _a;
|
|
520
|
+
const $input = $inputEl;
|
|
521
|
+
if ($input.type === 'radio') {
|
|
522
|
+
$input.checked = false;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
$input.value = '';
|
|
526
|
+
(_a = $input.parentElement) === null || _a === void 0 ? void 0 : _a.classList.remove('filled');
|
|
527
|
+
$input.dispatchEvent(new Event('input'));
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Показывает ошибку под полями ввода - ошибку, относящуюся ко всей форме.
|
|
533
|
+
*
|
|
534
|
+
* @param {String} text - Текст ошибки.
|
|
535
|
+
*/
|
|
536
|
+
showErrorForm(text) {
|
|
537
|
+
var _a;
|
|
538
|
+
const inputWrappersList = this.$el.querySelectorAll('.input__wrapper');
|
|
539
|
+
const $lastInputWrapper = inputWrappersList[inputWrappersList.length - 1];
|
|
540
|
+
let $errorWrapper = this.$el.querySelector('.error-block-under-input__wrapper');
|
|
541
|
+
if (!$errorWrapper || !($lastInputWrapper.nextElementSibling === $errorWrapper)) {
|
|
542
|
+
$errorWrapper = document.createElement('div');
|
|
543
|
+
$errorWrapper.className = 'error-block-under-input__wrapper';
|
|
544
|
+
$errorWrapper.innerHTML = `
|
|
545
|
+
<div class="error-block-under-input error-block-under-input--warning">
|
|
546
|
+
<div class="error-block-under-input__icon-wrapper">
|
|
547
|
+
<span class="error-block-under-input__icon">
|
|
548
|
+
<!-- SVG content -->
|
|
549
|
+
</span>
|
|
550
|
+
<span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:none;"></span>
|
|
551
|
+
</div>
|
|
552
|
+
<p class="error-block-under-input__text">
|
|
553
|
+
<span class="error-block-under-input__main-text">${text}</span>
|
|
554
|
+
<span class="error-block-under-input__secondary-text"></span>
|
|
555
|
+
</p>
|
|
556
|
+
</div>`;
|
|
557
|
+
$lastInputWrapper.insertAdjacentElement('afterend', $errorWrapper);
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
$errorWrapper.classList.add('error-block-under-input--warning');
|
|
561
|
+
$errorWrapper.querySelector('.error-block-under-input__icon').innerHTML = '<!-- SVG content -->';
|
|
562
|
+
$errorWrapper.querySelector('.error-block-under-input__main-text').textContent = text;
|
|
563
|
+
}
|
|
564
|
+
this.$el.addEventListener('input', () => {
|
|
565
|
+
this.hideErrorForm();
|
|
566
|
+
}, { passive: true, once: true });
|
|
567
|
+
(_a = $errorWrapper.querySelector('.error-block-under-input')) === null || _a === void 0 ? void 0 : _a.classList.remove('error-block-under-input--success');
|
|
568
|
+
const $secondaryText = $errorWrapper.querySelector('.error-block-under-input__secondary-text');
|
|
569
|
+
if ($secondaryText)
|
|
570
|
+
$secondaryText.textContent = '';
|
|
571
|
+
const currentHeight = parseFloat($errorWrapper.style.height) || 0;
|
|
572
|
+
const currentPaddingTop = parseFloat(window.getComputedStyle($errorWrapper).paddingTop) || 0;
|
|
573
|
+
const errorBlockHeight = $errorWrapper.querySelector('.error-block-under-input').scrollHeight + 16;
|
|
574
|
+
if (currentHeight !== errorBlockHeight) {
|
|
575
|
+
$errorWrapper.style.height = `${currentHeight}px`;
|
|
576
|
+
$errorWrapper.style.opacity = '0';
|
|
577
|
+
$errorWrapper.style.paddingTop = `${currentPaddingTop}px`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/** Скрывает ошибку под полями ввода - ошибку, относящуюся ко всей форме. */
|
|
581
|
+
hideErrorForm() {
|
|
582
|
+
const inputWrappersList = this.$el.querySelectorAll('.input__wrapper');
|
|
583
|
+
const $lastInputWrapper = inputWrappersList[inputWrappersList.length - 1];
|
|
584
|
+
const nextElement = $lastInputWrapper.nextElementSibling;
|
|
585
|
+
if (nextElement && nextElement.classList.contains('error-block-under-input__wrapper')) ;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Проверяет входные данные на их корректность.
|
|
589
|
+
*
|
|
590
|
+
* @returns {boolean} Корректны данные или нет (true/false).
|
|
591
|
+
*/
|
|
592
|
+
isCorrectArguments() {
|
|
593
|
+
if (this.$el.tagName !== 'FORM') {
|
|
594
|
+
console.warn(`$el не является тегом form`);
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
if (!this.$submit) {
|
|
598
|
+
console.warn(`В форме нет кнопки с типом submit`);
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export { Form as default };
|
|
606
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Находит родительский элемент по заданому селектору.
|
|
3
|
+
*
|
|
4
|
+
* @param {HTMLElement | null} $el - Элемент, чего родителя нужно найти.
|
|
5
|
+
* @param {string} selector - Селектор родителя.
|
|
6
|
+
*
|
|
7
|
+
* @returns {HTMLElement | null} Элемент родителя. Но если родителя с таким селектори не было найдено возвращается null.
|
|
8
|
+
*/
|
|
9
|
+
export declare function closest($el: HTMLElement | null, selector: string): HTMLElement | null;
|
|
10
|
+
/** Включает блокировку скролла страницы */
|
|
11
|
+
export declare function blockScrollBody(): void;
|
|
12
|
+
/** Отменяет блокировку скролла страницы */
|
|
13
|
+
export declare function unblockScrollBody(): void;
|
|
14
|
+
/**
|
|
15
|
+
* Проверяет является ли строка валидным адресом электронной почты.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} value - Строка, которую нужно проверить.
|
|
18
|
+
*
|
|
19
|
+
* @returns {boolean} Является ли строка валидным адресом электронной почты true/false.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isEmailValid(value: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Проверяет является ли строка валидным url.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} originalUrlString - Строка, которую нужно проверить.
|
|
26
|
+
*
|
|
27
|
+
* @returns {boolean} Является ли строка валидным url адресом true/false.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isUrlValid(originalUrlString: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Проверяет является ли строка валидным номером телефона.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} value - Строка, которую нужно проверить.
|
|
34
|
+
*
|
|
35
|
+
* @returns {boolean} Является ли строка валидным номером телефона true/false.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isPhoneValid(value: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Проверяет является ли строка валидным номером телефона.
|
|
40
|
+
*
|
|
41
|
+
* @param {HTMLInputElement} $inputTel.
|
|
42
|
+
*
|
|
43
|
+
* @returns {boolean} Является ли строка валидным номером телефона true/false.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isPhoneValidOnlyThisProject($inputTel: HTMLInputElement): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Кастомная сериализация формы в объект FormData.
|
|
48
|
+
*
|
|
49
|
+
* @param {HTMLElement} $element - DOM элемент для сериализации.
|
|
50
|
+
*
|
|
51
|
+
* @returns {FormData} Сериализованные данные формы в объекте FormData.
|
|
52
|
+
*/
|
|
53
|
+
export declare function serializeToFormData($element: HTMLElement): FormData;
|
|
54
|
+
/**
|
|
55
|
+
* Функция для разбора общих свойств ответа.
|
|
56
|
+
*
|
|
57
|
+
* @param {any} responseBody - Тело ответа.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseCommonResponseProperties(responseBody: any): void;
|
|
60
|
+
/**
|
|
61
|
+
* Сериализует данные формы в объект JSON.
|
|
62
|
+
*
|
|
63
|
+
* @param {HTMLFormElement} form - Элемент формы.
|
|
64
|
+
*
|
|
65
|
+
* @returns {Object} Сериализованные данные формы.
|
|
66
|
+
*/
|
|
67
|
+
export declare function serializeFormToJSON(form: HTMLFormElement): {
|
|
68
|
+
[key: string]: any;
|
|
69
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Параметры формы. */
|
|
2
|
+
interface FormOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Функция обратного вызова. Запускается, когда форма отправляется.
|
|
5
|
+
*
|
|
6
|
+
* @param formInstance - Инстанс формы.
|
|
7
|
+
*/
|
|
8
|
+
onSubmit?: (formInstance: Form) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Функция обратного вызова.
|
|
11
|
+
*
|
|
12
|
+
* @param responseBody - Тело ответа.
|
|
13
|
+
* @param formInstance - Инстанс формы.
|
|
14
|
+
*/
|
|
15
|
+
onResponse?: (responseBody: any, formInstance: Form) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Функция обратного вызова. Запускается после ответа сервера 200.
|
|
18
|
+
*
|
|
19
|
+
* @param responseBody - Тело ответа.
|
|
20
|
+
* @param formInstance - Инстанс формы.
|
|
21
|
+
*/
|
|
22
|
+
onResponseSuccess?: (responseBody: any, formInstance: Form) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Функция обратного вызова. Запускается после ответа сервера не 200.
|
|
25
|
+
*
|
|
26
|
+
* @param responseBody - Тело ответа.
|
|
27
|
+
* @param formInstance - Инстанс формы.
|
|
28
|
+
*/
|
|
29
|
+
onResponseUnsuccess?: (responseBody: any, formInstance: Form) => void;
|
|
30
|
+
/** Нужно ли показывать loader в кнопке. По умолчанию `true`. */
|
|
31
|
+
showLoaderButton?: boolean;
|
|
32
|
+
/** Нужно ли проскроливать до первого по порядку элемента с ошибкой. По умолчанию `true`. */
|
|
33
|
+
scrollToFirstErroredInput?: boolean;
|
|
34
|
+
/** Кастомный тип ошибки. */
|
|
35
|
+
customTypeError?: any;
|
|
36
|
+
/** Цвет лоадера. */
|
|
37
|
+
loaderColor?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Реализует форму отправки данных.
|
|
41
|
+
*
|
|
42
|
+
* @class
|
|
43
|
+
*/
|
|
44
|
+
export default class Form {
|
|
45
|
+
private $el;
|
|
46
|
+
private options;
|
|
47
|
+
private config;
|
|
48
|
+
private $submit;
|
|
49
|
+
private waitResponse;
|
|
50
|
+
private inputs;
|
|
51
|
+
private $licensesCheckbox;
|
|
52
|
+
/**
|
|
53
|
+
* Создать форму.
|
|
54
|
+
*
|
|
55
|
+
* @param {HTMLElement} $el - Элемент формы (тег form!).
|
|
56
|
+
* @param {Object} options - Параметры формы.
|
|
57
|
+
*/
|
|
58
|
+
constructor($el: HTMLElement, options: FormOptions);
|
|
59
|
+
private initialization;
|
|
60
|
+
/**
|
|
61
|
+
* Показывает ошибку для поля ввода.
|
|
62
|
+
*
|
|
63
|
+
* @param {HTMLInputElement} $input - Поле ввода.
|
|
64
|
+
* @param {String} text - Текст ошибки.
|
|
65
|
+
*/
|
|
66
|
+
private showError;
|
|
67
|
+
/** Показывает лоадер */
|
|
68
|
+
private showLoader;
|
|
69
|
+
/** Скрывает лоадер */
|
|
70
|
+
private hideLoader;
|
|
71
|
+
/**
|
|
72
|
+
* Скрывает ошибку.
|
|
73
|
+
*
|
|
74
|
+
* @param {HTMLInputElement} $input - Поле ввода.
|
|
75
|
+
*/
|
|
76
|
+
private hideError;
|
|
77
|
+
/**
|
|
78
|
+
* Проскроливает до первого ошибочного поля.
|
|
79
|
+
*
|
|
80
|
+
* @param {HTMLElement[]} inputsList - Массив элементов с ошибкой.
|
|
81
|
+
*/
|
|
82
|
+
private scrollToFirstErroredInput;
|
|
83
|
+
/**
|
|
84
|
+
* Проверяет все поля ввода на корректность их заполнения.
|
|
85
|
+
*
|
|
86
|
+
* Проверяются все поля ввода и если поле некорректно заполнено показывают ошибки рядом с полем ввода.
|
|
87
|
+
*
|
|
88
|
+
* @returns {boolean} Корректно заполнены или нет (true/false).
|
|
89
|
+
*/
|
|
90
|
+
private checkInputs;
|
|
91
|
+
/** Блокирует кнопку отправки данных. */
|
|
92
|
+
private disableSubmit;
|
|
93
|
+
/** Снимает блокировку с кнопки отправки данных. */
|
|
94
|
+
private enableSubmit;
|
|
95
|
+
/** Очищает поля ввода формы. */
|
|
96
|
+
clearInputs(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Показывает ошибку под полями ввода - ошибку, относящуюся ко всей форме.
|
|
99
|
+
*
|
|
100
|
+
* @param {String} text - Текст ошибки.
|
|
101
|
+
*/
|
|
102
|
+
private showErrorForm;
|
|
103
|
+
/** Скрывает ошибку под полями ввода - ошибку, относящуюся ко всей форме. */
|
|
104
|
+
private hideErrorForm;
|
|
105
|
+
/**
|
|
106
|
+
* Проверяет входные данные на их корректность.
|
|
107
|
+
*
|
|
108
|
+
* @returns {boolean} Корректны данные или нет (true/false).
|
|
109
|
+
*/
|
|
110
|
+
private isCorrectArguments;
|
|
111
|
+
}
|
|
112
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "form-father",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Form Father: Библиотека для обработки форм",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "rollup -c",
|
|
8
|
+
"watch": "rollup -c -w",
|
|
9
|
+
"demos": "BUILD_TARGET=demos npm run build && concurrently --kill-others \"vite\" \"npm run watch\""
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/poliklot/form-father.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"form",
|
|
17
|
+
"vanila",
|
|
18
|
+
"javascript",
|
|
19
|
+
"typescript",
|
|
20
|
+
"validation",
|
|
21
|
+
"library"
|
|
22
|
+
],
|
|
23
|
+
"author": "Poliklot",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/poliklot/form-father/issues"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@babel/preset-env": "^7.26.0",
|
|
34
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
35
|
+
"@rollup/plugin-commonjs": "^28.0.1",
|
|
36
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
37
|
+
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
38
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
39
|
+
"concurrently": "^9.0.1",
|
|
40
|
+
"postcss-preset-env": "^10.0.8",
|
|
41
|
+
"prettier-plugin-jsdoc": "^1.3.0",
|
|
42
|
+
"rollup": "^4.24.3",
|
|
43
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
44
|
+
"rollup-plugin-delete": "^2.1.0",
|
|
45
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
46
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
47
|
+
"sass-embedded": "^1.80.5",
|
|
48
|
+
"typescript": "^5.6.3",
|
|
49
|
+
"vite": "^5.4.10"
|
|
50
|
+
}
|
|
51
|
+
}
|