@alexsab-ru/scripts 0.13.0 → 0.14.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.
@@ -0,0 +1,240 @@
1
+ import { sendToCallTouch } from './calltouch';
2
+
3
+ window.dataLayer = window.dataLayer || [];
4
+
5
+ export function reachGoal(eventAction, t = {}) {
6
+ t.eventAction = eventAction;
7
+ if(isGTMInstalled()) {
8
+ dl("reachGoal-"+eventAction, {
9
+ ...t
10
+ })
11
+ } else {
12
+ if(checkGA4InDataLayer()) {
13
+ gGoal(eventAction, {
14
+ ...t
15
+ });
16
+ }
17
+ if(window.ct && window.calltouch_params) {
18
+ t.siteId = window.calltouch_params.site_id;
19
+ console.log("sendToCallTouch:", t);
20
+ sendToCallTouch(t)
21
+ .then(result => {
22
+ console.log('Данные успешно отправлены в колтач:', eventAction, result);
23
+ })
24
+ .catch(error => {
25
+ console.error('Ошибка при отправке данных в колтач:', eventAction, error);
26
+ });
27
+ window.ct(window.calltouch_params.mod_id, 'goal', eventAction);
28
+ }
29
+ ymGoal(eventAction, {
30
+ ...t
31
+ });
32
+ }
33
+ }
34
+
35
+ export function pageView(eventAction, t = {}) {
36
+ t.eventAction = eventAction;
37
+ if(isGTMInstalled()) {
38
+ dl("pageView-"+eventAction, {
39
+ ...t
40
+ })
41
+ } else {
42
+ if(checkGA4InDataLayer()) {
43
+ gGoal(eventAction, {
44
+ ...t
45
+ });
46
+ }
47
+ ymPage(eventAction, {
48
+ ...t
49
+ });
50
+ }
51
+ }
52
+
53
+ export function dl(event, t = {}) {
54
+ if (typeof window !== 'undefined' && window.dataLayer && Array.isArray(window.dataLayer)) {
55
+ window.dataLayer.push({
56
+ event: event,
57
+ ...t
58
+ });
59
+ }
60
+ }
61
+
62
+ export function getFormDataObject(formData, form_id) {
63
+ let eventProperties = {};
64
+ formData.forEach((value, key) => (eventProperties[key] = value));
65
+ eventProperties['formID'] = form_id;
66
+ return {
67
+ "eventProperties": eventProperties,
68
+ "eventCategory": "Lead",
69
+ "sourceName": "page",
70
+ };
71
+ }
72
+
73
+ export function gGoal(goalName, goalParams) {
74
+ try {
75
+ dl(goalName, goalParams)
76
+ } catch (err) {
77
+ console.error(goalName + ' - error send goal to Google Analytics:', err);
78
+ }
79
+ }
80
+
81
+ // Универсальная функция для обработки Yandex Metrika целей
82
+ function executeYandexMetrikaAction(actionType, name, params = {}) {
83
+ try {
84
+ if (typeof Ya !== 'undefined' && Ya._metrika && Ya._metrika.getCounters) {
85
+ Ya._metrika.getCounters().forEach((counter) => {
86
+ if (counter && counter.id) {
87
+ ym(counter.id, actionType, name, params);
88
+ }
89
+ });
90
+ }
91
+ } catch (err) {
92
+ console.error(`${name} - error send ${actionType} to Metrika:`, err);
93
+ }
94
+ }
95
+
96
+ export function ymGoal(goalName, goalParams = {}) {
97
+ executeYandexMetrikaAction("reachGoal", goalName, goalParams);
98
+ }
99
+
100
+ export function ymPage(pageName, goalParams = {}) {
101
+ executeYandexMetrikaAction("hit", pageName, goalParams);
102
+ }
103
+
104
+ export function addClickCopyContextmenuGoals(item, prefix) {
105
+ if (!item || !prefix) return;
106
+
107
+ const events = [
108
+ { event: 'click', suffix: '_click' },
109
+ { event: 'copy', suffix: '_copy' },
110
+ { event: 'contextmenu', suffix: '_contextmenu' }
111
+ ];
112
+
113
+ events.forEach(({ event, suffix }) => {
114
+ item.addEventListener(event, function(evt) {
115
+ reachGoal(prefix + suffix);
116
+ });
117
+ });
118
+ }
119
+
120
+ // Безопасная инициализация событий
121
+ function initializeEventListeners() {
122
+ // Телефонные ссылки
123
+ const phoneLinks = document.querySelectorAll('a[href^="tel:"]');
124
+ phoneLinks.forEach((tel) => {
125
+ addClickCopyContextmenuGoals(tel, "phone");
126
+ });
127
+
128
+ // Email ссылки
129
+ const emailLinks = document.querySelectorAll('a[href^="mailto:"]');
130
+ emailLinks.forEach((email) => {
131
+ addClickCopyContextmenuGoals(email, "email");
132
+ });
133
+
134
+ // Цели для форм
135
+ const goals = [
136
+ {
137
+ selector: 'form input',
138
+ action: 'click',
139
+ goal: 'form_click',
140
+ title: 'Клик в поле любой формы',
141
+ },
142
+ {
143
+ selector: 'form input',
144
+ action: 'change',
145
+ goal: 'form_change',
146
+ title: 'Изменения полей любой формы',
147
+ },
148
+ ];
149
+
150
+ goals.forEach(function(goalConfig) {
151
+ if (!goalConfig.goal && !goalConfig.hit) {
152
+ console.warn("Ошибка в списке целей", goalConfig);
153
+ return;
154
+ }
155
+
156
+ const elements = document.querySelectorAll(goalConfig.selector);
157
+ elements.forEach(function(element) {
158
+ if (goalConfig.goal) {
159
+ element.addEventListener(goalConfig.action, function(){
160
+ reachGoal(goalConfig.goal, {
161
+ title: goalConfig.title,
162
+ });
163
+ });
164
+ } else if (goalConfig.hit) {
165
+ element.addEventListener(goalConfig.action, function(){
166
+ pageView(goalConfig.hit, {
167
+ title: goalConfig.title,
168
+ });
169
+ });
170
+ }
171
+ });
172
+ });
173
+ }
174
+
175
+ function isGTMInstalled() {
176
+ // Кешируем результат для избежания повторных вычислений
177
+ if (typeof window.isGTMInstalled === 'boolean') {
178
+ return window.isGTMInstalled;
179
+ }
180
+
181
+ // Проверяем dataLayer
182
+ if (window.dataLayer &&
183
+ Array.isArray(window.dataLayer) &&
184
+ window.dataLayer.length > 0 &&
185
+ window.dataLayer[0] &&
186
+ window.dataLayer[0].event === "gtm.js") {
187
+ window.isGTMInstalled = true;
188
+ return true;
189
+ }
190
+
191
+ // Проверяем скрипты
192
+ const scripts = document.querySelectorAll('script[src*="gtm.js"]');
193
+ if (scripts.length > 0) {
194
+ window.isGTMInstalled = true;
195
+ return true;
196
+ }
197
+
198
+ window.isGTMInstalled = false;
199
+ return false;
200
+ }
201
+
202
+ function checkGA4InDataLayer() {
203
+ // Кешируем результат
204
+ if (typeof window.ga4Installed === 'boolean') {
205
+ return window.ga4Installed;
206
+ }
207
+
208
+ // Проверяем dataLayer на наличие GA4 конфигурации
209
+ if (window.dataLayer && Array.isArray(window.dataLayer)) {
210
+ const ga4Config = window.dataLayer.find(item =>
211
+ Array.isArray(item) &&
212
+ item.length >= 2 &&
213
+ item[0] === 'config' &&
214
+ typeof item[1] === 'string' &&
215
+ item[1].startsWith('G-')
216
+ );
217
+
218
+ if (ga4Config) {
219
+ window.ga4Installed = true;
220
+ return true;
221
+ }
222
+ }
223
+
224
+ // Проверяем скрипты gtag.js
225
+ const scripts = document.querySelectorAll('script[src*="gtag.js"]');
226
+ if (scripts.length > 0) {
227
+ window.ga4Installed = true;
228
+ return true;
229
+ }
230
+
231
+ window.ga4Installed = false;
232
+ return false;
233
+ }
234
+
235
+ // Инициализация после загрузки DOM
236
+ if (document.readyState === 'loading') {
237
+ document.addEventListener('DOMContentLoaded', initializeEventListeners);
238
+ } else {
239
+ initializeEventListeners();
240
+ }
@@ -0,0 +1,443 @@
1
+ import { getCookie, setCookie, deleteCookie } from './cookie';
2
+ import { createRequest } from './calltouch';
3
+ import { reachGoal, getFormDataObject } from './analytics';
4
+
5
+ export const noValidPhone = (phoneValue) => {
6
+ return ([...new Set(phoneValue.replace(/^(\+7)/g, "").replace(/\D/g, ""))].length === 1);
7
+ };
8
+
9
+ // Debounce функция для ограничения частоты вызовов
10
+ function debounce(func, wait) {
11
+ let timeout;
12
+ return function executedFunction(...args) {
13
+ const later = () => {
14
+ clearTimeout(timeout);
15
+ func(...args);
16
+ };
17
+ clearTimeout(timeout);
18
+ timeout = setTimeout(later, wait);
19
+ };
20
+ }
21
+
22
+ export function maskphone(e) {
23
+ const input = e.target || this;
24
+ if (!input || !input.value) return;
25
+
26
+ let num = input.value.replace(/^(\+7|8|7)/g, "").replace(/\D/g, "").split(/(?=.)/);
27
+ let i = num.length;
28
+
29
+ if (input.value !== "" && input.value !== "+") {
30
+ if (0 <= i) num.unshift("+7");
31
+ if (1 <= i) num.splice(1, 0, " ");
32
+ if (4 <= i) num.splice(5, 0, " ");
33
+ if (7 <= i) num.splice(9, 0, "-");
34
+ if (9 <= i) num.splice(12, 0, "-");
35
+ input.value = num.join("");
36
+ }
37
+ }
38
+
39
+ // Debounced версия маски телефона
40
+ const debouncedMaskphone = debounce(maskphone, 100);
41
+
42
+ export const phoneChecker = (phone) => {
43
+ if (!phone) return false;
44
+
45
+ let form = phone.closest("form");
46
+ if (!form) return false;
47
+
48
+ if (!phone.value.length) {
49
+ showErrorMes(form, ".phone", "Телефон является обязательным полем");
50
+ return false;
51
+ } else {
52
+ const phoneRe = new RegExp(/^\+7 [0-9]{3} [0-9]{3}-[0-9]{2}-[0-9]{2}$/);
53
+ if (!phoneRe.test(phone.value) || noValidPhone(phone.value)) {
54
+ showErrorMes(form, ".phone", "Введен некорректный номер телефона");
55
+ return false;
56
+ }
57
+ }
58
+ showErrorMes(form, ".phone", "");
59
+ return true;
60
+ };
61
+
62
+ // TEXTAREA
63
+ const minLengthTextareaField = 10;
64
+
65
+ const checkTextareaLength = (textarea, minLength) => {
66
+ if (!textarea) return;
67
+
68
+ if (textarea.value.length >= minLength) {
69
+ const errorElement = textarea.nextSibling?.nextElementSibling;
70
+ if (errorElement && errorElement.classList) {
71
+ errorElement.classList.add("hidden");
72
+ }
73
+ }
74
+ };
75
+
76
+ // BUTTON
77
+ const stateBtn = (btn, value, disable = false) => {
78
+ if (!btn) return;
79
+
80
+ if (btn.tagName === 'INPUT') {
81
+ btn.value = value;
82
+ btn.disabled = disable;
83
+ } else {
84
+ btn.textContent = value; // Используем textContent вместо innerText для безопасности
85
+ if (disable) {
86
+ btn.setAttribute('disabled', true);
87
+ } else {
88
+ btn.removeAttribute('disabled');
89
+ }
90
+ }
91
+ };
92
+
93
+ export const showErrorMes = (form, el, text) => {
94
+ if (!form || !el) return;
95
+
96
+ let field = form.querySelector(el);
97
+ if (field) {
98
+ field.textContent = text; // Используем textContent для безопасности
99
+ field.classList.remove("hidden");
100
+ }
101
+ };
102
+
103
+ export const showMessageModal = (messageModal, icon, message) => {
104
+ document.querySelectorAll(".modal-overlay").forEach((el) => {
105
+ el.classList.add("hidden");
106
+ });
107
+
108
+ if (messageModal) {
109
+ const iconElement = messageModal.querySelector("#icon");
110
+ const messageElement = messageModal.querySelector("p");
111
+
112
+ if (iconElement) iconElement.innerHTML = icon; // SVG можно вставлять через innerHTML
113
+ if (messageElement) messageElement.textContent = message; // Текст только через textContent
114
+
115
+ messageModal.classList.remove("hidden");
116
+ }
117
+ };
118
+
119
+ const propsParams = {
120
+ callback: null,
121
+ callback_error: null,
122
+ validation: null,
123
+ ct_routeKey: '',
124
+ confirmModalText: '',
125
+ verbose: false,
126
+ agreeSelector: "agree",
127
+ sendMailCookie: "SEND_MAIL",
128
+ }
129
+
130
+ export const errorIcon = '<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path fill="#ed1c24" d="M26,0A26,26,0,1,0,52,26,26,26,0,0,0,26,0Zm9.6,17.5a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2Zm-19.2,0a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2ZM39.65,40.69a.93.93,0,0,1-.45.11,1,1,0,0,1-.89-.55,13.81,13.81,0,0,0-24.62,0,1,1,0,1,1-1.78-.9,15.8,15.8,0,0,1,28.18,0A1,1,0,0,1,39.65,40.69Z"></path></svg>';
131
+ export const successIcon = '<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path fill="#279548" d="M26,0A26,26,0,1,0,52,26,26,26,0,0,0,26,0Zm9.6,17.5a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2Zm-19.2,0a2,2,0,1,1-2,2A2,2,0,0,1,16.4,17.5ZM40.09,32.15a15.8,15.8,0,0,1-28.18,0,1,1,0,0,1,1.78-.9,13.81,13.81,0,0,0,24.62,0,1,1,0,1,1,1.78.9Z"></path></svg>';
132
+ export const errorText = 'Упс! Что-то пошло не так. Перезагрузите страницу и попробуйте снова.';
133
+ export const successText = 'Спасибо! В скором времени мы свяжемся с Вами!';
134
+ export const messageModal = document.getElementById("message-modal");
135
+
136
+ // Валидация входных данных
137
+ function sanitizeInput(input) {
138
+ if (typeof input !== 'string') return '';
139
+ return input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
140
+ }
141
+
142
+ // Функция для безопасной обработки FormData
143
+ function processFormData(formData, form) {
144
+ // Добавляем URL страницы
145
+ formData.append("page_url", window.location.origin + window.location.pathname);
146
+
147
+ // Проверяем куки fta
148
+ const ftaCookie = getCookie('fta');
149
+ if (ftaCookie) {
150
+ formData.append("fta", true);
151
+ }
152
+
153
+ // Обрабатываем GTM campaign URL
154
+ const campaignUrl = getCookie('__gtm_campaign_url');
155
+ if (campaignUrl) {
156
+ try {
157
+ const source = new URL(campaignUrl);
158
+ const searchParams = new URLSearchParams(source.search);
159
+ searchParams.forEach((value, key) => {
160
+ formData.append(key, sanitizeInput(value));
161
+ });
162
+ } catch (error) {
163
+ console.warn('Invalid campaign URL:', error);
164
+ }
165
+ }
166
+
167
+ // Добавляем re если существует
168
+ if (typeof window.re !== 'undefined') {
169
+ formData.append("re", sanitizeInput(window.re));
170
+ }
171
+
172
+ // Обрабатываем URL параметры
173
+ const urlParams = new URLSearchParams(window.location.search);
174
+ urlParams.forEach((value, key) => {
175
+ const sanitizedValue = sanitizeInput(decodeURIComponent(value));
176
+ if (formData.has(key)) {
177
+ formData.set(key, sanitizedValue);
178
+ } else {
179
+ formData.append(key, sanitizedValue);
180
+ }
181
+ });
182
+
183
+ return formData;
184
+ }
185
+
186
+ async function submitForm(form) {
187
+ if (!form) return false;
188
+
189
+ const btn = form.querySelector('[type="submit"]');
190
+ const btnText = (btn && (btn.value || btn.textContent)) || 'Отправить';
191
+ const agree = form.querySelector('[name="' + props.agreeSelector + '"]');
192
+ const phone = form.querySelector('[name="phone"]');
193
+ const name = form.querySelector('[name="name"]');
194
+ const dealer = form.querySelector('[name="dealer"]');
195
+
196
+ if (!phoneChecker(phone)) {
197
+ return false;
198
+ }
199
+
200
+ if (dealer && dealer.hasAttribute('required')) {
201
+ if (!dealer.value) {
202
+ showErrorMes(form, '.dealer', 'Выберите дилерский центр');
203
+ return false;
204
+ }
205
+ }
206
+
207
+ if (props.validation && typeof props.validation === 'function') {
208
+ try {
209
+ const validationResult = props.validation(form);
210
+ if (validationResult === false) {
211
+ return false;
212
+ }
213
+ } catch (error) {
214
+ console.error('Validation error:', error);
215
+ return false;
216
+ }
217
+ }
218
+
219
+ if (!agree || !agree.checked) {
220
+ showErrorMes(form, "." + props.agreeSelector, "Чтобы продолжить, установите флажок");
221
+ return false;
222
+ }
223
+
224
+ stateBtn(btn, "Отправляем...", true);
225
+
226
+ // Отправка цели что форма submit только после всех проверок
227
+ reachGoal("form_submit");
228
+
229
+ let formData = new FormData(form);
230
+ let sendMailCookie = props.sendMailCookie;
231
+
232
+ if (formData.get('dealer')) {
233
+ sendMailCookie += "_" + formData.get('dealer');
234
+ }
235
+
236
+ // Обрабатываем FormData безопасно
237
+ formData = processFormData(formData, form);
238
+
239
+ let formDataObj = {};
240
+
241
+ // Обработка CallTouch
242
+ try {
243
+ if (props.ct_routeKey && props.ct_routeKey.trim() !== '') {
244
+ const phoneValue = phone ? phone.value : '';
245
+ const nameValue = name ? name.value : '';
246
+
247
+ const requestData = await createRequest(
248
+ props.ct_routeKey,
249
+ phoneValue,
250
+ nameValue,
251
+ props.verbose
252
+ );
253
+
254
+ formData.append("ct_callback", true);
255
+ formData.append("ctw_createRequest", JSON.stringify(requestData));
256
+ }
257
+ } catch (error) {
258
+ if (props.ct_routeKey && props.ct_routeKey.trim() !== '') {
259
+ formData.append("ctw_createRequest", JSON.stringify({ error: error.message || error }));
260
+ }
261
+ if (props.verbose) {
262
+ console.error("Error during request Calltouch callback:", error);
263
+ }
264
+ formDataObj = getFormDataObject(formData, form.id);
265
+ }
266
+
267
+ // Отправка данных на сервер
268
+ const params = new URLSearchParams([...formData]);
269
+
270
+ try {
271
+ const response = await fetch(url, {
272
+ method: "POST",
273
+ mode: "cors",
274
+ cache: "no-cache",
275
+ credentials: "same-origin",
276
+ headers: {
277
+ "Content-Type": "application/x-www-form-urlencoded",
278
+ },
279
+ body: params,
280
+ });
281
+
282
+ if (!response.ok) {
283
+ throw new Error(`HTTP error! status: ${response.status}`);
284
+ }
285
+
286
+ const data = await response.json();
287
+
288
+ if (props.verbose) {
289
+ console.log('Form response:', data);
290
+ }
291
+
292
+ stateBtn(btn, btnText);
293
+
294
+ if (data.answer === "required") {
295
+ reachGoal("form_required");
296
+ showErrorMes(form, data.field, sanitizeInput(data.message));
297
+ return false;
298
+ } else if (data.answer === "error") {
299
+ reachGoal("form_error");
300
+
301
+ if (props.callback_error && typeof props.callback_error === 'function') {
302
+ props.callback_error(data);
303
+ } else if (messageModal) {
304
+ const errorMsg = sanitizeInput(data.error || 'Неизвестная ошибка');
305
+ showMessageModal(messageModal, errorIcon, errorText + " " + errorMsg);
306
+ }
307
+ return false;
308
+ } else {
309
+ if (data.attention !== true) {
310
+ formDataObj = getFormDataObject(formData, form.id);
311
+ reachGoal("form_success", formDataObj);
312
+ }
313
+
314
+ setCookie(sendMailCookie, true, {
315
+ 'domain': window.location.hostname,
316
+ 'path': '/',
317
+ 'expires': 600
318
+ });
319
+
320
+ if (props.callback && typeof props.callback === 'function') {
321
+ props.callback(data);
322
+ } else if (messageModal) {
323
+ showMessageModal(messageModal, successIcon, successText);
324
+ }
325
+
326
+ form.reset();
327
+ return true;
328
+ }
329
+ } catch (error) {
330
+ reachGoal("form_error");
331
+ console.error("Ошибка отправки данных формы:", error);
332
+ deleteCookie(sendMailCookie);
333
+
334
+ if (props.callback_error && typeof props.callback_error === 'function') {
335
+ props.callback_error({ error: error.message });
336
+ } else if (messageModal) {
337
+ showMessageModal(messageModal, errorIcon, errorText + " " + error.message);
338
+ }
339
+
340
+ stateBtn(btn, btnText);
341
+ return false;
342
+ }
343
+ }
344
+
345
+ async function sendForm(form) {
346
+ if (!form) return false;
347
+
348
+ let formData = new FormData(form);
349
+ let sendMailCookie = props.sendMailCookie;
350
+
351
+ if (formData.get('dealer')) {
352
+ sendMailCookie += "_" + formData.get('dealer');
353
+ }
354
+
355
+ if (getCookie(sendMailCookie)) {
356
+ const confirmModal = document.getElementById('confirm-modal');
357
+ if (confirmModal) {
358
+ const messageElement = confirmModal.querySelector('p');
359
+ if (messageElement) {
360
+ const confirmText = props.confirmModalText ||
361
+ 'ПЕРЕДАЙ ТЕКСТ В ОБЪЕКТЕ props = {confirmModalText: "text"}';
362
+ messageElement.textContent = confirmText;
363
+ }
364
+
365
+ confirmModal.classList.remove("hidden");
366
+
367
+ const accept = confirmModal.querySelector('#accept-confirm');
368
+ const acceptClose = confirmModal.querySelector('#accept-close');
369
+
370
+ if (accept && !accept.dataset.listenerAdded) {
371
+ accept.dataset.listenerAdded = 'true';
372
+ accept.addEventListener('click', async () => {
373
+ confirmModal.classList.add("hidden");
374
+ deleteCookie(sendMailCookie);
375
+ await submitForm(form);
376
+ });
377
+ }
378
+
379
+ if (acceptClose && !acceptClose.dataset.listenerAdded) {
380
+ acceptClose.dataset.listenerAdded = 'true';
381
+ acceptClose.addEventListener('click', () => {
382
+ const modals = document.querySelectorAll('.modal-overlay');
383
+ form.reset();
384
+ modals.forEach((modal) => modal.classList.add("hidden"));
385
+ confirmModal.classList.add("hidden");
386
+ });
387
+ }
388
+ return false;
389
+ }
390
+ } else {
391
+ return await submitForm(form);
392
+ }
393
+ }
394
+
395
+ export const connectForms = (url, props = propsParams) => {
396
+ if (!url) {
397
+ console.error('URL is required for connectForms');
398
+ return;
399
+ }
400
+
401
+ props = { ...propsParams, ...props };
402
+
403
+ // Настройка масок телефонов
404
+ document.querySelectorAll("input[name=phone]").forEach(function (element) {
405
+ element.addEventListener("input", debouncedMaskphone);
406
+ element.addEventListener("change", () => phoneChecker(element));
407
+ });
408
+
409
+ // Настройка чекбоксов согласия
410
+ document.querySelectorAll("input[name=" + props.agreeSelector + "]").forEach(function (element) {
411
+ let errorMes = element.parentElement?.querySelector("." + props.agreeSelector);
412
+ if (errorMes) {
413
+ element.addEventListener("change", (e) => {
414
+ if (!e.target.checked) {
415
+ errorMes.classList.remove("hidden");
416
+ } else {
417
+ errorMes.classList.add("hidden");
418
+ }
419
+ });
420
+ }
421
+ });
422
+
423
+ // Настройка textarea
424
+ document.querySelectorAll("textarea").forEach(function (textarea) {
425
+ const debouncedCheck = debounce(() => {
426
+ checkTextareaLength(textarea, minLengthTextareaField);
427
+ }, 300);
428
+
429
+ if (textarea.addEventListener) {
430
+ textarea.addEventListener("input", debouncedCheck, false);
431
+ } else if (textarea.attachEvent) {
432
+ textarea.attachEvent("onpropertychange", debouncedCheck);
433
+ }
434
+ });
435
+
436
+ // Обработка отправки форм
437
+ document.querySelectorAll("form:not(.vue-form)").forEach((form) => {
438
+ form.addEventListener('submit', async (event) => {
439
+ event.preventDefault();
440
+ await sendForm(form);
441
+ });
442
+ });
443
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexsab-ru/scripts",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "common libs for websites",
5
5
  "main": "index.js",
6
6
  "scripts": {