vgapp 0.7.7 → 0.7.9
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 +20 -4
- package/LICENSE +22 -0
- package/app/modules/base-module.js +62 -17
- package/app/modules/module-fn.js +10 -100
- package/app/modules/vgalert/js/vgalert.js +356 -214
- package/app/modules/vgalert/readme.md +242 -0
- package/app/modules/vgcollapse/js/vgcollapse.js +216 -62
- package/app/modules/vgcollapse/readme.md +56 -0
- package/app/modules/vgcollapse/scss/_variables.scss +5 -0
- package/app/modules/vgcollapse/scss/vgcollapse.scss +41 -0
- package/app/modules/vgdropdown/js/vgdropdown.js +104 -118
- package/app/modules/vgdropdown/scss/vgdropdown.scss +1 -2
- package/app/modules/vgformsender/js/hideshowpass.js +7 -4
- package/app/modules/vgformsender/js/vgformsender.js +343 -160
- package/app/modules/vgformsender/readme.md +250 -0
- package/app/modules/vgformsender/scss/vgformsender.scss +11 -3
- package/app/modules/vgnav/js/vgnav.js +98 -26
- package/app/modules/vgnav/scss/_placement.scss +8 -93
- package/app/modules/vgnav/scss/vgnav.scss +0 -1
- package/app/utils/js/components/ajax.js +237 -0
- package/app/utils/js/components/lang.js +108 -0
- package/app/utils/js/components/params.js +5 -0
- package/app/utils/js/components/placement.js +111 -108
- package/app/utils/js/components/templater.js +365 -33
- package/app/utils/js/functions.js +275 -143
- package/app/utils/scss/default.scss +1 -0
- package/app/utils/scss/placement.scss +72 -0
- package/app/utils/scss/variables.scss +10 -5
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/index.scss +3 -0
- package/package.json +1 -1
- package/app/utils/js/components/alert.js +0 -8
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# VGFormSender — Модуль отправки форм
|
|
2
|
+
|
|
3
|
+
Модуль `VGFormSender` позволяет легко и гибко управлять отправкой форм на сайте. Он поддерживает как нативную, так и **AJAX-отправку**, валидацию, отображение уведомлений (через **VGModal** или **VGCollapse**), работу с паролями, спиннеры на кнопках и многое другое.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✅ Основные возможности
|
|
8
|
+
|
|
9
|
+
- 📤 **Отправка форм через AJAX или нативно**
|
|
10
|
+
- ✅ **HTML5-валидация** с подсветкой ошибок
|
|
11
|
+
- 🔔 **Уведомления** — в виде модального окна или collapse-блока
|
|
12
|
+
- 🔐 **Показ/скрытие пароля** (с иконкой "глаз")
|
|
13
|
+
- 🔁 **Спиннеры на кнопке отправки**
|
|
14
|
+
- 🔄 **Редиректы** после успешной или неудачной отправки
|
|
15
|
+
- 🧩 **Интерцепторы** (`beforeSend`, `success`, `error`) — полный контроль
|
|
16
|
+
- 🌐 **Мультиязычность** (поддержка `ru`, легко расширяемо)
|
|
17
|
+
- 📢 **События** (`before`, `success`, `error`) для внешнего контроля
|
|
18
|
+
- ⚙️ **Кастомизация** через параметры и `data-*` атрибуты
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🧱 Подключение
|
|
23
|
+
|
|
24
|
+
### Через JavaScript
|
|
25
|
+
```js
|
|
26
|
+
import VGFormSender from './app/modules/vgformsender/js/vgformsender.js';
|
|
27
|
+
```
|
|
28
|
+
Или подключите как часть сборки.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
## 🛠️ Инициализация
|
|
32
|
+
|
|
33
|
+
### 1. Через JavaScript
|
|
34
|
+
```js
|
|
35
|
+
VGFormSender.init(document.getElementById('contactForm'), {
|
|
36
|
+
validate: true, // включить валидацию
|
|
37
|
+
ajax: {
|
|
38
|
+
route: '/api/send', // URL для отправки
|
|
39
|
+
method: 'post'
|
|
40
|
+
},
|
|
41
|
+
alert: {
|
|
42
|
+
type: 'modal', // 'modal' или 'collapse'
|
|
43
|
+
enabled: true,
|
|
44
|
+
delay: 5000 // ожидание открытия, через 5 секунд
|
|
45
|
+
},
|
|
46
|
+
callback: {
|
|
47
|
+
afterSuccess: (form, instance, event, data) => {
|
|
48
|
+
console.log('Форма отправлена!', data);
|
|
49
|
+
},
|
|
50
|
+
afterError: (form, instance, event, data) => {
|
|
51
|
+
console.error('Ошибка:', data);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Через HTML (`data-*` атрибуты)
|
|
58
|
+
```html
|
|
59
|
+
<form action="/api/send" method="post" id="contactForm" data-vgformsender
|
|
60
|
+
data-validate="true"
|
|
61
|
+
data-alert-type="modal"
|
|
62
|
+
>
|
|
63
|
+
<!-- ... -->
|
|
64
|
+
<button type="submit" data-button-send="Отправляем..." data-button-spinner-enabled="true">Отправить</button>
|
|
65
|
+
</form>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## ⚙️ Параметры модуля
|
|
71
|
+
|
|
72
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
73
|
+
|---------------------------|-------------------------|------------------------------------|----------------------------------------------------------------------|
|
|
74
|
+
| `validate` | `boolean` | `false` | Включить HTML5-валидацию |
|
|
75
|
+
| `response.enabled` | `boolean` | `false` | Нативная обработка ответа (без AJAX) |
|
|
76
|
+
| `submit` | `boolean` | `false` | Отправлять нативно (без AJAX) |
|
|
77
|
+
| `fields` | `Array` | `[]` | Доп. данные для отправки |
|
|
78
|
+
| `pass.enabled` | `boolean` | `true` | Показывать иконку глаза у паролей |
|
|
79
|
+
| `alert.enabled` | `boolean` | `true` | Показывать уведомления |
|
|
80
|
+
| `alert.type` | `'modal' \| 'collapse'` | `'modal'` | Тип уведомления |
|
|
81
|
+
| `alert.errors` | `boolean` | `true` | Показывать детали ошибок |
|
|
82
|
+
| `alert.delay` | `number` | `0` | Задержка закрытия (мс), `0` — не закрывать |
|
|
83
|
+
| `ajax.route` | `string` | `''` | URL отправки (переопределяет `action`) |
|
|
84
|
+
| `ajax.method` | `string` | `'get'` | HTTP-метод (`post`, `put`, и т.д.) |
|
|
85
|
+
| `ajax.target` | `string` | `''` | Указывается ID целевого блока, для AJAX-ответа |
|
|
86
|
+
| `ajax.output` | `boolean` | `false` | Разрешает или запрещает добавление контента с сервера в целевой блок |
|
|
87
|
+
| `button.spinner.enabled` | `boolean` | `false` | Показывать спиннер на кнопке |
|
|
88
|
+
| `button.spinner.element` | `string` | `<span class="spinner-border...">` | HTML спиннера |
|
|
89
|
+
| `button.send` | `string` | `'Отправляем...'` | Текст кнопки при отправке |
|
|
90
|
+
| `button.initial` | `string` | `'Отправить'` | Исходный текст кнопки |
|
|
91
|
+
| `redirect.success` | `string` | `''` | Редирект после успеха |
|
|
92
|
+
| `redirect.error` | `string` | `''` | Редирект после ошибки |
|
|
93
|
+
| `lang` | `string` | `'ru'` | Язык сообщений |
|
|
94
|
+
| `interceptors.beforeSend` | `Function` | `Promise.resolve()` | Выполняется перед отправкой |
|
|
95
|
+
| `interceptors.success` | `Function\|false` | `false` | Кастомная обработка успеха |
|
|
96
|
+
| `interceptors.error` | `Function\|false` | `false` | Кастомная обработка ошибки |
|
|
97
|
+
| `callback.afterInit` | `Function` | `noop` | После инициализации |
|
|
98
|
+
| `callback.afterSuccess` | `Function` | `noop` | После успеха |
|
|
99
|
+
| `callback.afterError` | `Function` | `noop` | После ошибки |
|
|
100
|
+
| `callback.afterSend` | `Function` | `noop` | После любого ответа |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 🔔 События
|
|
105
|
+
|
|
106
|
+
Модуль генерирует события на DOM-элементе формы.
|
|
107
|
+
|
|
108
|
+
### Доступные события
|
|
109
|
+
|
|
110
|
+
| Событие | Данные | Описание |
|
|
111
|
+
|--------|--------|---------|
|
|
112
|
+
| `vg.fs.before` | `{ instance }` | Перед отправкой |
|
|
113
|
+
| `vg.fs.success` | `{ event, self, data }` | Успешная отправка |
|
|
114
|
+
| `vg.fs.error` | `{ event, self, data }` | Ошибка при отправке |
|
|
115
|
+
|
|
116
|
+
### Пример прослушивания
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
document.getElementById('contactForm').addEventListener('vg.fs.success', (e) => {
|
|
120
|
+
const { data } = e.vgformsender;
|
|
121
|
+
console.log('Ответ сервера:', data);
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 💬 Языки
|
|
127
|
+
|
|
128
|
+
Поддерживается русский (`ru`) по умолчанию. Вы можете расширить поддержку:
|
|
129
|
+
```js
|
|
130
|
+
// В lang.js lang_messages('en', 'errors').went_wrong = 'Something went wrong'; lang_titles('en', 'errors').title = 'Error';
|
|
131
|
+
```
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🖼️ Типы уведомлений
|
|
135
|
+
|
|
136
|
+
### 1. Модальное окно (`modal`)
|
|
137
|
+
```js
|
|
138
|
+
alert: {
|
|
139
|
+
type: 'modal',
|
|
140
|
+
enabled: true
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
Автоматически закрывает другие модальные окна (включая Bootstrap и VGModal).
|
|
144
|
+
|
|
145
|
+
### 2. Collapse-блок
|
|
146
|
+
```js
|
|
147
|
+
alert: {
|
|
148
|
+
type: 'collapse',
|
|
149
|
+
enabled: true
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
Уведомление появляется в начале формы.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🔄 Интерцепторы
|
|
157
|
+
|
|
158
|
+
Полный контроль над процессом:
|
|
159
|
+
```js
|
|
160
|
+
interceptors: {
|
|
161
|
+
beforeSend: () => {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
if (confirm('Отправить форму?')) {
|
|
164
|
+
resolve();
|
|
165
|
+
} else {
|
|
166
|
+
reject();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
success: (form, instance, data) => { // кастомная логика, отключает стандартное поведение
|
|
171
|
+
console.log('Кастомный успех');
|
|
172
|
+
return false; // важно: вернуть false, чтобы отключить стандартный alert
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🖱️ Статические методы
|
|
179
|
+
|
|
180
|
+
### `VGFormSender.init(element, params)`
|
|
181
|
+
Инициализирует форму.
|
|
182
|
+
|
|
183
|
+
### `VGFormSender.buttonClick(formID, callback, status)`
|
|
184
|
+
Подписывается на нажатие кнопки.
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
VGFormSender.buttonClick('#contactForm', (form, instance) => {
|
|
188
|
+
console.log('Кнопка нажата до отправки');
|
|
189
|
+
}, 'before');
|
|
190
|
+
```
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 🔄 Ответ сервера
|
|
194
|
+
Ответ сервера обязательно должен быть в формате JSON.
|
|
195
|
+
```php
|
|
196
|
+
json_encode([
|
|
197
|
+
'errors' => false, // флаг ошибки,
|
|
198
|
+
'title' => 'Успех!', // заголовок уведомления
|
|
199
|
+
'message' => 'Сообщение отправлено' // текст уведомления
|
|
200
|
+
])
|
|
201
|
+
```
|
|
202
|
+
или
|
|
203
|
+
|
|
204
|
+
```php
|
|
205
|
+
json_encode([
|
|
206
|
+
'errors' => false, // флаг ошибки,
|
|
207
|
+
'view' => '<...>' // HTML-контент
|
|
208
|
+
])
|
|
209
|
+
```
|
|
210
|
+
Если есть несколько ошибок логики работы сервера, передаем так:
|
|
211
|
+
|
|
212
|
+
```php
|
|
213
|
+
json_encode([
|
|
214
|
+
'errors' => [ // массив ошибок
|
|
215
|
+
['Не заполнено поля имя', 'Не заполнено поля email']
|
|
216
|
+
],
|
|
217
|
+
])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 🎨 CSS-классы
|
|
223
|
+
|
|
224
|
+
| Класс | Назначение |
|
|
225
|
+
|------|-----------|
|
|
226
|
+
| `.vg-form-sender` | Базовый класс формы |
|
|
227
|
+
| `.vg-form-sender--content` | Обёртка полей |
|
|
228
|
+
| `.vg-form-sender-alert` | Блок уведомления |
|
|
229
|
+
| `.vg-form-sender-modal` | Модальное уведомление |
|
|
230
|
+
| `.vg-form-sender-collapse` | Collapse-уведомление |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 📦 Зависимости
|
|
235
|
+
- `BaseModule` — базовый класс
|
|
236
|
+
- `VGModal`, `VGCollapse` — компоненты интерфейса
|
|
237
|
+
- `VGHideShowPass` — показ/скрытие пароля
|
|
238
|
+
- `utils/js/dom/*` — утилиты DOM
|
|
239
|
+
- `utils/js/functions` — вспомогательные функции
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 📄 Лицензия
|
|
244
|
+
|
|
245
|
+
MIT — свободно используйте и модифицируйте.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
> 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
|
|
250
|
+
> 📍 Поддерживается в проектах на VEGAS
|
|
@@ -49,13 +49,20 @@
|
|
|
49
49
|
|
|
50
50
|
.vg-form-sender-alert {
|
|
51
51
|
@include mix-alert-color-mode($class: vg-form-sender-alert);
|
|
52
|
-
|
|
53
52
|
background-color: var(--vg-form-sender-alert-background-color);
|
|
54
53
|
border: 1px solid var(--vg-form-sender-alert-border-color) ;
|
|
55
54
|
border-radius: var(--vg-border-radius);
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
.vg-btn-close {
|
|
57
|
+
svg {
|
|
58
|
+
path {
|
|
59
|
+
fill: var(--vg-form-sender-alert-close)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.vg-form-sender-alert-modal {
|
|
65
|
+
min-height: 260px;
|
|
59
66
|
display: flex;
|
|
60
67
|
align-items: center;
|
|
61
68
|
justify-content: center;
|
|
@@ -65,6 +72,7 @@
|
|
|
65
72
|
flex-direction: column;
|
|
66
73
|
align-items: center;
|
|
67
74
|
gap: 1.5rem;
|
|
75
|
+
padding: 1rem;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
.vg-form-sender-alert-content--text {
|
|
@@ -23,7 +23,7 @@ const CLASS_NAME_ACTIVE = 'active';
|
|
|
23
23
|
/**
|
|
24
24
|
* Constants toggle
|
|
25
25
|
*/
|
|
26
|
-
const SELECTOR_DATA_TOGGLE = '.'+ CLASS_NAME +' a';
|
|
26
|
+
const SELECTOR_DATA_TOGGLE = '.' + CLASS_NAME + ' a';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Constants Events
|
|
@@ -55,7 +55,8 @@ class VGNav extends BaseModule {
|
|
|
55
55
|
enable: true,
|
|
56
56
|
always: false,
|
|
57
57
|
title: '',
|
|
58
|
-
body: null
|
|
58
|
+
body: null,
|
|
59
|
+
target: '#sidebar-nav'
|
|
59
60
|
},
|
|
60
61
|
callbacks: {
|
|
61
62
|
afterInit: noop,
|
|
@@ -87,8 +88,12 @@ class VGNav extends BaseModule {
|
|
|
87
88
|
this.navigation = '.' + this._classes.wrapper;
|
|
88
89
|
|
|
89
90
|
if (this._params.animation.enable === false) {
|
|
90
|
-
this._params.animation.timeout = 10
|
|
91
|
+
this._params.animation.timeout = 10;
|
|
91
92
|
}
|
|
93
|
+
|
|
94
|
+
this._openDrops = new Map();
|
|
95
|
+
this._handleScroll = this._handleScroll.bind(this);
|
|
96
|
+
this._handleResize = this._handleResize.bind(this);
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
static get NAME() {
|
|
@@ -138,7 +143,7 @@ class VGNav extends BaseModule {
|
|
|
138
143
|
hamburger = '<span class="' + classes.hamburger + '--lines"><span></span><span></span><span></span></span>';
|
|
139
144
|
|
|
140
145
|
if (params.hamburger.title) {
|
|
141
|
-
mobileNavTitle = '<span class="' + classes.hamburger + '--title">'+ params.hamburger.title +'</span>';
|
|
146
|
+
mobileNavTitle = '<span class="' + classes.hamburger + '--title">' + params.hamburger.title + '</span>';
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
if (params.hamburger.body !== null) {
|
|
@@ -163,8 +168,8 @@ class VGNav extends BaseModule {
|
|
|
163
168
|
if ($dropdown_a.length) {
|
|
164
169
|
$dropdown_a.forEach(function (elem) {
|
|
165
170
|
if (!elem.querySelector('.toggle') && !elem.closest('.dots')) {
|
|
166
|
-
elem.setAttribute('aria-expanded', 'false')
|
|
167
|
-
elem.insertAdjacentHTML('beforeend', toggle)
|
|
171
|
+
elem.setAttribute('aria-expanded', 'false');
|
|
172
|
+
elem.insertAdjacentHTML('beforeend', toggle);
|
|
168
173
|
}
|
|
169
174
|
});
|
|
170
175
|
}
|
|
@@ -195,15 +200,32 @@ class VGNav extends BaseModule {
|
|
|
195
200
|
target.classList.add(CLASS_NAME_ACTIVE);
|
|
196
201
|
|
|
197
202
|
const $placement = new Placement({
|
|
198
|
-
|
|
199
|
-
|
|
203
|
+
reference: target,
|
|
204
|
+
drop: drop,
|
|
205
|
+
placement: 'bottom-start',
|
|
206
|
+
fallbackPlacements: ['top-start', 'bottom-end', 'top-end'],
|
|
207
|
+
offset: [0, 6],
|
|
208
|
+
boundary: 'clippingParents',
|
|
209
|
+
autoFlip: true,
|
|
210
|
+
overflowProtection: true
|
|
211
|
+
});
|
|
200
212
|
|
|
201
213
|
$placement._setPlacement();
|
|
202
214
|
|
|
215
|
+
this._openDrops.set(drop, {
|
|
216
|
+
reference: target,
|
|
217
|
+
placement: $placement,
|
|
218
|
+
scrollHandler: this._handleScroll,
|
|
219
|
+
resizeHandler: this._handleResize
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
window.addEventListener('scroll', this._handleScroll, { passive: true, capture: true });
|
|
223
|
+
window.addEventListener('resize', this._handleResize);
|
|
224
|
+
|
|
203
225
|
const completeCallBack = () => {
|
|
204
226
|
drop.classList.add(CLASS_NAME_FADE);
|
|
205
|
-
EventHandler.trigger(target, EVENT_KEY_SHOWN, relatedTarget)
|
|
206
|
-
}
|
|
227
|
+
EventHandler.trigger(target, EVENT_KEY_SHOWN, relatedTarget);
|
|
228
|
+
};
|
|
207
229
|
this._queueCallback(completeCallBack, drop, true, 10);
|
|
208
230
|
}
|
|
209
231
|
|
|
@@ -218,7 +240,7 @@ class VGNav extends BaseModule {
|
|
|
218
240
|
let element = relatedTarget.relatedTarget;
|
|
219
241
|
|
|
220
242
|
if ('elm' in relatedTarget && relatedTarget.elm) {
|
|
221
|
-
element = relatedTarget.elm
|
|
243
|
+
element = relatedTarget.elm;
|
|
222
244
|
}
|
|
223
245
|
|
|
224
246
|
if (element) {
|
|
@@ -245,15 +267,65 @@ class VGNav extends BaseModule {
|
|
|
245
267
|
if (index === 0) {
|
|
246
268
|
const completeCallback = () => {
|
|
247
269
|
el.classList.remove(CLASS_NAME_SHOW);
|
|
248
|
-
EventHandler.trigger(el, EVENT_KEY_HIDDEN, relatedTarget)
|
|
249
|
-
}
|
|
270
|
+
EventHandler.trigger(el, EVENT_KEY_HIDDEN, relatedTarget);
|
|
271
|
+
};
|
|
250
272
|
|
|
251
273
|
_this._queueCallback(completeCallback, el, true, 500);
|
|
252
274
|
}
|
|
275
|
+
|
|
276
|
+
const dropData = _this._openDrops.get(el);
|
|
277
|
+
if (dropData) {
|
|
278
|
+
window.removeEventListener('scroll', dropData.scrollHandler, { capture: true });
|
|
279
|
+
window.removeEventListener('resize', dropData.resizeHandler);
|
|
280
|
+
_this._openDrops.delete(el);
|
|
281
|
+
}
|
|
253
282
|
});
|
|
254
283
|
}
|
|
255
284
|
}
|
|
256
285
|
|
|
286
|
+
_handleScroll() {
|
|
287
|
+
for (const [drop, data] of this._openDrops.entries()) {
|
|
288
|
+
if (drop.offsetParent === null) {
|
|
289
|
+
this._cleanupDrop(drop);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
data.placement._setPlacement();
|
|
294
|
+
|
|
295
|
+
if (!this._isElementInViewport(drop)) {
|
|
296
|
+
const target = data.reference;
|
|
297
|
+
this.hide({ relatedTarget: target });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
_handleResize() {
|
|
303
|
+
for (const [drop, data] of this._openDrops.entries()) {
|
|
304
|
+
if (drop.offsetParent === null) continue;
|
|
305
|
+
data.placement._setPlacement();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_cleanupDrop(drop) {
|
|
310
|
+
const dropData = this._openDrops.get(drop);
|
|
311
|
+
if (dropData) {
|
|
312
|
+
window.removeEventListener('scroll', dropData.scrollHandler, { capture: true });
|
|
313
|
+
window.removeEventListener('resize', dropData.resizeHandler);
|
|
314
|
+
this._openDrops.delete(drop);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
_isElementInViewport(el) {
|
|
319
|
+
const rect = el.getBoundingClientRect();
|
|
320
|
+
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
321
|
+
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
322
|
+
|
|
323
|
+
const vertInView = (rect.top <= viewHeight) && ((rect.top + rect.height) >= 0);
|
|
324
|
+
const horInView = (rect.left <= viewWidth) && ((rect.left + rect.width) >= 0);
|
|
325
|
+
|
|
326
|
+
return vertInView && horInView;
|
|
327
|
+
}
|
|
328
|
+
|
|
257
329
|
static init(element, params = {}) {
|
|
258
330
|
const instance = VGNav.getOrCreateInstance(element, params);
|
|
259
331
|
instance.build();
|
|
@@ -276,7 +348,7 @@ class VGNav extends BaseModule {
|
|
|
276
348
|
|
|
277
349
|
let relatedTarget = {
|
|
278
350
|
relatedTarget: target
|
|
279
|
-
}
|
|
351
|
+
};
|
|
280
352
|
|
|
281
353
|
instance.show(relatedTarget);
|
|
282
354
|
});
|
|
@@ -293,9 +365,9 @@ class VGNav extends BaseModule {
|
|
|
293
365
|
}
|
|
294
366
|
|
|
295
367
|
currentElem = null;
|
|
296
|
-
instance.hide({relatedTarget: relatedTarget, elm: elm});
|
|
297
|
-
})
|
|
298
|
-
})
|
|
368
|
+
instance.hide({ relatedTarget: relatedTarget, elm: elm });
|
|
369
|
+
});
|
|
370
|
+
});
|
|
299
371
|
}
|
|
300
372
|
|
|
301
373
|
const vgNavSidebar = document.getElementById('sidebar-nav');
|
|
@@ -314,16 +386,16 @@ class VGNav extends BaseModule {
|
|
|
314
386
|
|
|
315
387
|
static clearDrops(event) {
|
|
316
388
|
if (event.button === 2 || (event.type === 'keyup' && event.key !== 'Tab')) {
|
|
317
|
-
return
|
|
389
|
+
return;
|
|
318
390
|
}
|
|
319
391
|
|
|
320
|
-
VGNav.hideOpenDrops(event)
|
|
392
|
+
VGNav.hideOpenDrops(event);
|
|
321
393
|
}
|
|
322
394
|
|
|
323
395
|
static hideOpenDrops(event) {
|
|
324
|
-
[...
|
|
396
|
+
[...Selectors.findAll('.dropdown:not(.disabled):not(:disabled).active')].forEach((el) => {
|
|
325
397
|
let target = event.target,
|
|
326
|
-
drop
|
|
398
|
+
drop = target.closest('.dropdown');
|
|
327
399
|
|
|
328
400
|
if (el !== drop) {
|
|
329
401
|
const nav = el.closest('.vg-nav');
|
|
@@ -337,9 +409,9 @@ class VGNav extends BaseModule {
|
|
|
337
409
|
return;
|
|
338
410
|
}
|
|
339
411
|
|
|
340
|
-
const relatedTarget = { relatedTarget: el }
|
|
412
|
+
const relatedTarget = { relatedTarget: el };
|
|
341
413
|
|
|
342
|
-
context.hide(relatedTarget)
|
|
414
|
+
context.hide(relatedTarget);
|
|
343
415
|
}
|
|
344
416
|
});
|
|
345
417
|
}
|
|
@@ -375,16 +447,16 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
|
|
|
375
447
|
|
|
376
448
|
if (dropContent && isFirst) {
|
|
377
449
|
if (drop.classList.contains('active')) {
|
|
378
|
-
instance.hide({relatedTarget: drop});
|
|
450
|
+
instance.hide({ relatedTarget: drop });
|
|
379
451
|
return;
|
|
380
452
|
}
|
|
381
453
|
} else {
|
|
382
454
|
[...Selectors.findAll('.active', nav)].forEach(function (el) {
|
|
383
|
-
instance.hide({relatedTarget: el})
|
|
455
|
+
instance.hide({ relatedTarget: el });
|
|
384
456
|
});
|
|
385
457
|
}
|
|
386
458
|
|
|
387
|
-
instance.show({relatedTarget: drop});
|
|
459
|
+
instance.show({ relatedTarget: drop });
|
|
388
460
|
});
|
|
389
461
|
|
|
390
462
|
export default VGNav;
|
|
@@ -1,44 +1,6 @@
|
|
|
1
1
|
&.vg-nav-horizontal {
|
|
2
2
|
.vg-nav-wrapper {
|
|
3
3
|
flex-direction: row;
|
|
4
|
-
|
|
5
|
-
.dropdown {
|
|
6
|
-
> .dropdown-content {
|
|
7
|
-
&.left, .left {
|
|
8
|
-
left: 0;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
&.right, .right {
|
|
12
|
-
right: 0;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
&.top, .top {
|
|
16
|
-
top: 0;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
&.bottom, .bottom {
|
|
20
|
-
bottom: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.dropdown {
|
|
24
|
-
ul.left {
|
|
25
|
-
left: 100%;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
ul.right {
|
|
29
|
-
right: 100%;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
ul.top {
|
|
33
|
-
top: 100%;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
ul.bottom {
|
|
37
|
-
bottom: 100%;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
4
|
}
|
|
43
5
|
}
|
|
44
6
|
|
|
@@ -46,64 +8,17 @@
|
|
|
46
8
|
.vg-nav-wrapper {
|
|
47
9
|
flex-direction: column;
|
|
48
10
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
&.left, .left {
|
|
52
|
-
left: 100%;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
&.right, .right {
|
|
56
|
-
right: 100%;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
&.top, .top {
|
|
60
|
-
top: 100%;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
&.bottom, .bottom {
|
|
64
|
-
bottom: 100%;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
&.fade {
|
|
68
|
-
&.top {
|
|
69
|
-
top: 0;
|
|
70
|
-
}
|
|
71
|
-
&.bottom {
|
|
72
|
-
bottom: 0;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
&.show {
|
|
78
|
-
&.top {
|
|
79
|
-
> ul {
|
|
80
|
-
top: 0;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
&.bottom {
|
|
84
|
-
> ul {
|
|
85
|
-
bottom: 0;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
11
|
+
[data-vg-placement=top-start], [data-vg-placement=bottom-start] {
|
|
12
|
+
left: 100%;
|
|
89
13
|
}
|
|
90
14
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.dropdown-mega-container {
|
|
95
|
-
left: 100%;
|
|
96
|
-
top: 100%;
|
|
97
|
-
|
|
98
|
-
&.fade {
|
|
99
|
-
&.top {
|
|
100
|
-
top: 0;
|
|
101
|
-
}
|
|
15
|
+
[data-vg-placement^=bottom] {
|
|
16
|
+
top: 0;
|
|
17
|
+
}
|
|
102
18
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
19
|
+
[data-vg-placement^=bottom] {
|
|
20
|
+
[data-vg-placement=top-start] {
|
|
21
|
+
bottom: 0;
|
|
107
22
|
}
|
|
108
23
|
}
|
|
109
24
|
}
|