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.
- package/README.md +32 -4
- package/app/modules/vgalert/js/vgalert.js +266 -78
- package/app/modules/vgalert/scss/_variables.scss +14 -1
- package/app/modules/vgalert/scss/vgalert.scss +103 -29
- package/app/modules/vgdynamictable/js/editable.js +430 -430
- package/app/modules/vgdynamictable/js/options.js +619 -619
- package/app/modules/vgdynamictable/js/utils/common.js +34 -34
- package/app/modules/vgmodal/js/vgmodal.interaction.js +216 -0
- package/app/modules/vgmodal/js/vgmodal.js +355 -361
- package/app/modules/vgmodal/js/vgmodal.minimized.js +227 -0
- package/app/modules/vgmodal/scss/vgmodal.scss +68 -4
- package/app/modules/vgnav/js/vgnav.js +45 -22
- package/app/modules/vgnav/scss/vgnav.scss +22 -14
- package/app/modules/vgnestable/README.md +39 -10
- package/app/modules/vgnestable/js/vgnestable.js +358 -141
- package/app/modules/vgnestable/scss/_variables.scss +15 -8
- package/app/modules/vgnestable/scss/vgnestable.scss +51 -13
- package/app/modules/vgselect/js/vgselect.js +129 -82
- package/app/modules/vgselect/readme.md +11 -9
- package/app/modules/vgsidebar/js/vgsidebar.js +79 -44
- package/app/utils/js/components/backdrop.js +116 -89
- package/build/vgapp.css +4492 -2
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +32145 -3
- package/build/vgapp.js.map +1 -1
- package/package.json +55 -1
|
@@ -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:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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.
|
|
461
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
-
| `
|
|
67
|
-
| `search.
|
|
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.
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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 =
|
|
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
|
-
|
|
208
|
-
this._element
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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;
|