vgapp 0.7.8 → 0.8.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +22 -0
  3. package/app/langs/en/buttons.json +10 -0
  4. package/app/langs/en/messages.json +32 -0
  5. package/app/langs/en/titles.json +6 -0
  6. package/app/langs/ru/buttons.json +10 -0
  7. package/app/langs/ru/messages.json +32 -0
  8. package/app/langs/ru/titles.json +6 -0
  9. package/app/modules/base-module.js +23 -2
  10. package/app/modules/module-fn.js +20 -9
  11. package/app/modules/vgalert/js/vgalert.js +362 -214
  12. package/app/modules/vgalert/readme.md +242 -0
  13. package/app/modules/vgcollapse/js/vgcollapse.js +216 -62
  14. package/app/modules/vgcollapse/readme.md +56 -0
  15. package/app/modules/vgcollapse/scss/_variables.scss +5 -0
  16. package/app/modules/vgcollapse/scss/vgcollapse.scss +41 -0
  17. package/app/modules/vgdropdown/js/vgdropdown.js +140 -38
  18. package/app/modules/vgdropdown/readme.md +225 -0
  19. package/app/modules/vgfiles/js/base.js +499 -0
  20. package/app/modules/vgfiles/js/droppable.js +159 -0
  21. package/app/modules/vgfiles/js/loader.js +389 -0
  22. package/app/modules/vgfiles/js/render.js +83 -0
  23. package/app/modules/vgfiles/js/sortable.js +155 -0
  24. package/app/modules/vgfiles/js/vgfiles.js +796 -280
  25. package/app/modules/vgfiles/readme.md +193 -0
  26. package/app/modules/vgfiles/scss/_animations.scss +18 -0
  27. package/app/modules/vgfiles/scss/_mixins.scss +73 -0
  28. package/app/modules/vgfiles/scss/_variables.scss +103 -26
  29. package/app/modules/vgfiles/scss/vgfiles.scss +573 -60
  30. package/app/modules/vgformsender/js/vgformsender.js +5 -1
  31. package/app/modules/vgformsender/readme.md +30 -1
  32. package/app/modules/vglawcookie/js/vglawcookie.js +96 -62
  33. package/app/modules/vglawcookie/readme.md +102 -0
  34. package/app/modules/vgsidebar/js/vgsidebar.js +6 -4
  35. package/app/utils/js/components/ajax.js +176 -104
  36. package/app/utils/js/components/animation.js +124 -39
  37. package/app/utils/js/components/backdrop.js +54 -31
  38. package/app/utils/js/components/lang.js +71 -64
  39. package/app/utils/js/components/params.js +34 -31
  40. package/app/utils/js/components/scrollbar.js +118 -67
  41. package/app/utils/js/components/templater.js +14 -4
  42. package/app/utils/js/dom/cookie.js +107 -64
  43. package/app/utils/js/dom/data.js +68 -20
  44. package/app/utils/js/dom/event.js +272 -239
  45. package/app/utils/js/dom/manipulator.js +135 -62
  46. package/app/utils/js/dom/selectors.js +134 -59
  47. package/app/utils/js/functions.js +183 -349
  48. package/build/vgapp.css +1 -1
  49. package/build/vgapp.css.map +1 -1
  50. package/index.scss +3 -0
  51. package/package.json +1 -1
  52. package/app/utils/js/components/overflow.js +0 -28
@@ -1,56 +1,116 @@
1
1
  import BaseModule from "../../base-module";
2
2
  import VGModal from "../../vgmodal";
3
3
 
4
- import {isElement, isVisible, makeRandomString, mergeDeepObject} from "../../../utils/js/functions";
5
- import {getSVG} from "../../module-fn";
6
- import {Classes, Manipulator} from "../../../utils/js/dom/manipulator";
4
+ import { isElement, isVisible, makeRandomString, mergeDeepObject } from "../../../utils/js/functions";
5
+ import { getSVG } from "../../module-fn";
6
+ import { Classes} from "../../../utils/js/dom/manipulator";
7
7
  import Selectors from "../../../utils/js/dom/selectors";
8
8
  import EventHandler from "../../../utils/js/dom/event";
9
+ import {lang_buttons, lang_messages} from "../../../utils/js/components/lang";
10
+ import Html from "../../../utils/js/components/templater";
9
11
 
12
+ /**
13
+ * @typedef {Object} AjaxParams
14
+ * @property {string} route - URL-адрес для AJAX-запроса.
15
+ * @property {string} target - Селектор элемента, куда будет вставлен ответ.
16
+ * @property {string} method - HTTP-метод ('get', 'post' и т.д.).
17
+ * @property {boolean} loader - Показывать ли индикатор загрузки.
18
+ * @property {boolean} once - Выполнить запрос только один раз.
19
+ * @property {boolean} output - Выводить ли результат в целевой элемент.
20
+ */
21
+
22
+ /**
23
+ * @typedef {Object} ModalParams
24
+ * @property {boolean} centered - Центрировать ли модальное окно по вертикали.
25
+ * @property {boolean|string} backdrop - Фоновая подложка ('static', true, false).
26
+ * @property {boolean} overflow - Разрешить прокрутку фона при открытом окне.
27
+ * @property {boolean} keyboard - Закрывать по нажатию Escape.
28
+ * @property {boolean} dismiss - Закрывать при клике по подложке.
29
+ * @property {Object} animation - Параметры анимации.
30
+ * @property {boolean} animation.enable - Включить анимацию.
31
+ * @property {string} animation.in - Класс анимации входа.
32
+ * @property {string} animation.out - Класс анимации выхода.
33
+ * @property {number} animation.delay - Задержка перед показом (мс).
34
+ * @property {number} animation.duration - Длительность анимации (мс).
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} ButtonConfig
39
+ * @property {string} [element] - Готовый HTML-элемент кнопки.
40
+ * @property {'button'|'a'} [tag='button'] - Тип элемента.
41
+ * @property {string} [type='button'] - Атрибут type (для <button>).
42
+ * @property {Object.<string, string>} [attr] - Дополнительные атрибуты.
43
+ * @property {string} toggle - Атрибут данных для управления.
44
+ * @property {string[]} class - CSS-классы кнопки.
45
+ * @property {string} text - Текст кнопки.
46
+ */
47
+
48
+ /**
49
+ * @typedef {Object} MessageConfig
50
+ * @property {string} title - Заголовок сообщения.
51
+ * @property {string} description - Описание/текст сообщения.
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} AlertParams
56
+ * @property {AjaxParams} ajax - Параметры AJAX-запроса.
57
+ * @property {ModalParams} modal - Параметры модального окна.
58
+ * @property {'confirm'|'info'} mode - Режим алерта: подтверждение или информационное.
59
+ * @property {'danger'|'warning'|'success'|'info'} theme - Тема оформления.
60
+ * @property {{agree: ButtonConfig, cancel: ButtonConfig}} buttons - Конфигурация кнопок.
61
+ * @property {MessageConfig} message - Сообщение и заголовок.
62
+ * @property {string} [icon] - SVG-иконка, соответствующая теме.
63
+ */
64
+
65
+ /**
66
+ * Константы
67
+ */
10
68
  const CLASS_NAME_ALERT = "vg-alert";
69
+ const DATA_AGREE = "data-vg-alert-agree";
70
+ const DATA_CANCEL = "data-vg-alert-cancel";
11
71
 
12
- class VGAlert {
13
- constructor(params = {}) {
14
- this._elementsDefault = {
15
- buttons: {
16
- agree: {
17
- element: '',
18
- tag: 'button',
19
- attr: {
20
- type: 'button',
21
- },
22
- toggle: 'data-vg-alert-agree',
23
- class: ['btn'],
24
- text: 'Да, согласен'
25
- },
26
- cancel: {
27
- element: '',
28
- tag: 'button',
29
- attr: {
30
- type: 'button',
31
- },
32
- toggle: 'data-vg-alert-cancel',
33
- class: ['btn'],
34
- text: 'Отмена'
35
- }
36
- },
37
- message: {
38
- title: 'Заголовок по умолчанию',
39
- description: 'Описание текущего действия',
40
- },
41
- icons: {
42
- danger: getSVG('danger'),
43
- warning: getSVG('warning'),
44
- success: getSVG('success'),
45
- info: getSVG('info'),
46
- }
47
- }
72
+ const NAME = "alert";
73
+ const NAME_KEY = "vg.alert";
74
+
75
+ // Глобальная блокировка: предотвращаем открытие нескольких алертов
76
+ let isAlertOpen = false;
48
77
 
78
+ /**
79
+ * Класс VGAlert — модальное окно подтверждения или информационное уведомление.
80
+ *
81
+ * @class
82
+ * @example
83
+ * VGAlert.call({
84
+ * mode: 'confirm',
85
+ * theme: 'danger',
86
+ * message: {
87
+ * title: 'Вы уверены?',
88
+ * description: 'Это действие нельзя отменить.'
89
+ * },
90
+ * buttons: {
91
+ * agree: { text: 'Удалить', class: ['btn-danger'] },
92
+ * cancel: { text: 'Отмена' }
93
+ * }
94
+ * }).then(() => {
95
+ * console.log('Подтверждено');
96
+ * }).catch(() => {
97
+ * console.log('Отменено');
98
+ * });
99
+ */
100
+ class VGAlert {
101
+ /**
102
+ * Создаёт экземпляр VGAlert.
103
+ *
104
+ * @param {AlertParams} params - Пользовательские параметры.
105
+ * @param {'ru'|'en'} [lang='ru'] - Язык интерфейса.
106
+ */
107
+ constructor(params = {}, lang = 'ru') {
108
+ this.lang = lang;
49
109
  this._defaultParams = {
50
110
  ajax: {
51
- route: '',
52
- target: '',
53
- method: 'get',
111
+ route: "",
112
+ target: "",
113
+ method: "get",
54
114
  loader: false,
55
115
  once: false,
56
116
  output: true,
@@ -63,32 +123,77 @@ class VGAlert {
63
123
  dismiss: true,
64
124
  animation: {
65
125
  enable: false,
66
- in: 'animate__rollIn',
67
- out: 'animate__rollOut',
126
+ in: "animate__rollIn",
127
+ out: "animate__rollOut",
68
128
  delay: 300,
69
- duration: 700
129
+ duration: 700,
70
130
  },
71
131
  },
72
- mode: 'confirm',
73
- theme: 'danger',
132
+ mode: "confirm",
133
+ theme: "danger",
74
134
  buttons: {},
75
135
  message: {},
76
136
  };
77
-
137
+ this._elementsDefault = {
138
+ buttons: {
139
+ agree: {
140
+ element: "",
141
+ tag: "button",
142
+ type: "button",
143
+ attr: {},
144
+ toggle: DATA_AGREE,
145
+ class: ["btn"],
146
+ text: lang_buttons(this.lang, NAME)['agree'],
147
+ },
148
+ cancel: {
149
+ element: "",
150
+ tag: "button",
151
+ type: "button",
152
+ attr: {},
153
+ toggle: DATA_CANCEL,
154
+ class: ["btn"],
155
+ text: lang_buttons(this.lang, NAME)['cancel'],
156
+ },
157
+ },
158
+ message: {
159
+ title: lang_messages(this.lang, NAME)['title'],
160
+ description: lang_messages(this.lang, NAME)['description']
161
+ },
162
+ icons: {
163
+ danger: getSVG("danger"),
164
+ warning: getSVG("warning"),
165
+ success: getSVG("success"),
166
+ info: getSVG("info"),
167
+ },
168
+ };
78
169
  this._params = this._setParams(params);
79
170
  }
80
171
 
81
- static call(options = {}) {
82
- const context = new VGAlert(options);
172
+ /**
173
+ * Открывает алерт и возвращает Promise.
174
+ *
175
+ * @static
176
+ * @param {AlertParams} options - Параметры алерта.
177
+ * @param {'ru'|'en'} [lang='ru'] - Язык.
178
+ * @returns {Promise<{accepted: boolean, timestamp: Date}>} Результат взаимодействия.
179
+ * @throws {Error} Если алерт уже открыт.
180
+ */
181
+ static call(options = {}, lang = 'ru') {
182
+ const context = new VGAlert(options, lang);
183
+
184
+ if (isAlertOpen) return Promise.reject({ accepted: false, reason: lang_messages(context.lang, NAME_KEY).reason });
185
+ isAlertOpen = true;
186
+
83
187
  let modal = context._buildModal();
84
188
  modal.show();
85
189
 
86
- let container = modal._element,
87
- agreeBtn = Selectors.find('[data-vg-alert-agree]', container),
88
- cancelBtn = Selectors.find('[data-vg-alert-cancel]', container);
190
+ const container = modal._element;
191
+ const agreeBtn = Selectors.find(`[${DATA_AGREE}]`, container);
192
+ const cancelBtn = Selectors.find(`[${DATA_CANCEL}]`, container);
89
193
 
90
194
  return new Promise((resolve, reject) => {
91
- const handleAgree = () => {
195
+ const handleAgree = (e) => {
196
+ e.preventDefault();
92
197
  cleanup();
93
198
  resolve({
94
199
  accepted: true,
@@ -97,241 +202,284 @@ class VGAlert {
97
202
  modal.hide();
98
203
  };
99
204
 
100
- const handleCancel = () => {
205
+ const handleCancel = (e) => {
206
+ e.preventDefault();
101
207
  modal.hide();
102
208
  };
103
209
 
104
- const cleanup = () => {
105
- if (agreeBtn) agreeBtn.removeEventListener('click', handleAgree);
106
- if (cancelBtn) cancelBtn.removeEventListener('click', handleCancel);
210
+ const handleKeydown = (e) => {
211
+ if (e.key === "Enter" && agreeBtn) {
212
+ e.preventDefault();
213
+ handleAgree(e);
214
+ }
215
+ if (e.key === "Escape") {
216
+ e.preventDefault();
217
+ handleCancel(e);
218
+ }
107
219
  };
108
220
 
109
- if (context._params.mode === 'confirm') {
110
- if (agreeBtn) agreeBtn.addEventListener('click', handleAgree);
111
- if (cancelBtn) cancelBtn.addEventListener('click', handleCancel);
112
-
113
- container.addEventListener('vg.modal.hide', () => {
114
- cleanup();
221
+ const cleanup = () => {
222
+ isAlertOpen = false;
223
+ document.removeEventListener("keydown", handleKeydown);
224
+ if (agreeBtn) agreeBtn.removeEventListener("click", handleAgree);
225
+ if (cancelBtn) cancelBtn.removeEventListener("click", handleCancel);
226
+ };
115
227
 
116
- reject({
117
- accepted: false,
118
- timestamp: new Date(),
119
- });
120
- })
228
+ if (context._params.mode === "confirm") {
229
+ if (agreeBtn) agreeBtn.addEventListener("click", handleAgree);
230
+ if (cancelBtn) cancelBtn.addEventListener("click", handleCancel);
121
231
  }
122
232
 
123
- if (context._params.mode === 'info') {
124
- if (cancelBtn) cancelBtn.addEventListener('click', handleCancel);
233
+ if (context._params.mode === "info") {
234
+ if (cancelBtn) cancelBtn.addEventListener("click", handleCancel);
235
+ }
125
236
 
126
- container.addEventListener('vg.modal.hide', () => {
127
- cleanup();
237
+ document.addEventListener("keydown", handleKeydown);
238
+ container.addEventListener("vg.modal.hide", () => {
239
+ cleanup();
240
+ reject({
241
+ accepted: false,
242
+ timestamp: new Date(),
243
+ });
244
+ });
128
245
 
129
- reject({
130
- accepted: false,
131
- timestamp: new Date(),
132
- });
133
- })
134
- }
135
- })
246
+ container.focus();
247
+ });
136
248
  }
137
249
 
250
+ /**
251
+ * Инициирует алерт подтверждения на основе DOM-элемента.
252
+ *
253
+ * @static
254
+ * @param {HTMLElement} elem - Элемент, вызвавший алерт.
255
+ * @param {AlertParams} options - Параметры алерта.
256
+ * @returns {void}
257
+ */
138
258
  static confirm(elem, options = {}) {
139
- let context = new VGAlert(options);
140
- if (context._params.mode !== 'confirm') return;
259
+ let lang = 'ru';
260
+
261
+ if ('lang' in options) {
262
+ lang = options.lang || 'ru';
263
+ delete options.lang;
264
+ }
265
+ const context = new VGAlert(options, lang);
266
+ if (context._params.mode !== "confirm") return;
141
267
 
142
268
  const instance = VGAlertConfirm.getOrCreateInstance(elem, context._params);
143
269
  instance.run(VGAlert);
144
270
  }
145
271
 
272
+ /**
273
+ * Слияние пользовательских параметров с дефолтными.
274
+ *
275
+ * @private
276
+ * @param {AlertParams} params - Пользовательские параметры.
277
+ * @returns {AlertParams} Полный объект параметров.
278
+ */
146
279
  _setParams(params) {
147
- params = mergeDeepObject(this._defaultParams, params);
148
- params.buttons = mergeDeepObject(this._elementsDefault.buttons, params.buttons);
149
- params.message = mergeDeepObject(this._elementsDefault.message, params.message);
150
- params.icon = this._elementsDefault.icons[params.theme];
280
+ const merged = mergeDeepObject(this._defaultParams, params);
281
+ merged.buttons = mergeDeepObject(this._elementsDefault.buttons, merged.buttons);
282
+ merged.message = mergeDeepObject(this._elementsDefault.message, merged.message);
283
+ merged.icon = this._elementsDefault.icons[merged.theme];
151
284
 
152
- return params;
285
+ return merged;
153
286
  }
154
287
 
288
+ /**
289
+ * Создаёт и возвращает экземпляр модального окна с контентом алерта.
290
+ *
291
+ * @private
292
+ * @returns {VGModal} Экземпляр модального окна.
293
+ */
155
294
  _buildModal() {
156
- let id = CLASS_NAME_ALERT + '-' + makeRandomString(),
157
- $modal = Selectors.find('.'+ CLASS_NAME_ALERT +'-modal');
158
-
295
+ const id = `${CLASS_NAME_ALERT}-${crypto.randomUUID ? crypto.randomUUID().slice(0, 8) : makeRandomString()}`;
296
+ const $modal = Selectors.find(`.${CLASS_NAME_ALERT}-modal`);
159
297
  if ($modal) $modal.remove();
160
298
 
161
- return VGModal.build(id, this._params.modal, (self) => {
162
- let element = self._element;
163
- element.classList.add(CLASS_NAME_ALERT + '-modal');
164
-
165
- let $body = Selectors.find('.vg-modal-body', element);
166
- if ($body) {
167
- let wrapper = document.createElement('div');
168
- Classes.add(wrapper, CLASS_NAME_ALERT + '-wrapper');
169
- Classes.add(wrapper, CLASS_NAME_ALERT + '-' + this._params.theme);
170
-
171
- let content = document.createElement('div');
172
- Classes.add(content, CLASS_NAME_ALERT + '-content');
173
-
174
- let icon = document.createElement('div');
175
- Classes.add(icon, CLASS_NAME_ALERT + '-content--icon');
176
- this._create(icon, 'icons', this._params.theme);
177
-
178
- let message = document.createElement('div');
179
- Classes.add(message, CLASS_NAME_ALERT + '-content--message');
180
-
181
- let title = document.createElement('div');
182
- Classes.add(title, CLASS_NAME_ALERT + '-content--title');
183
- this._create(title, 'messages', 'title');
299
+ const html = Html('dom');
184
300
 
185
- let description = document.createElement('div');
186
- Classes.add(description, CLASS_NAME_ALERT + '-content--description');
187
- this._create(description, 'messages', 'description');
301
+ return VGModal.build(id, this._params.modal, (modalInstance) => {
302
+ const element = modalInstance._element;
303
+ element.classList.add(`${CLASS_NAME_ALERT}-modal`);
304
+ element.setAttribute("role", "alertdialog");
305
+ element.setAttribute("aria-modal", "true");
188
306
 
189
- message.append(title);
190
- message.append(description);
307
+ const $body = Selectors.find(".vg-modal-body", element);
308
+ if (!$body) return;
191
309
 
192
- if (this._params.icon) content.append(icon);
193
- content.append(message);
194
-
195
- let buttons = document.createElement('div');
196
- Classes.add(buttons, 'vg-alert-buttons');
310
+ let icon = null;
311
+ if (this._params.icon) {
312
+ icon = html.div({class: `${CLASS_NAME_ALERT}-content--icon`}, this._params.icon, {isHTML: true});
313
+ }
197
314
 
198
- if (this._params.mode === 'confirm') {
199
- this._create(buttons, 'button', 'cancel');
200
- this._create(buttons, 'button', 'agree');
201
- }
315
+ const buttons = document.createElement("div");
316
+ Classes.add(buttons, "vg-alert-buttons");
202
317
 
203
- if (this._params.mode === 'info') {
204
- this._create(buttons, 'button', 'cancel');
205
- }
318
+ if (this._params.mode === "confirm") {
319
+ this._createButton(buttons, "cancel");
320
+ this._createButton(buttons, "agree");
321
+ }
206
322
 
207
- wrapper.append(content);
208
- wrapper.append(buttons);
209
- $body.append(wrapper);
323
+ if (this._params.mode === "info") {
324
+ this._createButton(buttons, "cancel");
210
325
  }
326
+
327
+ let wrapper = html.div({class: `${CLASS_NAME_ALERT}-wrapper ${CLASS_NAME_ALERT}-${this._params.theme}`}, [
328
+ html.div({class: `${CLASS_NAME_ALERT}-content`}, [
329
+ icon,
330
+ html.div({class: `${CLASS_NAME_ALERT}-content--message`}, [
331
+ html.div({class: `${CLASS_NAME_ALERT}-content--title`}, this._params.message.title),
332
+ html.div({class: `${CLASS_NAME_ALERT}-content--description`}, this._params.message.description),
333
+ ])
334
+ ]),
335
+ buttons
336
+ ]);
337
+
338
+ $body.appendChild(wrapper);
211
339
  });
212
340
  }
213
341
 
214
- _create(container, element, mode) {
215
- if (element === 'button') {
216
- let button = this._params.buttons[mode];
217
- if (button.element) {
218
- return container.innerHTML += button.element;
219
- } else {
220
- if (!button.tag) return;
221
-
222
- let btn = document.createElement(button.tag);
223
- Classes.add(btn, button.class.join(' '));
224
-
225
- if (button.attr) {
226
- let attr = button.attr;
227
- for (const key in attr) {
228
- Manipulator.set(btn, key, attr[key]);
229
- }
230
- }
342
+ /**
343
+ * Создаёт кнопку и добавляет её в контейнер.
344
+ *
345
+ * @private
346
+ * @param {HTMLElement} container - Родительский элемент.
347
+ * @param {'agree'|'cancel'} key - Ключ кнопки.
348
+ * @returns {void}
349
+ */
350
+ _createButton(container, key) {
351
+ const button = this._params.buttons[key];
352
+ if (!button || button.element) {
353
+ container.insertAdjacentHTML("beforeend", button?.element || "");
354
+ return;
355
+ }
231
356
 
232
- Manipulator.set(btn, button.toggle, true);
233
- btn.innerHTML = button.text;
357
+ if (!button.tag) return;
234
358
 
235
- container.append(btn);
236
- }
237
- }
359
+ let btn = null,
360
+ classes = [...new Set(button.class)].join(" "),
361
+ attrs = mergeDeepObject({
362
+ class: classes
363
+ }, button.attr);
238
364
 
239
- if (element === 'icons') {
240
- if (this._params.icon) {
241
- container.innerHTML = this._params.icon;
242
- }
365
+ if (button.tag === "button") {
366
+ btn = Html('dom').button(button.text, button.type, attrs);
367
+ } else if (button.tag === "a") {
368
+ btn = Html('dom').a('#', button.text, attrs);
243
369
  }
244
370
 
245
- if (element === 'messages') {
246
- if (this._params.message) {
247
- container.innerHTML = this._params.message[mode];
248
- }
249
- }
371
+ if (!btn) return;
372
+
373
+ btn.setAttribute(button.toggle, "true");
374
+ container.appendChild(btn);
250
375
  }
251
376
  }
252
377
 
253
-
254
378
  /**
255
- * Constants
379
+ * Константы для событий и селекторов
256
380
  */
257
- const NAME = 'alert';
258
- const NAME_KEY = 'vg.alert';
259
-
260
- const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="alert"]';
261
- const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
262
-
263
- const EVENT_KEY_LOADED = 'vg.alert.loaded';
264
- const EVENT_KEY_ACCEPT = 'vg.alert.accept';
265
- const EVENT_KEY_REJECT = 'vg.alert.reject';
381
+ const SELECTOR_DATA_TOGGLE = `[data-vg-toggle="${NAME}"]`;
382
+ const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
383
+ const EVENT_KEY_LOADED = `${NAME_KEY}.loaded`;
384
+ const EVENT_KEY_ACCEPT = `${NAME_KEY}.accept`;
385
+ const EVENT_KEY_REJECT = `${NAME_KEY}.reject`;
386
+ const EVENT_KEY_FINALLY = `${NAME_KEY}.finally`;
266
387
 
388
+ /**
389
+ * Класс для работы с алертами по data-атрибутам.
390
+ *
391
+ * @class
392
+ * @extends BaseModule
393
+ * @example
394
+ * <button data-vg-toggle="alert" data-ajax-route="/delete/1">Удалить</button>
395
+ */
267
396
  class VGAlertConfirm extends BaseModule {
397
+ /**
398
+ * Создаёт экземпляр VGAlertConfirm.
399
+ *
400
+ * @param {HTMLElement} element - DOM-элемент.
401
+ * @param {AlertParams} options - Параметры.
402
+ */
268
403
  constructor(element, options = {}) {
269
404
  super(element);
270
-
271
- this._params = this._getParams(this._element, mergeDeepObject({
272
-
273
- }, options));
405
+ this._params = this._getParams(element, mergeDeepObject({}, options));
274
406
  }
275
407
 
408
+ /**
409
+ * Возвращает имя модуля.
410
+ * @returns {string}
411
+ */
276
412
  static get NAME() {
277
413
  return NAME;
278
414
  }
279
415
 
416
+ /**
417
+ * Возвращает ключевое имя с префиксом.
418
+ * @returns {string}
419
+ */
280
420
  static get NAME_KEY() {
281
- return NAME_KEY
421
+ return NAME_KEY;
282
422
  }
283
423
 
284
- run(self) {
285
- if (this._params.mode !== 'confirm') return;
286
-
287
- self.call(this._params).then((resolve) => {
288
- if (resolve.accepted) {
289
- if (this._params.ajax.route) {
290
- return this._ajax()
291
- } else {
292
- return resolve;
293
- }
294
- }
295
- }).then((response) => {
296
- EventHandler.trigger(this._element, EVENT_KEY_ACCEPT, {vgalert: response});
297
- }).catch((error) => {
298
- EventHandler.trigger(this._element, EVENT_KEY_REJECT, {vgalert: error});
299
- })
424
+ /**
425
+ * Запускает логику алерта: вызывает модальное окно и обрабатывает результат.
426
+ *
427
+ * @param {typeof VGAlert} AlertClass - Класс алерта.
428
+ * @returns {void}
429
+ */
430
+ run(AlertClass) {
431
+ if (this._params.mode !== "confirm") return;
432
+
433
+ AlertClass.call(this._params)
434
+ .then((resolve) => {
435
+ if (!resolve.accepted) return Promise.reject(resolve);
436
+ if (!this._params.ajax.route) return resolve;
437
+ return this._ajax();
438
+ })
439
+ .then((response) => {
440
+ EventHandler.trigger(this._element, EVENT_KEY_ACCEPT, { vgalert: response });
441
+ })
442
+ .catch((error) => {
443
+ EventHandler.trigger(this._element, EVENT_KEY_REJECT, { vgalert: error });
444
+ })
445
+ .finally(() => {
446
+ EventHandler.trigger(this._element, EVENT_KEY_FINALLY, { vgalert: 'finally' });
447
+ });
300
448
  }
449
+
450
+ /**
451
+ * Выполняет AJAX-запрос после подтверждения.
452
+ *
453
+ * @private
454
+ * @returns {Promise<Object>} Ответ от сервера.
455
+ */
301
456
  _ajax() {
302
457
  return new Promise((resolve) => {
303
458
  this._route((status, data) => {
304
- EventHandler.trigger(this._element, EVENT_KEY_LOADED, {stats: status, data: data});
305
-
306
- resolve({
307
- status: status,
308
- data: data
309
- });
310
- })
459
+ EventHandler.trigger(this._element, EVENT_KEY_LOADED, { stats: status, data });
460
+ resolve({ status, data });
461
+ });
311
462
  });
312
463
  }
313
464
  }
314
465
 
315
- EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, (event) => {
466
+ // Делегирование кликов по data-атрибутам
467
+ EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
316
468
  event.preventDefault();
469
+ const target = event.target;
317
470
 
318
- let target = event.target;
319
471
  if (!isVisible(target) || !isElement(target)) return;
320
472
 
321
473
  VGAlert.confirm(target, {
322
- message: {
323
- title: 'Удалить этот товар',
324
- description: 'Внимание этот товар будет удален'
325
- },
326
474
  buttons: {
327
475
  agree: {
328
- class: ['btn', 'btn-primary'],
476
+ class: ["btn-primary"],
329
477
  },
330
478
  cancel: {
331
- class: ['btn', 'btn-outline-primary'],
332
- }
333
- }
479
+ class: ["btn-outline-primary"],
480
+ },
481
+ },
334
482
  });
335
- })
483
+ });
336
484
 
337
485
  export default VGAlert;