vgapp 0.8.1 → 0.8.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/CHANGELOG.md +10 -0
- package/app/langs/en/buttons.json +8 -0
- package/app/langs/en/messages.json +3 -0
- package/app/langs/en/titles.json +3 -0
- package/app/langs/ru/buttons.json +8 -0
- package/app/langs/ru/messages.json +3 -0
- package/app/langs/ru/titles.json +3 -0
- package/app/modules/vgloadmore/js/vgloadmore.js +159 -8
- package/app/modules/vgrollup/js/vgrollup.js +328 -160
- package/app/modules/vgrollup/readme.md +196 -0
- package/app/modules/vgselect/js/handlers.js +220 -0
- package/app/modules/vgselect/js/vgselect.js +783 -298
- package/app/modules/vgselect/readme.md +180 -0
- package/app/modules/vgselect/scss/_variables.scss +20 -0
- package/app/modules/vgselect/scss/vgselect.scss +42 -2
- package/app/modules/vgsidebar/js/vgsidebar.js +194 -84
- package/app/modules/vgsidebar/readme.md +157 -0
- package/app/modules/vgspy/js/vgspy.js +236 -132
- package/app/modules/vgspy/readme.md +105 -0
- package/app/modules/vgtabs/js/vgtabs.js +290 -182
- package/app/modules/vgtabs/readme.md +156 -0
- package/app/modules/vgtoast/js/vgtoast.js +260 -156
- package/app/modules/vgtoast/readme.md +145 -0
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# VGSelect — Кастомный `<select>` с расширенными возможностями
|
|
2
|
+
|
|
3
|
+
`VGSelect` — это продвинутый JavaScript-модуль для замены стандартного HTML-элемента `<select>` на полностью кастомизируемый компонент с поддержкой поиска, мультивыбора, динамической загрузки данных, i18n, пагинации и автоматического обновления через `MutationObserver`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✅ Основные возможности
|
|
8
|
+
|
|
9
|
+
| Функция | Поддержка |
|
|
10
|
+
|--------|---------|
|
|
11
|
+
| 🔹 Кастомный дизайн | ✅ |
|
|
12
|
+
| 🔹 Поиск (локальный и удалённый) | ✅ |
|
|
13
|
+
| 🔹 Поддержка мультивыбора (`multiple`) | ✅ |
|
|
14
|
+
| 🔹 Динамическая загрузка данных (AJAX) | ✅ |
|
|
15
|
+
| 🔹 Пагинация и "Загрузить ещё" | ✅ |
|
|
16
|
+
| 🔹 i18n (многоязычность) | ✅ |
|
|
17
|
+
| 🔹 Автообновление при изменении `<select>` | ✅ (`MutationObserver`) |
|
|
18
|
+
| 🔹 Обработка `optgroup` | ✅ |
|
|
19
|
+
| 🔹 Поддержка `disabled`, `required`, `placeholder` | ✅ |
|
|
20
|
+
| 🔹 Полная доступность (ARIA) | ✅ |
|
|
21
|
+
| 🔹 Коллбэки и события | ✅ |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 📦 Установка и инициализация
|
|
26
|
+
|
|
27
|
+
### HTML
|
|
28
|
+
```html
|
|
29
|
+
<select id="country" class="vg-select w-100" name="country" data-max="10" required>
|
|
30
|
+
<option value="2" data-price="1">Россия</option>
|
|
31
|
+
<option value="3" data-price="2">Узбекистан</option>
|
|
32
|
+
<option value="4" data-price="3">Казахстан</option>
|
|
33
|
+
<option value="5" data-price="4" selected>Белоруссия</option>
|
|
34
|
+
<option value="6" data-price="5" disabled>Китай</option>
|
|
35
|
+
</select>
|
|
36
|
+
```
|
|
37
|
+
### JavaScript
|
|
38
|
+
```js
|
|
39
|
+
js import VGSelect from './app/modules/vgselect/js/vgselect';
|
|
40
|
+
// Инициализация
|
|
41
|
+
VGSelect.init(document.getElementById('mySelect'), {
|
|
42
|
+
lang: 'ru',
|
|
43
|
+
placeholder: 'Выберите значение',
|
|
44
|
+
search: {
|
|
45
|
+
enabled: true,
|
|
46
|
+
remote: true,
|
|
47
|
+
route: '/api/search',
|
|
48
|
+
delay: 300,
|
|
49
|
+
minTerm: 1,
|
|
50
|
+
pagination: true,
|
|
51
|
+
loadMoreText: 'Загрузить ещё'
|
|
52
|
+
},
|
|
53
|
+
onInit: (element) => console.log('VGSelect инициализирован'),
|
|
54
|
+
onChange: (element, data) => console.log('Значение изменено:', data)
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🔧 Параметры инициализации
|
|
61
|
+
|
|
62
|
+
| Параметр | Тип | Описание |
|
|
63
|
+
|--------|-----|---------|
|
|
64
|
+
| `lang` | `string` | Язык интерфейса (поддерживается i18n). По умолчанию: `ru` |
|
|
65
|
+
| `placeholder` | `string` | Текст плейсхолдера |
|
|
66
|
+
| `search.enabled` | `boolean` | Включить поле поиска |
|
|
67
|
+
| `search.remote` | `boolean` | Поиск через AJAX |
|
|
68
|
+
| `search.route` | `string` | URL для удалённого поиска |
|
|
69
|
+
| `search.delay` | `number` | Задержка перед запросом (мс) |
|
|
70
|
+
| `search.minTerm` | `number` | Минимальная длина запроса |
|
|
71
|
+
| `search.pagination` | `boolean` | Включить пагинацию |
|
|
72
|
+
| `search.pageParam` | `string` | Название параметра страницы в URL (`page`) |
|
|
73
|
+
| `search.termParam` | `string` | Название параметра поиска (`q`) |
|
|
74
|
+
| `search.perPage` | `number` | Количество элементов на страницу |
|
|
75
|
+
| `search.loadMoreText` | `string` | Текст кнопки "Загрузить ещё" |
|
|
76
|
+
| `onInit` | `function` | Коллбэк при инициализации |
|
|
77
|
+
| `onShow` | `function` | Коллбэк при открытии |
|
|
78
|
+
| `onHide` | `function` | Коллбэк при закрытии |
|
|
79
|
+
| `onChange` | `function` | Коллбэк при изменении значения |
|
|
80
|
+
| `onSelect` | `function` | Коллбэк при выборе элемента |
|
|
81
|
+
| `onClear` | `function` | Коллбэк при очистке выбора |
|
|
82
|
+
| `onLoadNext` | `function` | Коллбэк при загрузке следующей страницы |
|
|
83
|
+
| `onSearch` | `function` | Коллбэк при вводе в поиске |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🌐 i18n (международные сообщения)
|
|
88
|
+
|
|
89
|
+
Модуль поддерживает локализацию через:
|
|
90
|
+
- `lang_titles(lang, component)` — заголовки
|
|
91
|
+
- `lang_messages(lang, component)` — сообщения (например, "Загрузка...")
|
|
92
|
+
- `lang_buttons(lang, component)` — кнопки (например, "Загрузить ещё")
|
|
93
|
+
|
|
94
|
+
Поддерживаемые языки: `ru`, `en` и др. (настраивается в ядре).
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 🔁 Динамическая загрузка данных
|
|
99
|
+
|
|
100
|
+
Если включён `search.remote`, компонент отправляет запрос на указанный `route` с параметрами:
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
?q=searchTerm&page=1&per_page=20
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Ожидается JSON-ответ:
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"results":
|
|
110
|
+
[
|
|
111
|
+
{ "id": "1", "text": "Опция 1" },
|
|
112
|
+
{ "id": "2", "text": "Опция 2" }
|
|
113
|
+
],
|
|
114
|
+
"pagination": {
|
|
115
|
+
"current_page": 1,
|
|
116
|
+
"total_pages": 5
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 🔍 Поиск
|
|
123
|
+
|
|
124
|
+
- **Локальный**: фильтрация уже существующих опций.
|
|
125
|
+
- **Удалённый**: AJAX-запрос с пагинацией.
|
|
126
|
+
- Поддержка кнопки **"Загрузить ещё"** при включённой пагинации.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🔄 Автообновление (MutationObserver)
|
|
131
|
+
|
|
132
|
+
Компонент автоматически обновляется при:
|
|
133
|
+
- Изменении `option` или `optgroup` в исходном `<select>`
|
|
134
|
+
- Изменении атрибутов: `disabled`, `required`, `hidden`, `style`
|
|
135
|
+
- Добавлении/удалении опций через DOM
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 🎯 API методы
|
|
140
|
+
|
|
141
|
+
| Метод | Описание |
|
|
142
|
+
|------|---------|
|
|
143
|
+
| `VGSelect.init(select, params, rebuild)` | Инициализация |
|
|
144
|
+
| `VGSelect.destroy(select)` | Удаление компонента |
|
|
145
|
+
| `VGSelect.updateUI(select)` | Обновление отображаемого значения |
|
|
146
|
+
| `VGSelect.changeSelector(select, value, data)` | Программная установка значения |
|
|
147
|
+
| `VGSelect.addOptions(select, data, { preserve })` | Добавление опций (удобно при AJAX) |
|
|
148
|
+
| `instance.show()` | Открыть выпадающий список |
|
|
149
|
+
| `instance.hide()` | Закрыть выпадающий список |
|
|
150
|
+
| `instance.toggle()` | Переключить состояние |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 📣 События
|
|
155
|
+
|
|
156
|
+
| Событие | Описание |
|
|
157
|
+
|--------|---------|
|
|
158
|
+
| `vg.select.init` | Инициализация завершена |
|
|
159
|
+
| `vg.select.show` | Начало открытия |
|
|
160
|
+
| `vg.select.shown` | Открытие завершено |
|
|
161
|
+
| `vg.select.hide` | Начало закрытия |
|
|
162
|
+
| `vg.select.hidden` | Закрытие завершено |
|
|
163
|
+
| `vg.select.change` | Значение изменено |
|
|
164
|
+
| `vg.select.select` | Элемент выбран |
|
|
165
|
+
| `vg.select.clear` | Выбор очищен |
|
|
166
|
+
| `vg.select.rebuild` | Список перестроен (после поиска) |
|
|
167
|
+
| `vg.select.loadNext` | Загружена следующая страница |
|
|
168
|
+
| `vg.select.error` | Ошибка (AJAX, данные и т.д.) |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 📝 Лицензия
|
|
173
|
+
|
|
174
|
+
MIT. Свободно использовать и модифицировать.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
📌 *Разработано в рамках фронтенд-системы VG Modules.*
|
|
179
|
+
> 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
|
|
180
|
+
> 📍 Поддерживается в проектах VEGAS
|
|
@@ -78,3 +78,23 @@ $select-search-map: (
|
|
|
78
78
|
background-color: #f2f2f2,
|
|
79
79
|
color: #000000
|
|
80
80
|
) !default;
|
|
81
|
+
|
|
82
|
+
$select-tags-map: (
|
|
83
|
+
gap: 4px,
|
|
84
|
+
width: 100%,
|
|
85
|
+
min-height: 32px,
|
|
86
|
+
padding: 2px 0
|
|
87
|
+
) !default;
|
|
88
|
+
|
|
89
|
+
$select-tag-map: (
|
|
90
|
+
gap: 4px,
|
|
91
|
+
background: #0d6efd,
|
|
92
|
+
color: white,
|
|
93
|
+
padding: 2px 6px,
|
|
94
|
+
border-radius: 4px,
|
|
95
|
+
font-size: 14px,
|
|
96
|
+
remove-width: 12px,
|
|
97
|
+
remove-height: 12px,
|
|
98
|
+
remove-stroke: white,
|
|
99
|
+
remove-stroke-width: 2
|
|
100
|
+
) !default;
|
|
@@ -18,8 +18,8 @@ select {
|
|
|
18
18
|
top: 0;
|
|
19
19
|
opacity: 0;
|
|
20
20
|
z-index: -1000;
|
|
21
|
-
width:
|
|
22
|
-
height:
|
|
21
|
+
width: 0;
|
|
22
|
+
height: 0;
|
|
23
23
|
display: inline-block;
|
|
24
24
|
visibility: hidden;
|
|
25
25
|
}
|
|
@@ -34,6 +34,8 @@ select {
|
|
|
34
34
|
@include mix-vars('select-optgroup-hover', $select-optgroup-hover-map);
|
|
35
35
|
@include mix-vars('select-list-hover', $select-list-hover-map);
|
|
36
36
|
@include mix-vars('select-search', $select-search-map);
|
|
37
|
+
@include mix-vars('select-tags', $select-tags-map);
|
|
38
|
+
@include mix-vars('select-tag', $select-tag-map);
|
|
37
39
|
--vg-select-list-max-height: #{$select-list-max-height};
|
|
38
40
|
--vg-select-list-scrollbar-width: #{$select-list-scrollbar-width};
|
|
39
41
|
--vg-select-list-scrollbar-bg: #{$select-list-scrollbar-bg};
|
|
@@ -46,6 +48,44 @@ select {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
&-tags {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: var(--vg-select-tags-gap);
|
|
55
|
+
align-items: center;
|
|
56
|
+
width: var(--vg-select-tags-width);
|
|
57
|
+
min-height: var(--vg-select-tags-min-height);
|
|
58
|
+
padding: var(--vg-select-tags-padding);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&-tag {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: var(--vg-select-tag-gap);
|
|
65
|
+
background: var(--vg-select-tag-background);
|
|
66
|
+
color: var(--vg-select-tag-color);
|
|
67
|
+
padding: var(--vg-select-tag-padding);
|
|
68
|
+
border-radius: var(--vg-select-tag-border-radius);
|
|
69
|
+
font-size: var(--vg-select-tag-font-size);
|
|
70
|
+
|
|
71
|
+
&-remove {
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
opacity: 0.7;
|
|
74
|
+
width: var(--vg-select-tag-remove-width);
|
|
75
|
+
height: var(--vg-select-tag-remove-height);
|
|
76
|
+
stroke: var(--vg-select-tag-remove-stroke);
|
|
77
|
+
stroke-width: var(--vg-select-tag-remove-stroke-width);
|
|
78
|
+
|
|
79
|
+
&:hover {
|
|
80
|
+
opacity: 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&-multiple-input {
|
|
86
|
+
font: inherit;
|
|
87
|
+
}
|
|
88
|
+
|
|
49
89
|
&-current {
|
|
50
90
|
@each $key, $value in $select-current-map {
|
|
51
91
|
#{$key}: var(--vg-select-current-#{$key})
|
|
@@ -1,35 +1,85 @@
|
|
|
1
1
|
import BaseModule from "../../base-module";
|
|
2
|
-
import {isDisabled, isVisible, mergeDeepObject} from "../../../utils/js/functions";
|
|
2
|
+
import { isDisabled, isVisible, mergeDeepObject } from "../../../utils/js/functions";
|
|
3
3
|
import EventHandler from "../../../utils/js/dom/event";
|
|
4
|
-
import {dismissTrigger} from "../../module-fn";
|
|
4
|
+
import { dismissTrigger } from "../../module-fn";
|
|
5
5
|
import Selectors from "../../../utils/js/dom/selectors";
|
|
6
6
|
import Backdrop from "../../../utils/js/components/backdrop";
|
|
7
7
|
import ScrollBarHelper from "../../../utils/js/components/scrollbar";
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
10
|
+
* @constant {string} NAME - Имя модуля.
|
|
12
11
|
*/
|
|
13
12
|
const NAME = 'sidebar';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @constant {string} NAME_KEY - Пространство имён для событий.
|
|
16
|
+
*/
|
|
14
17
|
const NAME_KEY = 'vg.sidebar';
|
|
15
|
-
const SELECTOR_DATA_TOGGLE= '[data-vg-toggle="sidebar"]';
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @constant {string} SELECTOR_DATA_TOGGLE - Селектор для элементов активации сайдбара.
|
|
21
|
+
*/
|
|
22
|
+
const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="sidebar"]';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @constant {string} CLASS_NAME_SHOW - Класс, отвечающий за отображение сайдбара.
|
|
26
|
+
*/
|
|
17
27
|
const CLASS_NAME_SHOW = 'show';
|
|
18
|
-
const CLASS_NAME_OPEN = 'vg-sidebar-open';
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const EVENT_KEY_LOADED = `${NAME_KEY}.loaded`;
|
|
29
|
+
/**
|
|
30
|
+
* @constant {string} CLASS_NAME_OPEN - Класс, добавляемый к body при открытом сайдбаре.
|
|
31
|
+
*/
|
|
32
|
+
const CLASS_NAME_OPEN = 'vg-sidebar-open';
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @constant {Object} EVENT_KEYS - Объект с ключами событий для модуля.
|
|
36
|
+
*/
|
|
37
|
+
const EVENT_KEYS = {
|
|
38
|
+
HIDE: `${NAME_KEY}.hide`,
|
|
39
|
+
HIDDEN: `${NAME_KEY}.hidden`,
|
|
40
|
+
SHOW: `${NAME_KEY}.show`,
|
|
41
|
+
SHOWN: `${NAME_KEY}.shown`,
|
|
42
|
+
LOADED: `${NAME_KEY}.loaded`,
|
|
43
|
+
KEYDOWN_DISMISS: `keydown.dismiss.${NAME_KEY}`,
|
|
44
|
+
HIDE_PREVENTED: `hidePrevented.${NAME_KEY}`,
|
|
45
|
+
CLICK_DATA_API: `click.${NAME_KEY}.data.api`,
|
|
46
|
+
POPSTATE_DATA_API: `popstate.${NAME_KEY}.data.api`,
|
|
47
|
+
DOM_LOADED_DATA_API: `DOMContentLoaded.${NAME_KEY}.data.api`,
|
|
48
|
+
};
|
|
31
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Класс VGSidebar реализует функциональность боковой панели (сайдбара) с поддержкой:
|
|
52
|
+
* - открытия/закрытия по клику или хэшу
|
|
53
|
+
* - поддержки backdrop
|
|
54
|
+
* - блокировки скролла при открытии
|
|
55
|
+
* - анимаций
|
|
56
|
+
* - AJAX-загрузки контента
|
|
57
|
+
*
|
|
58
|
+
* @extends BaseModule
|
|
59
|
+
*/
|
|
32
60
|
class VGSidebar extends BaseModule {
|
|
61
|
+
/**
|
|
62
|
+
* Создаёт экземпляр VGSidebar.
|
|
63
|
+
*
|
|
64
|
+
* @param {HTMLElement} element - Основной элемент сайдбара.
|
|
65
|
+
* @param {Object} params - Параметры конфигурации.
|
|
66
|
+
* @param {boolean} [params.backdrop=true] - Показывать подложку.
|
|
67
|
+
* @param {boolean} [params.overflow=true] - Блокировать скролл при открытии.
|
|
68
|
+
* @param {boolean} [params.keyboard=true] - Закрывать по клавише Escape.
|
|
69
|
+
* @param {boolean} [params.hash=false] - Поддержка открытия по хэшу URL.
|
|
70
|
+
* @param {Object} [params.animation] - Настройки анимации.
|
|
71
|
+
* @param {boolean} [params.animation.enable=false] - Включить анимацию.
|
|
72
|
+
* @param {string} [params.animation.in='animate__rollIn'] - Класс входной анимации.
|
|
73
|
+
* @param {string} [params.animation.out='animate__rollOut'] - Класс выходной анимации.
|
|
74
|
+
* @param {number} [params.animation.delay=800] - Задержка перед закрытием (мс).
|
|
75
|
+
* @param {Object} [params.ajax] - Параметры AJAX-загрузки.
|
|
76
|
+
* @param {string} [params.ajax.route=''] - URL для загрузки.
|
|
77
|
+
* @param {string} [params.ajax.target=''] - Селектор цели для вставки.
|
|
78
|
+
* @param {string} [params.ajax.method='get'] - HTTP-метод.
|
|
79
|
+
* @param {boolean} [params.ajax.loader=false] - Показывать лоадер.
|
|
80
|
+
* @param {boolean} [params.ajax.once=false] - Загружать только один раз.
|
|
81
|
+
* @param {boolean} [params.ajax.output=true] - Вставлять ответ в DOM.
|
|
82
|
+
*/
|
|
33
83
|
constructor(element, params = {}) {
|
|
34
84
|
super(element, params);
|
|
35
85
|
|
|
@@ -54,79 +104,105 @@ class VGSidebar extends BaseModule {
|
|
|
54
104
|
}
|
|
55
105
|
}, params));
|
|
56
106
|
|
|
107
|
+
this._scrollBar = new ScrollBarHelper();
|
|
108
|
+
this._params.animation.delay = this._params.animation.enable ? this._params.animation.delay : 0;
|
|
109
|
+
|
|
57
110
|
this._addEventListeners();
|
|
58
111
|
this._dismissElement();
|
|
59
|
-
|
|
60
|
-
this._scrollBar = new ScrollBarHelper();
|
|
61
|
-
this._params.animation.delay = !this._params.animation.enable ? 0 : this._params.animation.delay;
|
|
62
|
-
this._animation(this._element, VGSidebar.NAME_KEY, this._params.animation);
|
|
112
|
+
this._animation(this._element, NAME_KEY, this._params.animation);
|
|
63
113
|
}
|
|
64
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Статическое свойство: имя модуля.
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
65
119
|
static get NAME() {
|
|
66
120
|
return NAME;
|
|
67
121
|
}
|
|
68
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Статическое свойство: ключ для событий и данных.
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
69
127
|
static get NAME_KEY() {
|
|
70
|
-
return NAME_KEY
|
|
128
|
+
return NAME_KEY;
|
|
71
129
|
}
|
|
72
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Переключает состояние сайдбара (открыть/закрыть).
|
|
133
|
+
*
|
|
134
|
+
* @param {HTMLElement} [relatedTarget] - Элемент, инициировавший открытие.
|
|
135
|
+
* @returns {void}
|
|
136
|
+
*/
|
|
73
137
|
toggle(relatedTarget) {
|
|
74
|
-
return
|
|
138
|
+
return this._isShown() ? this.hide() : this.show(relatedTarget);
|
|
75
139
|
}
|
|
76
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Открывает сайдбар.
|
|
143
|
+
*
|
|
144
|
+
* @param {HTMLElement} [relatedTarget] - Элемент, инициировавший открытие.
|
|
145
|
+
* @returns {void}
|
|
146
|
+
*/
|
|
77
147
|
show(relatedTarget) {
|
|
78
|
-
|
|
79
|
-
if (isDisabled(_this._element)) return;
|
|
148
|
+
if (isDisabled(this._element)) return;
|
|
80
149
|
|
|
81
|
-
if (relatedTarget)
|
|
150
|
+
if (relatedTarget) {
|
|
151
|
+
this._params = this._getParams(relatedTarget, this._params);
|
|
152
|
+
}
|
|
82
153
|
|
|
83
|
-
|
|
84
|
-
|
|
154
|
+
// Событие загрузки (может использоваться для AJAX)
|
|
155
|
+
this._route((status, data) => {
|
|
156
|
+
EventHandler.trigger(this._element, EVENT_KEYS.LOADED, { stats: status, data });
|
|
85
157
|
});
|
|
86
158
|
|
|
87
|
-
const showEvent = EventHandler.trigger(
|
|
159
|
+
const showEvent = EventHandler.trigger(this._element, EVENT_KEYS.SHOW, { relatedTarget });
|
|
88
160
|
if (showEvent.defaultPrevented) return;
|
|
89
161
|
|
|
90
|
-
if (
|
|
162
|
+
if (this._params.backdrop) {
|
|
91
163
|
Backdrop.show();
|
|
92
164
|
}
|
|
93
165
|
|
|
94
|
-
if (
|
|
166
|
+
if (this._params.overflow) {
|
|
95
167
|
this._scrollBar.hide();
|
|
96
168
|
}
|
|
97
169
|
|
|
98
170
|
if (this._params.hash) {
|
|
99
|
-
window.history.pushState(null,
|
|
100
|
-
|
|
101
|
-
EventHandler.on(window, EVENT_KEY_POPSTATE_DATA_API, () => {
|
|
102
|
-
this.hide();
|
|
103
|
-
});
|
|
171
|
+
window.history.pushState(null, '', `#${this._element.id}`);
|
|
172
|
+
EventHandler.on(window, EVENT_KEYS.POPSTATE_DATA_API, () => this.hide());
|
|
104
173
|
}
|
|
105
174
|
|
|
106
|
-
|
|
175
|
+
this._element.classList.add(CLASS_NAME_SHOW);
|
|
107
176
|
document.body.classList.add(CLASS_NAME_OPEN);
|
|
108
177
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
178
|
+
const completeCallback = () => {
|
|
179
|
+
const backdrop = Selectors.find('.vg-backdrop');
|
|
180
|
+
if (backdrop) {
|
|
181
|
+
EventHandler.on(backdrop, 'mousedown.vg.backdrop', () => this.hide());
|
|
182
|
+
}
|
|
183
|
+
EventHandler.trigger(this._element, EVENT_KEYS.SHOWN, { relatedTarget });
|
|
184
|
+
};
|
|
113
185
|
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
_this._queueCallback(completeCallBack, _this._element, true, 50)
|
|
186
|
+
this._queueCallback(completeCallback, this._element, true, 50);
|
|
117
187
|
}
|
|
118
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Закрывает сайдбар.
|
|
191
|
+
*
|
|
192
|
+
* @param {boolean} [isLeaveBackDrop=false] - Не убирать подложку.
|
|
193
|
+
* @returns {void}
|
|
194
|
+
*/
|
|
119
195
|
hide(isLeaveBackDrop = false) {
|
|
120
196
|
if (isDisabled(this._element)) return;
|
|
121
197
|
|
|
122
|
-
const hideEvent = EventHandler.trigger(this._element,
|
|
198
|
+
const hideEvent = EventHandler.trigger(this._element, EVENT_KEYS.HIDE);
|
|
123
199
|
if (hideEvent.defaultPrevented) return;
|
|
124
200
|
|
|
125
201
|
document.body.classList.remove(CLASS_NAME_OPEN);
|
|
202
|
+
this._element.classList.remove(CLASS_NAME_SHOW);
|
|
126
203
|
|
|
127
204
|
setTimeout(() => {
|
|
128
|
-
this._element.setAttribute('aria-expanded', false);
|
|
129
|
-
this._element.classList.remove(CLASS_NAME_SHOW);
|
|
205
|
+
this._element.setAttribute('aria-expanded', 'false');
|
|
130
206
|
|
|
131
207
|
const completeCallback = () => {
|
|
132
208
|
if (!isLeaveBackDrop) {
|
|
@@ -136,88 +212,122 @@ class VGSidebar extends BaseModule {
|
|
|
136
212
|
this._scrollBar.reset();
|
|
137
213
|
}
|
|
138
214
|
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (this._params.overflow) {
|
|
215
|
+
} else if (this._params.overflow) {
|
|
142
216
|
this._scrollBar.reset();
|
|
143
217
|
}
|
|
144
218
|
|
|
145
219
|
if (this._params.hash) {
|
|
146
|
-
history.
|
|
220
|
+
history.replaceState('', document.title, window.location.pathname + window.location.search);
|
|
147
221
|
}
|
|
148
222
|
|
|
149
|
-
EventHandler.trigger(this._element,
|
|
223
|
+
EventHandler.trigger(this._element, EVENT_KEYS.HIDDEN);
|
|
150
224
|
}
|
|
151
|
-
}
|
|
225
|
+
};
|
|
226
|
+
|
|
152
227
|
this._queueCallback(completeCallback, this._element, true);
|
|
153
228
|
}, this._params.animation.delay);
|
|
154
229
|
}
|
|
155
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Очищает ресурсы модуля.
|
|
233
|
+
* @override
|
|
234
|
+
*/
|
|
156
235
|
dispose() {
|
|
157
236
|
super.dispose();
|
|
237
|
+
EventHandler.off(this._element, EVENT_KEYS.HIDE);
|
|
238
|
+
EventHandler.off(window, EVENT_KEYS.POPSTATE_DATA_API);
|
|
239
|
+
this._scrollBar.reset();
|
|
158
240
|
}
|
|
159
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Проверяет, открыт ли сайдбар.
|
|
244
|
+
* @returns {boolean}
|
|
245
|
+
* @private
|
|
246
|
+
*/
|
|
160
247
|
_isShown() {
|
|
161
248
|
return this._element.classList.contains(CLASS_NAME_SHOW);
|
|
162
249
|
}
|
|
163
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Добавляет глобальные слушатели событий (например, Escape).
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
164
255
|
_addEventListeners() {
|
|
165
|
-
EventHandler.on(document,
|
|
256
|
+
EventHandler.on(document, EVENT_KEYS.KEYDOWN_DISMISS, (event) => {
|
|
166
257
|
if (event.key !== 'Escape') return;
|
|
167
258
|
|
|
168
259
|
if (this._params.keyboard) {
|
|
169
260
|
this.hide();
|
|
170
|
-
|
|
261
|
+
} else {
|
|
262
|
+
EventHandler.trigger(this._element, EVENT_KEYS.HIDE_PREVENTED);
|
|
171
263
|
}
|
|
172
|
-
|
|
173
|
-
EventHandler.trigger(this._element, EVENT_KEY_HIDE_PREVENTED)
|
|
174
264
|
});
|
|
175
265
|
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Инициализирует поведение закрытия по клику вне (через `dismissTrigger`).
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
_dismissElement() {
|
|
272
|
+
dismissTrigger(this);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Заглушка для возможной AJAX-логики. Может быть переопределена.
|
|
277
|
+
* @param {Function} callback - Колбэк после загрузки.
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
280
|
+
_route(callback) {
|
|
281
|
+
// Здесь может быть реализация AJAX-загрузки
|
|
282
|
+
callback(true, null);
|
|
283
|
+
}
|
|
176
284
|
}
|
|
177
285
|
|
|
286
|
+
// Автоматическая инициализация по data-атрибутам
|
|
178
287
|
dismissTrigger(VGSidebar);
|
|
179
288
|
|
|
180
289
|
/**
|
|
181
|
-
* Data API
|
|
290
|
+
* Реализация Data API: открытие сайдбара по data-атрибуту.
|
|
182
291
|
*/
|
|
183
|
-
EventHandler.on(document,
|
|
292
|
+
EventHandler.on(document, EVENT_KEYS.CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
|
184
293
|
const target = Selectors.getElementFromSelector(this);
|
|
294
|
+
if (!target) return;
|
|
185
295
|
|
|
186
296
|
if (['A', 'AREA'].includes(this.tagName)) {
|
|
187
|
-
event.preventDefault()
|
|
297
|
+
event.preventDefault();
|
|
188
298
|
}
|
|
189
299
|
|
|
190
|
-
if (isDisabled(this))
|
|
191
|
-
|
|
192
|
-
|
|
300
|
+
if (isDisabled(this)) return;
|
|
301
|
+
|
|
302
|
+
this.setAttribute('aria-expanded', 'true');
|
|
193
303
|
|
|
194
|
-
|
|
195
|
-
EventHandler.one(target,
|
|
196
|
-
this.setAttribute('aria-expanded', false);
|
|
197
|
-
})
|
|
304
|
+
// Сбрасываем атрибут после закрытия
|
|
305
|
+
EventHandler.one(target, EVENT_KEYS.HIDDEN, () => {
|
|
306
|
+
this.setAttribute('aria-expanded', 'false');
|
|
307
|
+
});
|
|
198
308
|
|
|
199
|
-
|
|
309
|
+
// Закрываем уже открытый сайдбар
|
|
310
|
+
const alreadyOpen = Selectors.find('.vg-sidebar.show');
|
|
200
311
|
if (alreadyOpen && alreadyOpen !== target) {
|
|
201
|
-
VGSidebar.getInstance(alreadyOpen).hide()
|
|
312
|
+
VGSidebar.getInstance(alreadyOpen).hide();
|
|
202
313
|
}
|
|
203
314
|
|
|
204
|
-
const
|
|
205
|
-
|
|
315
|
+
const instance = VGSidebar.getOrCreateInstance(target);
|
|
316
|
+
instance.toggle(this);
|
|
206
317
|
});
|
|
207
318
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
319
|
+
/**
|
|
320
|
+
* Открытие сайдбара по хэшу при загрузке страницы.
|
|
321
|
+
*/
|
|
322
|
+
EventHandler.on(document, EVENT_KEYS.DOM_LOADED_DATA_API, function () {
|
|
323
|
+
const hash = window.location.hash.slice(1);
|
|
324
|
+
if (!hash) return;
|
|
325
|
+
|
|
326
|
+
const target = Selectors.find(`#${hash}`);
|
|
327
|
+
if (target && target.classList.contains('vg-sidebar') && !isDisabled(target)) {
|
|
328
|
+
const instance = VGSidebar.getOrCreateInstance(target);
|
|
329
|
+
instance.toggle();
|
|
220
330
|
}
|
|
221
|
-
})
|
|
331
|
+
});
|
|
222
332
|
|
|
223
|
-
export default VGSidebar;
|
|
333
|
+
export default VGSidebar;
|