vgapp 1.4.0 → 1.4.2

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.
@@ -39,7 +39,7 @@
39
39
  }
40
40
  }
41
41
 
42
- &-item {
42
+ &-item {
43
43
  margin: var(--vg-nestable-item-margin);
44
44
  position: relative;
45
45
  transition: var(--vg-nestable-item-transition);
@@ -48,10 +48,43 @@
48
48
  opacity: var(--vg-nestable-item-dragging-opacity);
49
49
  }
50
50
 
51
- &.is-drag-ghost {
52
- opacity: var(--vg-nestable-item-ghost-opacity);
53
- }
54
- }
51
+ &.is-drag-ghost {
52
+ opacity: var(--vg-nestable-item-ghost-opacity);
53
+ }
54
+
55
+ &.is-drop-before > .vg-nestable-inner {
56
+ box-shadow: inset 0 3px 0 var(--vg-nestable-item-drop-line);
57
+ }
58
+
59
+ &.is-drop-after > .vg-nestable-inner {
60
+ box-shadow: inset 0 -3px 0 var(--vg-nestable-item-drop-line);
61
+ }
62
+
63
+ &.is-drop-attachable > .vg-nestable-inner {
64
+ outline: var(--vg-nestable-item-drop-attachable-outline);
65
+ outline-offset: 2px;
66
+ }
67
+
68
+ &.is-drop-inside > .vg-nestable-inner {
69
+ border-color: var(--vg-nestable-item-drop-inside-border);
70
+ background: var(--vg-nestable-item-drop-inside-bg);
71
+ box-shadow: 0 0 0 3px var(--vg-nestable-item-drop-inside-bg);
72
+ }
73
+
74
+ &.is-drop-denied > .vg-nestable-inner {
75
+ border-color: var(--vg-nestable-item-drop-denied);
76
+ box-shadow: 0 0 0 2px rgba(220, 53, 69, .12);
77
+ }
78
+
79
+ &.is-drag-disabled > .vg-nestable-inner {
80
+ background: var(--vg-nestable-item-disabled-bg);
81
+ }
82
+
83
+ &.is-drag-disabled > .vg-nestable-inner > .vg-nestable-handle {
84
+ color: var(--vg-nestable-item-disabled-handle-color);
85
+ cursor: not-allowed;
86
+ }
87
+ }
55
88
 
56
89
  &-inner {
57
90
  display: flex;
@@ -75,8 +108,9 @@
75
108
  border: var(--vg-nestable-handle-border);
76
109
  background: var(--vg-nestable-handle-bg);
77
110
  border-radius: var(--vg-nestable-handle-border-radius);
78
- cursor: var(--vg-nestable-handle-cursor);
79
- user-select: none;
111
+ cursor: var(--vg-nestable-handle-cursor);
112
+ user-select: none;
113
+ touch-action: none;
80
114
 
81
115
  &:active {
82
116
  cursor: var(--vg-nestable-handle-active-cursor);
@@ -155,9 +189,13 @@
155
189
  height: 0 !important;
156
190
  }
157
191
 
158
- &-drag-element {
159
- opacity: var(--vg-nestable-drag-element-opacity);
160
- transform: var(--vg-nestable-drag-element-transform);
161
- box-shadow: var(--vg-nestable-drag-element-shadow);
162
- }
163
- }
192
+ &-item.is-drag-ghost#{&}-drag-element {
193
+ opacity: var(--vg-nestable-drag-element-opacity);
194
+ transform: var(--vg-nestable-drag-element-transform);
195
+ box-shadow: var(--vg-nestable-drag-element-shadow);
196
+
197
+ > .vg-nestable-inner {
198
+ background: var(--vg-nestable-inner-bg);
199
+ }
200
+ }
201
+ }
@@ -68,16 +68,17 @@ class VGSelect extends BaseModule {
68
68
  constructor(element, params = {}) {
69
69
  super(element, params);
70
70
 
71
- this._params = this._getParams(element, mergeDeepObject({
72
- lang: document.documentElement.lang || 'ru',
71
+ this._params = this._getParams(element, mergeDeepObject({
72
+ lang: document.documentElement.lang || 'ru',
73
+ autosearch: true,
73
74
  // Dropdown placement behavior:
74
75
  // - none: default CSS positioning (no JS)
75
76
  // - auto: choose top/bottom based on available space in overflow ancestor/viewport
76
77
  // - top: force open upwards
77
78
  // - bottom: force open downwards
78
79
  position: 'none',
79
- search: {
80
- enabled: true,
80
+ search: {
81
+ enabled: false,
81
82
  route: '',
82
83
  remote: false,
83
84
  delay: 300,
@@ -102,9 +103,10 @@ class VGSelect extends BaseModule {
102
103
  onLoadNext: null,
103
104
  }, params));
104
105
 
105
- this._observer = null;
106
- this._observerTimeout = null;
107
- this._drop = Selectors.find(SELECTOR_DROPDOWN, this._element);
106
+ this._observer = null;
107
+ this._observerTimeout = null;
108
+ this._visibilityTransitionId = 0;
109
+ this._drop = Selectors.find(SELECTOR_DROPDOWN, this._element);
108
110
  this._searchTerm = '';
109
111
  this._currentPage = 1;
110
112
  this._totalPages = null;
@@ -120,8 +122,9 @@ class VGSelect extends BaseModule {
120
122
  this._positioningScrollParent = null;
121
123
  this._boundUpdateDropdownPlacement = () => this._updateDropdownPlacement();
122
124
 
123
- this._initObserver();
124
- this._initLoadMoreButton();
125
+ this._initObserver();
126
+ this._syncSearch();
127
+ this._initLoadMoreButton();
125
128
 
126
129
  this._triggerEvent(EVENT_KEY_INIT);
127
130
  this._callCallback('onInit');
@@ -275,12 +278,39 @@ class VGSelect extends BaseModule {
275
278
  return new Set([...DATA_ATTR_COPY_EXCLUDE_DEFAULT, ...customExcluded]);
276
279
  }
277
280
 
278
- static _normalizeDataAttrKey(value) {
281
+ static _normalizeDataAttrKey(value) {
279
282
  return String(value || '')
280
283
  .trim()
281
284
  .replace(/^data-/, '')
282
285
  .toLowerCase();
283
- }
286
+ }
287
+
288
+ /**
289
+ * Определяет, нужно ли показывать поиск.
290
+ * search.enabled и search.remote включают его при любом количестве опций.
291
+ * autosearch: true использует порог 7, число задаёт собственный порог.
292
+ * @param {HTMLSelectElement} selector
293
+ * @param {Object} params
294
+ * @returns {boolean}
295
+ * @private
296
+ */
297
+ static _isSearchEnabled(selector, params = {}) {
298
+ if (params.search?.enabled === true || params.search?.remote === true) return true;
299
+
300
+ const autosearch = params.autosearch;
301
+ if (autosearch === false) return false;
302
+
303
+ const numericThreshold = Number(autosearch);
304
+ const threshold = autosearch !== true && Number.isFinite(numericThreshold) && numericThreshold >= 0
305
+ ? numericThreshold
306
+ : 7;
307
+ const optionCount = Array.from(selector?.options || []).filter(option => {
308
+ if (option.hidden) return false;
309
+ return option.value !== '' || option.textContent.trim() !== '';
310
+ }).length;
311
+
312
+ return optionCount > threshold;
313
+ }
284
314
 
285
315
  /**
286
316
  * Проверяет, является ли значение "пустым" (соответствует placeholder)
@@ -396,48 +426,57 @@ class VGSelect extends BaseModule {
396
426
  selector.insertAdjacentElement('afterend', container);
397
427
  selector.dataset.inited = 'true';
398
428
 
399
- this.getOrCreateInstance(container, params);
400
- this.updateUI(selector);
401
- const instance = VGSelect.getInstance(container);
402
-
403
- let searchInput = null;
404
- if (Manipulator.has(selector, 'data-search-enabled')) {
405
- const search = document.createElement('div');
406
- search.classList.add(CLASS_NAME_SEARCH);
407
- searchInput = document.createElement('input');
408
- searchInput.name = 'vg-select-search';
409
- searchInput.type = 'text';
410
- searchInput.placeholder = lang_titles(instance._params.lang, NAME)['search'];
411
- searchInput.autocomplete = 'off';
412
- searchInput.setAttribute('role', 'searchbox');
413
- search.appendChild(searchInput);
414
- dropdown.insertBefore(search, dropdown.firstChild);
415
- }
416
-
417
- if (searchInput && instance) {
418
- let searchTimeout;
419
-
420
- searchInput.addEventListener('input', (e) => {
421
- const term = e.target.value.trim();
422
- const params = instance._params;
423
-
424
- instance._callCallback('onSearch', { term });
425
-
426
- if (params.search.remote && params.search.route) {
427
- if (term.length < (params.search.minterm || 1)) return;
428
-
429
- clearTimeout(searchTimeout);
430
- searchTimeout = setTimeout(() => {
431
- instance._fetchRemoteData(term);
432
- }, params.search.delay || 300);
433
- } else {
434
- instance._filterLocalOptions(term);
435
- }
436
- });
437
- }
438
-
439
- return container;
440
- }
429
+ this.getOrCreateInstance(container, params);
430
+ this.updateUI(selector);
431
+
432
+ return container;
433
+ }
434
+
435
+ /**
436
+ * Добавляет или убирает поле поиска согласно search.enabled/autosearch.
437
+ * @private
438
+ */
439
+ _syncSearch() {
440
+ const selector = this._element.previousElementSibling;
441
+ if (!selector || selector.tagName !== 'SELECT' || !this._drop) return;
442
+
443
+ const enabled = VGSelect._isSearchEnabled(selector, this._params);
444
+ const currentSearch = this._drop.querySelector(`.${CLASS_NAME_SEARCH}`);
445
+ if (!enabled) {
446
+ currentSearch?.remove();
447
+ return;
448
+ }
449
+ if (currentSearch) return;
450
+
451
+ const search = document.createElement('div');
452
+ search.classList.add(CLASS_NAME_SEARCH);
453
+ const searchInput = document.createElement('input');
454
+ searchInput.name = 'vg-select-search';
455
+ searchInput.type = 'text';
456
+ searchInput.placeholder = lang_titles(this._params.lang, NAME)['search'];
457
+ searchInput.autocomplete = 'off';
458
+ searchInput.setAttribute('role', 'searchbox');
459
+ search.appendChild(searchInput);
460
+ this._drop.insertBefore(search, this._drop.firstChild);
461
+
462
+ let searchTimeout;
463
+ searchInput.addEventListener('input', (e) => {
464
+ const term = e.target.value.trim();
465
+ const params = this._params;
466
+
467
+ this._callCallback('onSearch', { term });
468
+ if (params.search.remote && params.search.route) {
469
+ if (term.length < (params.search.minterm || 1)) return;
470
+
471
+ clearTimeout(searchTimeout);
472
+ searchTimeout = setTimeout(() => {
473
+ this._fetchRemoteData(term);
474
+ }, params.search.delay || 300);
475
+ } else {
476
+ this._filterLocalOptions(term);
477
+ }
478
+ });
479
+ }
441
480
 
442
481
  /**
443
482
  * Переключает открытие/закрытие выпадающего списка
@@ -454,11 +493,16 @@ class VGSelect extends BaseModule {
454
493
  show(relatedTarget) {
455
494
  if (isDisabled(this._element)) return;
456
495
 
457
- const e = EventHandler.trigger(this._element, EVENT_KEY_SHOW, { relatedTarget });
458
- if (e.defaultPrevented) return;
459
-
460
- this._element.classList.add(CLASS_NAME_SHOW);
461
- if (this._getPositionMode() !== 'none') {
496
+ const e = EventHandler.trigger(this._element, EVENT_KEY_SHOW, { relatedTarget });
497
+ if (e.defaultPrevented) return;
498
+
499
+ const transitionId = ++this._visibilityTransitionId;
500
+ this._element.classList.add(CLASS_NAME_SHOW);
501
+ // active задаёт видимое состояние dropdown (opacity/height). Оно не должно
502
+ // зависеть от таймера: под нагрузкой отложенный callback оставлял уже
503
+ // открытый dropdown прозрачным и с height: 0.
504
+ this._element.classList.add(CLASS_NAME_ACTIVE);
505
+ if (this._getPositionMode() !== 'none') {
462
506
  this._setupDropdownPlacement();
463
507
  } else {
464
508
  // Ensure no leftovers if the instance had JS positioning before.
@@ -472,14 +516,12 @@ class VGSelect extends BaseModule {
472
516
  const toggle = this._element.querySelector(SELECTOR_DATA_TOGGLE);
473
517
  toggle.setAttribute('aria-expanded', 'true');
474
518
 
475
- if (this._params.search?.enabled) {
476
- const input = this._element.querySelector(SELECTOR_SEARCH_INPUT);
477
- if (input) input.focus();
478
- }
479
-
480
- return this._queueCallback(() => {
481
- this._element.classList.add(CLASS_NAME_ACTIVE);
482
- this._updateDropdownPlacement();
519
+ const searchInput = this._element.querySelector(SELECTOR_SEARCH_INPUT);
520
+ if (searchInput) searchInput.focus();
521
+
522
+ return this._queueCallback(() => {
523
+ if (transitionId !== this._visibilityTransitionId || !this._isShown()) return;
524
+ this._updateDropdownPlacement();
483
525
  EventHandler.trigger(this._element, EVENT_KEY_SHOWN, { relatedTarget });
484
526
  this._triggerEvent(EVENT_KEY_OPEN);
485
527
  this._callCallback('onShow');
@@ -500,16 +542,18 @@ class VGSelect extends BaseModule {
500
542
  * @private
501
543
  */
502
544
  _completeHide(relatedTarget = {}) {
503
- const e = EventHandler.trigger(this._element, EVENT_KEY_HIDE, relatedTarget);
504
- if (e.defaultPrevented) return;
505
-
506
- this._teardownDropdownPlacement();
545
+ const e = EventHandler.trigger(this._element, EVENT_KEY_HIDE, relatedTarget);
546
+ if (e.defaultPrevented) return;
547
+
548
+ const transitionId = ++this._visibilityTransitionId;
549
+ this._teardownDropdownPlacement();
507
550
 
508
551
  this._element.classList.remove(CLASS_NAME_ACTIVE);
509
552
  this._element.querySelector(SELECTOR_DATA_TOGGLE).setAttribute('aria-expanded', 'false');
510
-
511
- this._queueCallback(() => {
512
- this._element.classList.remove(CLASS_NAME_SHOW);
553
+
554
+ this._queueCallback(() => {
555
+ if (transitionId !== this._visibilityTransitionId) return;
556
+ this._element.classList.remove(CLASS_NAME_SHOW);
513
557
  EventHandler.trigger(this._element, EVENT_KEY_HIDDEN, relatedTarget);
514
558
  this._triggerEvent(EVENT_KEY_CLOSE);
515
559
  this._callCallback('onHide');
@@ -593,16 +637,18 @@ class VGSelect extends BaseModule {
593
637
  this._element.classList.remove('disabled');
594
638
  }
595
639
 
596
- const drop = this._element.querySelector(SELECTOR_DROPDOWN);
597
- VGSelect.buildListOptions(select, drop, this._params);
598
- VGSelect.updateUI(select);
640
+ const drop = this._element.querySelector(SELECTOR_DROPDOWN);
641
+ VGSelect.buildListOptions(select, drop, this._params);
642
+ this._syncSearch();
643
+ VGSelect.updateUI(select);
599
644
  }
600
645
 
601
646
  /**
602
647
  * Освобождает ресурсы (отключает observer, очищает таймеры)
603
648
  */
604
- dispose() {
605
- if (this._observer) {
649
+ dispose() {
650
+ this._visibilityTransitionId++;
651
+ if (this._observer) {
606
652
  this._observer.disconnect();
607
653
  this._observer = null;
608
654
  }
@@ -1233,10 +1279,11 @@ class VGSelect extends BaseModule {
1233
1279
  });
1234
1280
  }
1235
1281
 
1236
- if (isRebuild) {
1237
- const drop = container.querySelector(`.${CLASS_NAME_DROPDOWN}`);
1238
- VGSelect.buildListOptions(select, drop, instance?._params || {});
1239
- instance?._triggerEvent(EVENT_KEY_REBUILD);
1282
+ if (isRebuild) {
1283
+ const drop = container.querySelector(`.${CLASS_NAME_DROPDOWN}`);
1284
+ VGSelect.buildListOptions(select, drop, instance?._params || {});
1285
+ instance?._syncSearch();
1286
+ instance?._triggerEvent(EVENT_KEY_REBUILD);
1240
1287
  } else {
1241
1288
  this.updateUI(select);
1242
1289
  }
@@ -38,10 +38,11 @@
38
38
  ```js
39
39
  js import VGSelect from './app/modules/vgselect/js/vgselect';
40
40
  // Инициализация
41
- VGSelect.init(document.getElementById('mySelect'), {
42
- lang: 'ru',
43
- placeholder: 'Выберите значение',
44
- search: {
41
+ VGSelect.init(document.getElementById('mySelect'), {
42
+ lang: 'ru',
43
+ placeholder: 'Выберите значение',
44
+ autosearch: true,
45
+ search: {
45
46
  enabled: true,
46
47
  remote: true,
47
48
  route: '/api/search',
@@ -61,10 +62,11 @@ VGSelect.init(document.getElementById('mySelect'), {
61
62
 
62
63
  | Параметр | Тип | Описание |
63
64
  |--------|-----|---------|
64
- | `lang` | `string` | Язык интерфейса (поддерживается i18n). По умолчанию: `ru` |
65
- | `placeholder` | `string` | Текст плейсхолдера |
66
- | `search.enabled` | `boolean` | Включить поле поиска |
67
- | `search.remote` | `boolean` | Поиск через AJAX |
65
+ | `lang` | `string` | Язык интерфейса (поддерживается i18n). По умолчанию: `ru` |
66
+ | `placeholder` | `string` | Текст плейсхолдера |
67
+ | `autosearch` | `boolean \| number` | Автоматически включить поиск, если опций больше порога. `true` — порог 7 (по умолчанию), число — собственный порог, `false` — отключить автоматику |
68
+ | `search.enabled` | `boolean` | Включить поле поиска вручную при любом количестве опций |
69
+ | `search.remote` | `boolean` | Поиск через AJAX; при `true` поле поиска включается независимо от количества локальных опций |
68
70
  | `search.route` | `string` | URL для удалённого поиска |
69
71
  | `search.delay` | `number` | Задержка перед запросом (мс) |
70
72
  | `search.minTerm` | `number` | Минимальная длина запроса |
@@ -177,4 +179,4 @@ MIT. Свободно использовать и модифицировать.
177
179
 
178
180
  📌 *Разработано в рамках фронтенд-системы VG Modules.*
179
181
  > 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
180
- > 📍 Поддерживается в проектах VEGAS
182
+ > 📍 Поддерживается в проектах VEGAS
@@ -19,7 +19,10 @@ const NAME_KEY = 'vg.sidebar';
19
19
  /**
20
20
  * @constant {string} SELECTOR_DATA_TOGGLE - Селектор для элементов активации сайдбара.
21
21
  */
22
- const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="sidebar"]';
22
+ const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="sidebar"]';
23
+ const OPEN_SELECTOR = '.vg-sidebar.show';
24
+ const BACKDROP_OWNER_ATTR = 'data-vg-backdrop-owner';
25
+ const BACKDROP_OWNER_VALUE = 'sidebar';
23
26
 
24
27
  /**
25
28
  * @constant {string} CLASS_NAME_SHOW - Класс, отвечающий за отображение сайдбара.
@@ -104,8 +107,9 @@ class VGSidebar extends BaseModule {
104
107
  }
105
108
  }, params));
106
109
 
107
- this._scrollBar = new ScrollBarHelper();
108
- this._params.animation.delay = this._params.animation.enable ? this._params.animation.delay : 0;
110
+ this._scrollBar = new ScrollBarHelper();
111
+ this._backdropElement = null;
112
+ this._params.animation.delay = this._params.animation.enable ? this._params.animation.delay : 0;
109
113
 
110
114
  this._addEventListeners();
111
115
  this._dismissElement();
@@ -124,9 +128,18 @@ class VGSidebar extends BaseModule {
124
128
  * Статическое свойство: ключ для событий и данных.
125
129
  * @returns {string}
126
130
  */
127
- static get NAME_KEY() {
128
- return NAME_KEY;
129
- }
131
+ static get NAME_KEY() {
132
+ return NAME_KEY;
133
+ }
134
+
135
+ static getOpenSidebars(excludeElement = null) {
136
+ return Selectors.findAll(OPEN_SELECTOR).filter(sidebar => sidebar !== excludeElement);
137
+ }
138
+
139
+ static getBackdropElement() {
140
+ const sidebarBackdrops = Selectors.findAll(`.vg-backdrop[${BACKDROP_OWNER_ATTR}="${BACKDROP_OWNER_VALUE}"]`);
141
+ return sidebarBackdrops.length ? sidebarBackdrops[sidebarBackdrops.length - 1] : null;
142
+ }
130
143
 
131
144
  /**
132
145
  * Переключает состояние сайдбара (открыть/закрыть).
@@ -145,8 +158,8 @@ class VGSidebar extends BaseModule {
145
158
  * @param {HTMLElement} [relatedTarget] - Элемент, инициировавший открытие.
146
159
  * @returns {void}
147
160
  */
148
- show(relatedTarget) {
149
- if (isDisabled(this._element)) return;
161
+ show(relatedTarget) {
162
+ if (isDisabled(this._element) || this._isShown()) return;
150
163
 
151
164
  if (relatedTarget) {
152
165
  this._params = this._getParams(relatedTarget, this._params);
@@ -159,13 +172,33 @@ class VGSidebar extends BaseModule {
159
172
  const showEvent = EventHandler.trigger(this._element, EVENT_KEYS.SHOW, { relatedTarget });
160
173
  if (showEvent.defaultPrevented) return;
161
174
 
162
- if (this._params.backdrop) {
163
- Backdrop.show();
164
- }
165
-
166
- if (this._params.overflow) {
167
- this._scrollBar.hide();
168
- }
175
+ const alreadyOpenSidebars = VGSidebar.getOpenSidebars(this._element);
176
+ const hasOpenSidebar = alreadyOpenSidebars.length > 0;
177
+ let currentBackdrop = null;
178
+ if (this._params.backdrop) {
179
+ if (hasOpenSidebar) {
180
+ currentBackdrop = VGSidebar.getBackdropElement();
181
+ if (currentBackdrop) {
182
+ this._backdropElement = currentBackdrop;
183
+ } else {
184
+ Backdrop.show((backdrop) => {
185
+ currentBackdrop = backdrop;
186
+ this._backdropElement = backdrop;
187
+ backdrop.setAttribute(BACKDROP_OWNER_ATTR, BACKDROP_OWNER_VALUE);
188
+ });
189
+ }
190
+ } else {
191
+ Backdrop.show((backdrop) => {
192
+ currentBackdrop = backdrop;
193
+ this._backdropElement = backdrop;
194
+ backdrop.setAttribute(BACKDROP_OWNER_ATTR, BACKDROP_OWNER_VALUE);
195
+ });
196
+ }
197
+ }
198
+
199
+ if (this._params.overflow && !hasOpenSidebar) {
200
+ this._scrollBar.hide();
201
+ }
169
202
 
170
203
  if (this._params.hash) {
171
204
  history.pushState(null, '', `#${this._element.id}`);
@@ -179,13 +212,13 @@ class VGSidebar extends BaseModule {
179
212
  }
180
213
 
181
214
  this._element.classList.add(CLASS_NAME_SHOW);
182
- document.body.classList.add(CLASS_NAME_OPEN);
183
-
184
- const completeCallback = () => {
185
- const backdrop = Selectors.find('.vg-backdrop');
186
- if (backdrop) {
187
- EventHandler.on(backdrop, 'mousedown.vg.backdrop', () => this.hide());
188
- }
215
+ document.body.classList.add(CLASS_NAME_OPEN);
216
+
217
+ const completeCallback = () => {
218
+ const backdrop = this._params.backdrop ? (currentBackdrop || VGSidebar.getBackdropElement()) : null;
219
+ if (backdrop) {
220
+ EventHandler.on(backdrop, 'mousedown.vg.backdrop', () => this.hide());
221
+ }
189
222
  EventHandler.trigger(this._element, EVENT_KEYS.SHOWN, { relatedTarget });
190
223
  };
191
224
 
@@ -198,29 +231,37 @@ class VGSidebar extends BaseModule {
198
231
  * @param {boolean} [isLeaveBackDrop=false] - Не убирать подложку.
199
232
  * @returns {void}
200
233
  */
201
- hide(isLeaveBackDrop = false) {
202
- if (isDisabled(this._element)) return;
234
+ hide(isLeaveBackDrop = false) {
235
+ if (isDisabled(this._element) || !this._isShown()) return;
203
236
 
204
237
  const hideEvent = EventHandler.trigger(this._element, EVENT_KEYS.HIDE);
205
238
  if (hideEvent.defaultPrevented) return;
206
239
 
207
- document.body.classList.remove(CLASS_NAME_OPEN);
208
- this._element.classList.remove(CLASS_NAME_SHOW);
240
+ this._element.classList.remove(CLASS_NAME_SHOW);
241
+ const remainingOpenSidebars = VGSidebar.getOpenSidebars(this._element);
242
+ if (!remainingOpenSidebars.length) {
243
+ document.body.classList.remove(CLASS_NAME_OPEN);
244
+ }
209
245
 
210
246
  setTimeout(() => {
211
247
  this._element.setAttribute('aria-expanded', 'false');
212
248
 
213
249
  const completeCallback = () => {
214
- if (!isLeaveBackDrop) {
215
- if (this._params.backdrop) {
216
- Backdrop.hide(() => {
217
- if (this._params.overflow) {
218
- this._scrollBar.reset();
219
- }
220
- });
221
- } else if (this._params.overflow) {
222
- this._scrollBar.reset();
223
- }
250
+ if (!isLeaveBackDrop) {
251
+ if (this._params.backdrop && !remainingOpenSidebars.length) {
252
+ Backdrop.hide(() => {
253
+ this._backdropElement = null;
254
+ if (this._params.overflow && !Backdrop.isActive()) {
255
+ this._scrollBar.reset();
256
+ }
257
+ }, this._backdropElement || VGSidebar.getBackdropElement());
258
+ } else if (this._params.backdrop) {
259
+ this._backdropElement = null;
260
+ } else if (this._params.overflow) {
261
+ if (!remainingOpenSidebars.length && !Backdrop.isActive()) {
262
+ this._scrollBar.reset();
263
+ }
264
+ }
224
265
 
225
266
  if (this._params.hash && window.location.hash === `#${this._element.id}`) {
226
267
  history.replaceState(
@@ -305,13 +346,7 @@ EventHandler.on(document, EVENT_KEYS.CLICK_DATA_API, SELECTOR_DATA_TOGGLE, funct
305
346
  this.setAttribute('aria-expanded', 'false');
306
347
  });
307
348
 
308
- // Закрываем уже открытый сайдбар
309
- const alreadyOpen = Selectors.find('.vg-sidebar.show');
310
- if (alreadyOpen && alreadyOpen !== target) {
311
- VGSidebar.getInstance(alreadyOpen).hide();
312
- }
313
-
314
- const instance = VGSidebar.getOrCreateInstance(target);
349
+ const instance = VGSidebar.getOrCreateInstance(target);
315
350
  instance.toggle(this);
316
351
  });
317
352
 
@@ -332,4 +367,4 @@ EventHandler.on(document, EVENT_KEYS.DOM_LOADED_DATA_API, function () {
332
367
  }
333
368
  });
334
369
 
335
- export default VGSidebar;
370
+ export default VGSidebar;