vgapp 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,13 +1,50 @@
1
- # VEGAS-APP 1.2.2 (Июнь, 16, 2026)
1
+ # VEGAS-APP 1.2.5 (Июнь, 25, 2026)
2
+ ## Новые фичи
3
+ * Добавлен новый модуль `VGTooltip` с режимами `tooltip` и `popover`, Data API, автопозиционированием, fallback placement, закрытием по `Esc` и клику вне элемента.
4
+ * В `VGAlert` добавлен режим рендера `render.type = 'overlay'` для встраивания alert поверх текущей `.vg-modal` или `.vg-sidebar`.
5
+
6
+ ## Изменения
7
+ * В `VGAlert` рендер modal/overlay унифицирован: контейнер и экземпляр рендера создаются один раз, удалены отладочные `console.log`, закрытие вынесено в общий helper.
8
+ * В `VGAlert` отмена confirm теперь корректно отклоняет `Promise`, а обработчик `vg.modal.hide` подписывается только для modal-режима и срабатывает один раз.
9
+ * В `VGTooltip` глобальные обработчики `Esc` и клика вне элемента переведены на работу со всеми открытыми экземплярами, а сброс placement-классов вынесен в общий список.
10
+ * Обновлена документация `VGAlert` и `VGTooltip`, из директории `app/modules/vgalert/js/old/` удалены устаревшие файлы.
11
+ * Обновлена версия пакета в `package.json`: `1.2.4` -> `1.2.5`.
12
+
13
+ ---
14
+
15
+ # VEGAS-APP 1.2.4 (Июнь, 19, 2026)
16
+ ## Новые фичи
17
+ * В `VGModal` добавлены параметры `persistent` и `state` для opt-in persistent-модалок и сохранения состояния drag/resize через `localStorage`.
18
+
19
+ ## Изменения
20
+ * В `VGModal` persistent-модалки исключены из селектора обычных открытых окон и больше не закрываются автоматически при открытии следующей модалки.
21
+ * В `VGModal` сохранение и восстановление inline-состояния диалога и контента встроено в цикл `show/hide`, включая поддержку пользовательского `state.key`.
22
+ * В `VGModal` при `backdrop: true` клик по backdrop закрывает активную модалку через экземпляр текущего окна, а логика закрытия вынесена из общего `Backdrop`.
23
+ * В `VGModal` при `backdrop: false` модалка больше не блокирует страницу: добавлен `pointer-events: none` на контейнер и отдельная ветка корректного cleanup без backdrop.
24
+ * Обновлена версия пакета в `package.json`: `1.2.3` -> `1.2.4`.
25
+
26
+ ---
27
+
28
+ # VEGAS-APP 1.2.3 (Июнь, 16, 2026)
29
+ ## Изменения
30
+ * В `VGToast` исправлен lifecycle `show/hide/dispose`: добавлены защита от повторного hide, очистка отложенного скрытия и проверки актуального элемента в async callback.
31
+ * В `VGToast` `shown/hidden` больше не срабатывают по уже удалённому экземпляру, а `_isShown()` стал null-safe.
32
+ * В `VGFormSender` параметр `alert.toast.closeModalsBeforeToast` по умолчанию переключён в `true`, чтобы перед показом toast закрывались открытые модалки.
33
+ * В событие `vg.modal.loaded` добавлен `relatedTarget`, чтобы обработчики загрузки получали элемент-источник открытия модалки.
34
+ * Обновлена версия пакета в `package.json`: `1.2.2` -> `1.2.3`.
35
+
36
+ ---
37
+ # VEGAS-APP 1.2.2 (Июнь, 16, 2026)
2
38
  ## Новые фичи
3
39
  * В VGFiles добавлена настройка кнопок подтверждения удаления через `removes.buttons`, `removes.single.buttons` и `removes.all.buttons`.
4
40
  * В VGFormSender добавлены опции `alert.modal.closeModalsBeforeModal` и `alert.toast.closeModalsBeforeToast` для управления закрытием открытых модалок перед показом alert-modal или toast.
5
41
 
6
- ## Изменения
7
- * В VGFormSender при `alert.modal.closeModalsBeforeModal = false` новая alert-modal открывается поверх существующей модалки, получает класс `vg-modal-stacked` и повышенный `z-index`.
8
- * В VGFormSender закрытие открытых Bootstrap/VGModal модалок вынесено в общий helper.
9
- * В VGFormSender служебный параметр `closeModalsBeforeToast` не передается в настройки VGToast.
10
- * Обновлена версия пакета в `package.json`: `1.2.1` -> `1.2.2`.
42
+ ## Изменения
43
+ * В VGFormSender при `alert.modal.closeModalsBeforeModal = false` новая alert-modal открывается поверх существующей модалки, получает класс `vg-modal-stacked` и повышенный `z-index`.
44
+ * В VGFormSender закрытие открытых Bootstrap/VGModal модалок вынесено в общий helper.
45
+ * В VGFormSender служебный параметр `closeModalsBeforeToast` не передается в настройки VGToast.
46
+ * В VGModal persistent-модалка помечается отдельным признаком и не влияет на расчёт открытых модалок, а клик по backdrop закрывает именно активную модалку.
47
+ * Обновлена версия пакета в `package.json`: `1.2.1` -> `1.2.2`.
11
48
 
12
49
  ---
13
50
 
package/README.md CHANGED
@@ -38,7 +38,8 @@ npm i vgapp
38
38
  - `VGSidebar` — боковые панели с управлением открытием/закрытием.
39
39
  - `VGSpy` — scroll‑spy для подсветки активных ссылок по секциям.
40
40
  - `VGTabs` — вкладки с переключением контента и поддержкой URL/hash.
41
- - `VGToast` — всплывающие уведомления со стеком и автозакрытием.
41
+ - `VGToast` — всплывающие уведомления со стеком и автозакрытием.
42
+ - `VGTooltip` — tooltip/popover подсказки с Data API, автопозиционированием и закрытием по `Esc`/клику вне элемента.
42
43
 
43
44
  ## Authors
44
45
 
@@ -3,64 +3,12 @@ import VGModal from "../../vgmodal";
3
3
 
4
4
  import { isElement, isVisible, makeRandomString, mergeDeepObject } from "../../../utils/js/functions";
5
5
  import { getSVG } from "../../module-fn";
6
- import { Classes} from "../../../utils/js/dom/manipulator";
6
+ import {Classes, Manipulator} from "../../../utils/js/dom/manipulator";
7
7
  import Selectors from "../../../utils/js/dom/selectors";
8
8
  import EventHandler from "../../../utils/js/dom/event";
9
9
  import {lang_buttons, lang_messages} from "../../../utils/js/components/lang";
10
10
  import Html from "../../../utils/js/components/templater";
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
- */
11
+ import Params from "../../../utils/js/components/params";
64
12
 
65
13
  /**
66
14
  * Константы
@@ -75,38 +23,14 @@ const NAME_KEY = "vg.alert";
75
23
  // Глобальная блокировка: предотвращаем открытие нескольких алертов
76
24
  let isAlertOpen = false;
77
25
 
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
26
  class VGAlert {
101
- /**
102
- * Создаёт экземпляр VGAlert.
103
- *
104
- * @param {AlertParams} params - Пользовательские параметры.
105
- * @param {'ru'|'en'} [lang='ru'] - Язык интерфейса.
106
- */
107
27
  constructor(params = {}, lang = 'ru') {
108
28
  this.lang = lang;
109
29
  this._defaultParams = {
30
+ render: {
31
+ type: "modal", // modal or overlay,
32
+ dismiss: false,
33
+ },
110
34
  ajax: {
111
35
  route: "",
112
36
  target: "",
@@ -169,29 +93,55 @@ class VGAlert {
169
93
  this._params = this._setParams(params);
170
94
  }
171
95
 
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
96
  static call(options = {}, lang = 'ru') {
182
97
  const context = new VGAlert(options, lang);
183
98
 
184
99
  if (isAlertOpen) return Promise.reject({ accepted: false, reason: lang_messages(context.lang, NAME_KEY).reason });
185
100
  isAlertOpen = true;
186
101
 
187
- let modal = context._buildModal();
188
- modal.show();
102
+ const getContainer = () => {
103
+ if (context._params.render.type === "overlay") {
104
+ const overlay = context._buildOverlay();
105
+ return {
106
+ element: overlay.element,
107
+ render: overlay.render,
108
+ type: 'overlay'
109
+ }
110
+ } else if (context._params.render.type === "modal") {
111
+ const modal = context._buildModal();
112
+ return {
113
+ element: modal._element,
114
+ render: modal,
115
+ type: 'modal'
116
+ }
117
+ }
118
+ };
119
+
120
+ const {element: container, render, type} = getContainer();
121
+
122
+ if (type === 'modal') {
123
+ render.show();
124
+ }
189
125
 
190
- const container = modal._element;
191
126
  const agreeBtn = Selectors.find(`[${DATA_AGREE}]`, container);
192
127
  const cancelBtn = Selectors.find(`[${DATA_CANCEL}]`, container);
193
128
 
194
129
  return new Promise((resolve, reject) => {
130
+ const closeAlert = () => {
131
+ if (type === 'modal') {
132
+ render.hide();
133
+ return;
134
+ }
135
+
136
+ if (type === 'overlay') {
137
+ container.remove();
138
+
139
+ if (context._params.render.dismiss) {
140
+ render.hide();
141
+ }
142
+ }
143
+ };
144
+
195
145
  const handleAgree = (e) => {
196
146
  e.preventDefault();
197
147
  cleanup();
@@ -199,12 +149,17 @@ class VGAlert {
199
149
  accepted: true,
200
150
  timestamp: new Date(),
201
151
  });
202
- modal.hide();
152
+ closeAlert();
203
153
  };
204
154
 
205
155
  const handleCancel = (e) => {
206
156
  e.preventDefault();
207
- modal.hide();
157
+ cleanup();
158
+ reject({
159
+ accepted: false,
160
+ timestamp: new Date(),
161
+ });
162
+ closeAlert();
208
163
  };
209
164
 
210
165
  const handleKeydown = (e) => {
@@ -235,26 +190,21 @@ class VGAlert {
235
190
  }
236
191
 
237
192
  document.addEventListener("keydown", handleKeydown);
238
- container.addEventListener("vg.modal.hide", () => {
239
- cleanup();
240
- reject({
241
- accepted: false,
242
- timestamp: new Date(),
243
- });
244
- });
193
+
194
+ if (type === 'modal') {
195
+ container.addEventListener("vg.modal.hide", () => {
196
+ cleanup();
197
+ reject({
198
+ accepted: false,
199
+ timestamp: new Date(),
200
+ });
201
+ }, {once: true});
202
+ }
245
203
 
246
204
  container.focus();
247
205
  });
248
206
  }
249
207
 
250
- /**
251
- * Инициирует алерт подтверждения на основе DOM-элемента.
252
- *
253
- * @static
254
- * @param {HTMLElement} elem - Элемент, вызвавший алерт.
255
- * @param {AlertParams} options - Параметры алерта.
256
- * @returns {void}
257
- */
258
208
  static confirm(elem, options = {}) {
259
209
  let lang = 'ru';
260
210
 
@@ -269,13 +219,6 @@ class VGAlert {
269
219
  instance.run(VGAlert);
270
220
  }
271
221
 
272
- /**
273
- * Слияние пользовательских параметров с дефолтными.
274
- *
275
- * @private
276
- * @param {AlertParams} params - Пользовательские параметры.
277
- * @returns {AlertParams} Полный объект параметров.
278
- */
279
222
  _setParams(params) {
280
223
  const merged = mergeDeepObject(this._defaultParams, params);
281
224
  merged.buttons = mergeDeepObject(this._elementsDefault.buttons, merged.buttons);
@@ -285,68 +228,88 @@ class VGAlert {
285
228
  return merged;
286
229
  }
287
230
 
288
- /**
289
- * Создаёт и возвращает экземпляр модального окна с контентом алерта.
290
- *
291
- * @private
292
- * @returns {VGModal} Экземпляр модального окна.
293
- */
294
231
  _buildModal() {
295
- const id = `${CLASS_NAME_ALERT}-${crypto.randomUUID ? crypto.randomUUID().slice(0, 8) : makeRandomString()}`;
296
- const $modal = Selectors.find(`.${CLASS_NAME_ALERT}-modal`);
297
- if ($modal) $modal.remove();
232
+ const id = `${CLASS_NAME_ALERT}-${makeRandomString()}`;
298
233
 
299
- const html = Html('dom');
234
+ return VGModal.build(id, this._params.modal, (modal) => {
235
+ modal._element.classList.add(`${CLASS_NAME_ALERT}-modal`);
236
+ const body = Selectors.find(".vg-modal-body", modal._element);
237
+ body.append(this._buildContent());
238
+ });
239
+ }
300
240
 
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");
241
+ _buildContent() {
242
+ const html = Html('dom');
306
243
 
307
- const $body = Selectors.find(".vg-modal-body", element);
308
- if (!$body) return;
244
+ let icon = null;
309
245
 
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
- }
246
+ if (this._params.icon) {
247
+ icon = html.div(
248
+ {class: `${CLASS_NAME_ALERT}-content--icon`},
249
+ this._params.icon,
250
+ {isHTML: true}
251
+ );
252
+ }
314
253
 
315
- const buttons = document.createElement("div");
316
- Classes.add(buttons, "vg-alert-buttons");
254
+ const buttons = document.createElement("div");
255
+ Classes.add(buttons, "vg-alert-buttons");
317
256
 
318
- if (this._params.mode === "confirm") {
319
- this._createButton(buttons, "cancel");
320
- this._createButton(buttons, "agree");
321
- }
257
+ if (this._params.mode === "confirm") {
258
+ this._createButton(buttons, "cancel");
259
+ this._createButton(buttons, "agree");
260
+ }
322
261
 
323
- if (this._params.mode === "info") {
324
- this._createButton(buttons, "cancel");
325
- }
262
+ if (this._params.mode === "info") {
263
+ this._createButton(buttons, "cancel");
264
+ }
326
265
 
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
- ]),
266
+ return html.div(
267
+ {class: `${CLASS_NAME_ALERT}-wrapper ${CLASS_NAME_ALERT}-${this._params.theme}`},
268
+ [
269
+ html.div(
270
+ {class: `${CLASS_NAME_ALERT}-content`},
271
+ [
272
+ icon,
273
+ html.div(
274
+ {class: `${CLASS_NAME_ALERT}-content--message`},
275
+ [
276
+ html.div(
277
+ {class: `${CLASS_NAME_ALERT}-content--title`},
278
+ this._params.message.title
279
+ ),
280
+ html.div(
281
+ {class: `${CLASS_NAME_ALERT}-content--description`},
282
+ this._params.message.description
283
+ )
284
+ ]
285
+ )
286
+ ]
287
+ ),
335
288
  buttons
336
- ]);
289
+ ]
290
+ );
291
+ }
337
292
 
338
- $body.appendChild(wrapper);
339
- });
293
+ _buildOverlay() {
294
+ let targetContainers = ['.vg-modal', '.vg-sidebar'];
295
+ const containerWrap = this._params.relatedTarget.closest(targetContainers.join(', '));
296
+
297
+ const modal = VGModal.getOrCreateInstance(containerWrap);
298
+ const container = Selectors.find('.vg-modal-content', modal._element) || containerWrap;
299
+
300
+ const overlay = document.createElement('div');
301
+
302
+ overlay.className = `${CLASS_NAME_ALERT}-overlay`;
303
+ overlay.append(this._buildContent());
304
+
305
+ container.append(overlay);
306
+
307
+ return {
308
+ element: overlay,
309
+ render: modal,
310
+ };
340
311
  }
341
312
 
342
- /**
343
- * Создаёт кнопку и добавляет её в контейнер.
344
- *
345
- * @private
346
- * @param {HTMLElement} container - Родительский элемент.
347
- * @param {'agree'|'cancel'} key - Ключ кнопки.
348
- * @returns {void}
349
- */
350
313
  _createButton(container, key) {
351
314
  const button = this._params.buttons[key];
352
315
  if (!button || button.element) {
@@ -385,51 +348,25 @@ const EVENT_KEY_ACCEPT = `${NAME_KEY}.accept`;
385
348
  const EVENT_KEY_REJECT = `${NAME_KEY}.reject`;
386
349
  const EVENT_KEY_FINALLY = `${NAME_KEY}.finally`;
387
350
 
388
- /**
389
- * Класс для работы с алертами по data-атрибутам.
390
- *
391
- * @class
392
- * @extends BaseModule
393
- * @example
394
- * <button data-vg-toggle="alert" data-ajax-route="/delete/1">Удалить</button>
395
- */
396
351
  class VGAlertConfirm extends BaseModule {
397
- /**
398
- * Создаёт экземпляр VGAlertConfirm.
399
- *
400
- * @param {HTMLElement} element - DOM-элемент.
401
- * @param {AlertParams} options - Параметры.
402
- */
403
352
  constructor(element, options = {}) {
404
353
  super(element);
405
354
  this._params = this._getParams(element, mergeDeepObject({}, options));
406
355
  }
407
356
 
408
- /**
409
- * Возвращает имя модуля.
410
- * @returns {string}
411
- */
412
357
  static get NAME() {
413
358
  return NAME;
414
359
  }
415
360
 
416
- /**
417
- * Возвращает ключевое имя с префиксом.
418
- * @returns {string}
419
- */
420
361
  static get NAME_KEY() {
421
362
  return NAME_KEY;
422
363
  }
423
364
 
424
- /**
425
- * Запускает логику алерта: вызывает модальное окно и обрабатывает результат.
426
- *
427
- * @param {typeof VGAlert} AlertClass - Класс алерта.
428
- * @returns {void}
429
- */
430
365
  run(AlertClass) {
431
366
  if (this._params.mode !== "confirm") return;
432
367
 
368
+ this._params.relatedTarget = this._element;
369
+
433
370
  AlertClass.call(this._params)
434
371
  .then((resolve) => {
435
372
  if (!resolve.accepted) return Promise.reject(resolve);
@@ -447,12 +384,6 @@ class VGAlertConfirm extends BaseModule {
447
384
  });
448
385
  }
449
386
 
450
- /**
451
- * Выполняет AJAX-запрос после подтверждения.
452
- *
453
- * @private
454
- * @returns {Promise<Object>} Ответ от сервера.
455
- */
456
387
  _ajax() {
457
388
  return new Promise((resolve) => {
458
389
  this._route((status, data) => {
@@ -482,4 +413,4 @@ EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, functi
482
413
  });
483
414
  });
484
415
 
485
- export default VGAlert;
416
+ export default VGAlert;