vgapp 0.5.9 → 0.6.0
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 +3 -0
- package/app/modules/base-module.js +10 -0
- package/app/modules/vgdropdown/js/vgdropdown.js +11 -10
- package/app/modules/vgnav/!old/!vgnav.js +510 -0
- package/app/modules/vgnav/!old/scss/_breakpoints.scss +127 -0
- package/app/modules/vgnav/!old/scss/_hamburger.scss +62 -0
- package/app/modules/vgnav/!old/scss/_placement.scss +70 -0
- package/app/modules/vgnav/!old/scss/_toggle.scss +20 -0
- package/app/modules/vgnav/!old/scss/_variables.scss +70 -0
- package/app/modules/vgnav/!old/scss/vgnav.scss +164 -0
- package/app/modules/vgnav/js/vgnav.js +136 -254
- package/app/modules/vgnav/scss/_placement.scss +1 -1
- package/app/modules/vgnav/scss/_variables.scss +18 -18
- package/app/modules/vgnav/scss/vgnav.scss +13 -10
- package/app/modules/vgselect/js/vgselect.js +9 -1
- package/app/utils/js/components/placement.js +58 -1
- package/build/vgapp.css +1507 -2
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +30 -2
- package/package.json +1 -1
- package/app/utils/js/components/responsive.js +0 -83
package/CHANGELOG.md
CHANGED
|
@@ -96,6 +96,16 @@ class BaseModule {
|
|
|
96
96
|
new Animation(element, key, params);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
isMobileDevice() {
|
|
100
|
+
const userAgent = navigator.userAgent;
|
|
101
|
+
const isMobileUA = /Android|iPhone|iPad|iPod/i.test(userAgent);
|
|
102
|
+
const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
103
|
+
const isSmallScreen = window.innerWidth < 768;
|
|
104
|
+
const isHighDPI = window.devicePixelRatio >= 2;
|
|
105
|
+
|
|
106
|
+
return isMobileUA || (isTouchDevice && isSmallScreen && isHighDPI);
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
static getInstance(element) {
|
|
100
110
|
return Data.get(Selectors.find(element), this.NAME_KEY)
|
|
101
111
|
}
|
|
@@ -209,8 +209,9 @@ class VGDropdown extends BaseModule {
|
|
|
209
209
|
static init(element, params = {}) {
|
|
210
210
|
const instance = VGDropdown.getOrCreateInstance(element, params);
|
|
211
211
|
|
|
212
|
-
if (instance._params.hover) {
|
|
212
|
+
if (instance._params.hover && !instance.isMobileDevice()) {
|
|
213
213
|
let currentElem = null;
|
|
214
|
+
|
|
214
215
|
EventHandler.on(instance._parent, EVENT_MOUSEOVER_DATA_API, function (event) {
|
|
215
216
|
if (currentElem) return;
|
|
216
217
|
VGDropdown.hideOpenToggles(event);
|
|
@@ -236,16 +237,16 @@ class VGDropdown extends BaseModule {
|
|
|
236
237
|
currentElem = null;
|
|
237
238
|
instance._completeHide({relatedTarget: instance._element});
|
|
238
239
|
})
|
|
239
|
-
} else {
|
|
240
|
-
EventHandler.on(document, EVENT_KEYUP_DATA_API, SELECTOR_DATA_TOGGLE, VGDropdown.keydownHandler);
|
|
241
|
-
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, '.' + TARGET_CONTAINER, VGDropdown.keydownHandler);
|
|
242
|
-
EventHandler.on(document, EVENT_KEYUP_DATA_API, VGDropdown.clearDrops);
|
|
243
|
-
EventHandler.on(document, EVENT_CLICK_DATA_API, VGDropdown.clearDrops);
|
|
244
|
-
EventHandler.on(element, EVENT_CLICK_DATA_API, function (event) {
|
|
245
|
-
event.preventDefault();
|
|
246
|
-
instance.toggle();
|
|
247
|
-
});
|
|
248
240
|
}
|
|
241
|
+
|
|
242
|
+
EventHandler.on(document, EVENT_KEYUP_DATA_API, SELECTOR_DATA_TOGGLE, VGDropdown.keydownHandler);
|
|
243
|
+
EventHandler.on(document, EVENT_KEYDOWN_DATA_API, '.' + TARGET_CONTAINER, VGDropdown.keydownHandler);
|
|
244
|
+
EventHandler.on(document, EVENT_KEYUP_DATA_API, VGDropdown.clearDrops);
|
|
245
|
+
EventHandler.on(document, EVENT_CLICK_DATA_API, VGDropdown.clearDrops);
|
|
246
|
+
EventHandler.on(element, EVENT_CLICK_DATA_API, function (event) {
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
instance.toggle();
|
|
249
|
+
});
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
static hideOpenToggles(event) {
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import BaseModule from "../../base-module";
|
|
2
|
+
import Selectors from "../../../utils/js/dom/selectors";
|
|
3
|
+
import Responsive from "../../../utils/js/components/responsive";
|
|
4
|
+
import {getSVG} from "../../module-fn";
|
|
5
|
+
import {execute, isDisabled, isVisible, mergeDeepObject, noop, normalizeData} from "../../../utils/js/functions";
|
|
6
|
+
import EventHandler from "../../../utils/js/dom/event";
|
|
7
|
+
import {Manipulator} from "../../../utils/js/dom/manipulator";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Constants
|
|
11
|
+
*/
|
|
12
|
+
const NAME = 'nav';
|
|
13
|
+
const NAME_KEY = 'vg.nav';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constants Classes
|
|
17
|
+
*/
|
|
18
|
+
const CLASS_NAME_SHOW = 'show';
|
|
19
|
+
const CLASS_NAME_FADE = 'fade';
|
|
20
|
+
const CLASS_NAME_ACTIVE = 'active';
|
|
21
|
+
const SELECTOR_DATA_TOGGLE = '.vg-nav a';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Constants Events
|
|
25
|
+
*/
|
|
26
|
+
const EVENT_KEY_HIDE = `${NAME_KEY}.hide`;
|
|
27
|
+
const EVENT_KEY_HIDDEN = `${NAME_KEY}.hidden`;
|
|
28
|
+
const EVENT_KEY_SHOW = `${NAME_KEY}.show`;
|
|
29
|
+
const EVENT_KEY_SHOWN = `${NAME_KEY}.shown`;
|
|
30
|
+
|
|
31
|
+
const EVENT_MOUSEOVER_DATA_API = `mouseover.${NAME_KEY}.data.api`;
|
|
32
|
+
const EVENT_MOUSEOUT_DATA_API = `mouseout.${NAME_KEY}.data.api`;
|
|
33
|
+
const EVENT_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
|
|
34
|
+
const EVENT_KEYUP_DATA_API = `keyup.${NAME_KEY}.data.api`;
|
|
35
|
+
const EVENT_RESIZE_DATA_API = `resize.${NAME_KEY}.data.api`;
|
|
36
|
+
|
|
37
|
+
class VGNav extends BaseModule {
|
|
38
|
+
constructor(element, params = {}) {
|
|
39
|
+
super(element);
|
|
40
|
+
|
|
41
|
+
this._params = this._getParams(element, mergeDeepObject({
|
|
42
|
+
breakpoint: false,
|
|
43
|
+
placement: 'horizontal',
|
|
44
|
+
classes: {
|
|
45
|
+
hamburgerActive: 'vg-nav-hamburger-active',
|
|
46
|
+
hamburgerAlways: 'vg-nav-hamburger-always',
|
|
47
|
+
hamburger: 'vg-nav-hamburger',
|
|
48
|
+
container: 'vg-nav-container',
|
|
49
|
+
wrapper: 'vg-nav-wrapper',
|
|
50
|
+
active: 'vg-nav-active',
|
|
51
|
+
expand: 'vg-nav-expand',
|
|
52
|
+
cloned: 'vg-nav-cloned',
|
|
53
|
+
hover: 'vg-nav-hover',
|
|
54
|
+
flip: 'vg-nav-flip',
|
|
55
|
+
XXXL: 'vg-nav-xxxl',
|
|
56
|
+
XXL: 'vg-nav-xxl',
|
|
57
|
+
XL: 'vg-nav-xl',
|
|
58
|
+
LG: 'vg-nav-lg',
|
|
59
|
+
MD: 'vg-nav-md',
|
|
60
|
+
SM: 'vg-nav-sm',
|
|
61
|
+
XS: 'vg-nav-xs'
|
|
62
|
+
},
|
|
63
|
+
expand: true,
|
|
64
|
+
hover: false,
|
|
65
|
+
position: true,
|
|
66
|
+
collapse: true,
|
|
67
|
+
toggle: '<span class="default"></span>',
|
|
68
|
+
hamburger: {
|
|
69
|
+
enable: true,
|
|
70
|
+
always: false,
|
|
71
|
+
title: '',
|
|
72
|
+
body: null
|
|
73
|
+
},
|
|
74
|
+
callback: noop,
|
|
75
|
+
animation: true,
|
|
76
|
+
timeoutAnimation: 300,
|
|
77
|
+
ajax: {
|
|
78
|
+
route: '',
|
|
79
|
+
target: '',
|
|
80
|
+
method: 'get',
|
|
81
|
+
loader: false,
|
|
82
|
+
once: false,
|
|
83
|
+
output: true,
|
|
84
|
+
}
|
|
85
|
+
}, params));
|
|
86
|
+
|
|
87
|
+
this._navigation = null;
|
|
88
|
+
this.navigation = '.' + this._params.classes.wrapper;
|
|
89
|
+
|
|
90
|
+
this.movedLinks = [];
|
|
91
|
+
this.$links = Selectors.findAll('.' + this._params.classes.wrapper + ' > li', this.navigation)
|
|
92
|
+
|
|
93
|
+
if (this._params.animation === false) {
|
|
94
|
+
this._params.timeoutAnimation = 10
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static get NAME() {
|
|
99
|
+
return NAME;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static get NAME_KEY() {
|
|
103
|
+
return NAME_KEY;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
get navigation() {
|
|
107
|
+
return this._navigation;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
set navigation(el) {
|
|
111
|
+
let elm = Selectors.find(el, this._element);
|
|
112
|
+
if (!elm) return;
|
|
113
|
+
this._navigation = elm;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
build() {
|
|
117
|
+
if (!this.navigation) return;
|
|
118
|
+
|
|
119
|
+
let params = this._params;
|
|
120
|
+
|
|
121
|
+
// Вешаем основные классы
|
|
122
|
+
this._element.classList.add(params.classes.container);
|
|
123
|
+
this._element.classList.add('vg-nav-' + params.placement);
|
|
124
|
+
|
|
125
|
+
// Если нужно оставить список меню или установить медиа точку
|
|
126
|
+
if (!params.breakpoint) {
|
|
127
|
+
params.expand = false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!params.hamburger.always) {
|
|
131
|
+
if (!params.breakpoint || !params.expand) {
|
|
132
|
+
this._element.classList.add(params.classes.expand);
|
|
133
|
+
} else if (params.breakpoint !== false) {
|
|
134
|
+
this._element.classList.add('vg-nav-' + params.breakpoint);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
this._element.classList.add(params.classes.hamburgerAlways);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Меню срабатывает при наведении, если это не мобильное устройство
|
|
141
|
+
if (params.hover) {
|
|
142
|
+
this._element.classList.add(params.classes.hover);
|
|
143
|
+
|
|
144
|
+
if (Responsive.checkMobileOrTablet()) {
|
|
145
|
+
this._element.classList.remove(params.classes.hover);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Устанавливаем гамбургер, если его нет в разметке
|
|
150
|
+
if (params.expand && !params.hamburger.body && params.hamburger.enable) {
|
|
151
|
+
let isHamburger = Selectors.find('.' + params.classes.hamburger, this._element);
|
|
152
|
+
|
|
153
|
+
if (isHamburger === null) {
|
|
154
|
+
let mTitle = '',
|
|
155
|
+
hamburger = '<span class="' + params.classes.hamburger + '--lines"><span></span><span></span><span></span></span>';
|
|
156
|
+
|
|
157
|
+
if (params.hamburger.title) {
|
|
158
|
+
mTitle = '<span class="' + params.classes.hamburger + '--title">'+ params.hamburger.title +'</span>';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (params.hamburger.body !== null) {
|
|
162
|
+
hamburger = params.hamburger.body;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this._element.insertAdjacentHTML('afterbegin','<a href="#sidebar-nav" class="' + params.classes.hamburger + '" data-vg-toggle="sidebar">' + mTitle + hamburger +'</a>');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Устанавливаем указатель переключателя
|
|
170
|
+
if (params.toggle) {
|
|
171
|
+
let $dropdown_a = [...Selectors.findAll('.dropdown-mega > a, .dropdown > a', this._element)],
|
|
172
|
+
toggle = '<span class="toggle">' + params.toggle + '</span>';
|
|
173
|
+
|
|
174
|
+
if ($dropdown_a.length) {
|
|
175
|
+
$dropdown_a.forEach(function (elem) {
|
|
176
|
+
if (!elem.querySelector('.toggle') && !elem.closest('.dots')) {
|
|
177
|
+
elem.setAttribute('aria-expanded', 'false')
|
|
178
|
+
elem.insertAdjacentHTML('beforeend', toggle)
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (params.collapse && Responsive.check(this) && params.placement !== 'vertical') {
|
|
185
|
+
setCollapse(this);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if ('afterInit' in this._params.callback) {
|
|
189
|
+
execute(this._params.callback.afterInit, [this]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Функция сворачивания
|
|
194
|
+
* TODO Придумать что то с мега меню, которое уходит в подменю
|
|
195
|
+
* TODO Так же есть косяки при ресайзе
|
|
196
|
+
*/
|
|
197
|
+
function setCollapse(_this) {
|
|
198
|
+
let width_navigation_responsive = _this.navigation.clientWidth,
|
|
199
|
+
width_all_links_responsive = 0,
|
|
200
|
+
$dots = Selectors.find('.dots', _this.navigation),
|
|
201
|
+
_dots = getSVG('dots');
|
|
202
|
+
|
|
203
|
+
if (_this.$links.length) {
|
|
204
|
+
if ($dots) {
|
|
205
|
+
width_all_links_responsive = $dots.clientWidth
|
|
206
|
+
} else {
|
|
207
|
+
let $a = Selectors.find('a', _this.$links[0]),
|
|
208
|
+
$linkStyle = getComputedStyle($a),
|
|
209
|
+
paddingLeft = normalizeData($linkStyle.paddingLeft.slice(0, -2)),
|
|
210
|
+
paddingRight = normalizeData($linkStyle.paddingRight.slice(0, -2)),
|
|
211
|
+
padding = paddingLeft + paddingRight;
|
|
212
|
+
|
|
213
|
+
// TODO не совсем верно, но мы точно знаем ширину точек в svg - 16px
|
|
214
|
+
width_all_links_responsive = padding + 16;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (let $link of _this.$links) {
|
|
218
|
+
let width = $link.getBoundingClientRect().width;
|
|
219
|
+
width_all_links_responsive = width_all_links_responsive + width;
|
|
220
|
+
|
|
221
|
+
if ((width_navigation_responsive) < width_all_links_responsive) {
|
|
222
|
+
_this.movedLinks.push($link);
|
|
223
|
+
$link.remove();
|
|
224
|
+
} else {
|
|
225
|
+
if (_this.movedLinks.length) {
|
|
226
|
+
if ($dots) {
|
|
227
|
+
_this.navigation.insertBefore(_this.movedLinks[0], $dots)
|
|
228
|
+
} else {
|
|
229
|
+
_this.navigation.appendChild(_this.movedLinks[0])
|
|
230
|
+
}
|
|
231
|
+
_this.movedLinks.splice(0, 1);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (_this.movedLinks.length) {
|
|
237
|
+
if (!$dots) {
|
|
238
|
+
_this.navigation.insertAdjacentHTML('beforeend','<li class="dropdown dots">' + '<a href="#" aria-expanded="false">'+ _dots +'</a></li>');
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
if ($dots) {
|
|
242
|
+
$dots.remove();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let $d = _this.navigation.querySelector('.dots');
|
|
247
|
+
if ($d && _this.movedLinks.length) {
|
|
248
|
+
let $dropdown = $d.querySelector('ul');
|
|
249
|
+
if ($dropdown) {
|
|
250
|
+
for (let link of _this.movedLinks) {
|
|
251
|
+
$dropdown.prepend(link);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
let $dropdown = document.createElement('ul');
|
|
255
|
+
$dropdown.classList.add('dropdown-content');
|
|
256
|
+
$dropdown.classList.add('right');
|
|
257
|
+
|
|
258
|
+
for (let link of _this.movedLinks) {
|
|
259
|
+
$dropdown.prepend(link);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
$d.appendChild($dropdown);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
show(relatedTarget) {
|
|
270
|
+
let target = relatedTarget.relatedTarget;
|
|
271
|
+
|
|
272
|
+
if (!target || isDisabled(target)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!target.closest('.dropdown-content')) {
|
|
277
|
+
target.classList.add('first');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const showEvent = EventHandler.trigger(target, EVENT_KEY_SHOW, { relatedTarget });
|
|
281
|
+
if (showEvent.defaultPrevented) return;
|
|
282
|
+
|
|
283
|
+
let drop = Selectors.find('.dropdown-content', target),
|
|
284
|
+
link = target.firstElementChild;
|
|
285
|
+
|
|
286
|
+
if (link) link.setAttribute('aria-expanded', 'true');
|
|
287
|
+
drop.classList.add(CLASS_NAME_SHOW);
|
|
288
|
+
target.classList.add(CLASS_NAME_ACTIVE);
|
|
289
|
+
|
|
290
|
+
setDropPosition(drop)
|
|
291
|
+
|
|
292
|
+
const completeCallBack = () => {
|
|
293
|
+
drop.classList.add(CLASS_NAME_FADE);
|
|
294
|
+
EventHandler.trigger(target, EVENT_KEY_SHOWN, relatedTarget)
|
|
295
|
+
}
|
|
296
|
+
this._queueCallback(completeCallBack, drop, true, 50);
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
*
|
|
300
|
+
* @param $drop
|
|
301
|
+
*/
|
|
302
|
+
function setDropPosition($drop) {
|
|
303
|
+
let {width, right} = $drop.getBoundingClientRect(),
|
|
304
|
+
window_width = window.innerWidth;
|
|
305
|
+
|
|
306
|
+
let N_right = window_width - right - width;
|
|
307
|
+
|
|
308
|
+
$drop.classList.remove('right');
|
|
309
|
+
$drop.classList.remove('left');
|
|
310
|
+
|
|
311
|
+
let $parent = $drop.closest('li'),
|
|
312
|
+
$ul = $parent.querySelectorAll('ul');
|
|
313
|
+
|
|
314
|
+
if (N_right > width) {
|
|
315
|
+
for (const $el of $ul) {
|
|
316
|
+
$el.classList.add('left');
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
for (const $el of $ul) {
|
|
320
|
+
$el.classList.add('right');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
hide(relatedTarget) {
|
|
327
|
+
const _this = this;
|
|
328
|
+
if ('ontouchstart' in document.documentElement) {
|
|
329
|
+
for (const element of [].concat(...document.body.children)) {
|
|
330
|
+
EventHandler.off(element, 'mouseover', noop);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let element = relatedTarget.relatedTarget;
|
|
335
|
+
|
|
336
|
+
if ('elm' in relatedTarget && relatedTarget.elm) {
|
|
337
|
+
element = relatedTarget.elm
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (element) {
|
|
341
|
+
const hideEvent = EventHandler.trigger(element, EVENT_KEY_HIDE);
|
|
342
|
+
if (hideEvent.defaultPrevented) return;
|
|
343
|
+
|
|
344
|
+
element.classList.remove(CLASS_NAME_ACTIVE);
|
|
345
|
+
|
|
346
|
+
if (element.classList.contains('first')) {
|
|
347
|
+
element.classList.remove('first');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
[...Selectors.findAll('.' + CLASS_NAME_SHOW, element)].forEach(function (el, index) {
|
|
351
|
+
el.classList.remove(CLASS_NAME_FADE);
|
|
352
|
+
|
|
353
|
+
let parent = el.closest('.dropdown');
|
|
354
|
+
if (parent.classList.contains(CLASS_NAME_ACTIVE)) {
|
|
355
|
+
parent.classList.remove(CLASS_NAME_ACTIVE);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let link = el.previousElementSibling;
|
|
359
|
+
if (link) link.setAttribute('aria-expanded', 'false');
|
|
360
|
+
|
|
361
|
+
if (index === 0) {
|
|
362
|
+
const completeCallback = () => {
|
|
363
|
+
el.classList.remove(CLASS_NAME_SHOW);
|
|
364
|
+
EventHandler.trigger(el, EVENT_KEY_HIDDEN, relatedTarget)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
_this._queueCallback(completeCallback, el, true, 500);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* TODO если на странице несколько навигаций, то есть косяки
|
|
375
|
+
* @param element
|
|
376
|
+
* @param params
|
|
377
|
+
*/
|
|
378
|
+
static init(element, params = {}) {
|
|
379
|
+
const instance = VGNav.getOrCreateInstance(element, params);
|
|
380
|
+
instance.build();
|
|
381
|
+
|
|
382
|
+
let drops = Selectors.findAll('.dropdown', instance._navigation)
|
|
383
|
+
|
|
384
|
+
if (instance._params.hover) {
|
|
385
|
+
[...drops].forEach(function (el) {
|
|
386
|
+
let currentElem = null;
|
|
387
|
+
EventHandler.on(el, EVENT_MOUSEOVER_DATA_API, function (event) {
|
|
388
|
+
if (currentElem) return;
|
|
389
|
+
VGNav.hideOpenDrops(event);
|
|
390
|
+
|
|
391
|
+
let target = event.target.closest('.dropdown');
|
|
392
|
+
if (!target) return;
|
|
393
|
+
|
|
394
|
+
if (!instance.navigation.contains(target)) return;
|
|
395
|
+
currentElem = target;
|
|
396
|
+
|
|
397
|
+
let relatedTarget = {
|
|
398
|
+
relatedTarget: target
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
instance.show(relatedTarget);
|
|
402
|
+
});
|
|
403
|
+
EventHandler.on(el, EVENT_MOUSEOUT_DATA_API, function (event) {
|
|
404
|
+
if (!currentElem) return;
|
|
405
|
+
|
|
406
|
+
let relatedTarget = event.relatedTarget.closest('.dropdown'),
|
|
407
|
+
elm = currentElem;
|
|
408
|
+
|
|
409
|
+
while (relatedTarget) {
|
|
410
|
+
if (relatedTarget === currentElem) return;
|
|
411
|
+
relatedTarget = relatedTarget.parentNode;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
currentElem = null;
|
|
415
|
+
instance.hide({relatedTarget: relatedTarget, elm: elm});
|
|
416
|
+
})
|
|
417
|
+
})
|
|
418
|
+
} else {
|
|
419
|
+
EventHandler.on(document, EVENT_KEYUP_DATA_API, VGNav.clearDrops);
|
|
420
|
+
EventHandler.on(document, EVENT_CLICK_DATA_API, VGNav.clearDrops);
|
|
421
|
+
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
|
422
|
+
if (!Manipulator.has(this, 'aria-expanded')) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if ('click' in instance._params.callback) {
|
|
427
|
+
execute(instance._params.callback.click, [this]);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
event.preventDefault();
|
|
431
|
+
|
|
432
|
+
let self = this.closest('.vg-nav'),
|
|
433
|
+
isFirst = self.querySelector('.first');
|
|
434
|
+
|
|
435
|
+
let target = this.closest('.dropdown');
|
|
436
|
+
if (!target) return;
|
|
437
|
+
|
|
438
|
+
if (isDisabled(target) && !isVisible(target)) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (isFirst && this.closest('.first')) {
|
|
443
|
+
if (target.classList.contains('active')) {
|
|
444
|
+
instance.hide({relatedTarget: target});
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
[...Selectors.findAll('.active', self)].forEach(function (el) {
|
|
449
|
+
if (el && el !== target) {
|
|
450
|
+
instance.hide({relatedTarget: el})
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
instance.show({relatedTarget: target});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const vgNavSidebar = document.getElementById('sidebar-nav');
|
|
460
|
+
let hamburger = instance._element.querySelector('.' + instance._params.classes.hamburger);
|
|
461
|
+
|
|
462
|
+
if (vgNavSidebar && hamburger) {
|
|
463
|
+
vgNavSidebar.addEventListener('vg.sidebar.show', function () {
|
|
464
|
+
hamburger.classList.add(instance._params.classes.hamburgerActive);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
vgNavSidebar.addEventListener('vg.sidebar.hide', function () {
|
|
468
|
+
hamburger.classList.remove(instance._params.classes.hamburgerActive);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
static clearDrops(event) {
|
|
474
|
+
if (event.button === 2 || (event.type === 'keyup' && event.key !== 'Tab')) {
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
VGNav.hideOpenDrops(event)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
static hideOpenDrops(event) {
|
|
482
|
+
const openToggles = Selectors.findAll('.dropdown:not(.disabled):not(:disabled).active');
|
|
483
|
+
|
|
484
|
+
for (const toggle of openToggles) {
|
|
485
|
+
const context = VGNav.getInstance(toggle.closest('.vg-nav'));
|
|
486
|
+
if (!context) continue;
|
|
487
|
+
|
|
488
|
+
if (event.target.closest('.first')) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const relatedTarget = { relatedTarget: toggle }
|
|
493
|
+
|
|
494
|
+
if (event.type === 'click') {
|
|
495
|
+
relatedTarget.clickEvent = event
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
context.hide(relatedTarget)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
EventHandler.on(window, EVENT_RESIZE_DATA_API, function () {
|
|
504
|
+
if (Selectors.find('.vg-nav')) {
|
|
505
|
+
const instance = VGNav.getOrCreateInstance('.vg-nav', {});
|
|
506
|
+
instance.build();
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
export default VGNav;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Breakpoint viewport sizes and media queries.
|
|
2
|
+
//
|
|
3
|
+
// Breakpoints are defined as a map of (name: minimum width), order from small to large:
|
|
4
|
+
//
|
|
5
|
+
// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px)
|
|
6
|
+
//
|
|
7
|
+
// The map defined in the `$nav-breakpoints` global variable is used as the `$breakpoints` argument by default.
|
|
8
|
+
|
|
9
|
+
// Name of the next breakpoint, or null for the last breakpoint.
|
|
10
|
+
//
|
|
11
|
+
// >> breakpoint-next(sm)
|
|
12
|
+
// md
|
|
13
|
+
// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
|
|
14
|
+
// md
|
|
15
|
+
// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl xxl))
|
|
16
|
+
// md
|
|
17
|
+
@function breakpoint-next($name, $breakpoints: $nav-breakpoints, $breakpoint-names: map-keys($breakpoints)) {
|
|
18
|
+
$n: index($breakpoint-names, $name);
|
|
19
|
+
@if not $n {
|
|
20
|
+
@error "breakpoint `#{$name}` not found in `#{$breakpoints}`";
|
|
21
|
+
}
|
|
22
|
+
@return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Minimum breakpoint width. Null for the smallest (first) breakpoint.
|
|
26
|
+
//
|
|
27
|
+
// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
|
|
28
|
+
// 576px
|
|
29
|
+
@function breakpoint-min($name, $breakpoints: $nav-breakpoints) {
|
|
30
|
+
$min: map-get($breakpoints, $name);
|
|
31
|
+
@return if($min != 0, $min, null);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Maximum breakpoint width.
|
|
35
|
+
// The maximum value is reduced by 0.02px to work around the limitations of
|
|
36
|
+
// `min-` and `max-` prefixes and viewports with fractional widths.
|
|
37
|
+
// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max
|
|
38
|
+
// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.
|
|
39
|
+
// See https://bugs.webkit.org/show_bug.cgi?id=178261
|
|
40
|
+
//
|
|
41
|
+
// >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
|
|
42
|
+
// 767.98px
|
|
43
|
+
@function breakpoint-max($name, $breakpoints: $nav-breakpoints) {
|
|
44
|
+
$max: map-get($breakpoints, $name);
|
|
45
|
+
@return if($max and $max > 0, $max - .02, null);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.
|
|
49
|
+
// Useful for making responsive utilities.
|
|
50
|
+
//
|
|
51
|
+
// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
|
|
52
|
+
// "" (Returns a blank string)
|
|
53
|
+
// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px))
|
|
54
|
+
// "-sm"
|
|
55
|
+
@function breakpoint-infix($name, $breakpoints: $nav-breakpoints) {
|
|
56
|
+
@return if(breakpoint-min($name, $breakpoints) == null, "", "-#{$name}");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
|
|
60
|
+
// Makes the @content apply to the given breakpoint and wider.
|
|
61
|
+
@mixin media-breakpoint-up($name, $breakpoints: $nav-breakpoints) {
|
|
62
|
+
$min: breakpoint-min($name, $breakpoints);
|
|
63
|
+
@if $min {
|
|
64
|
+
@media (min-width: $min) {
|
|
65
|
+
@content;
|
|
66
|
+
}
|
|
67
|
+
} @else {
|
|
68
|
+
@content;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
|
|
73
|
+
// Makes the @content apply to the given breakpoint and narrower.
|
|
74
|
+
@mixin media-breakpoint-down($name, $breakpoints: $nav-breakpoints) {
|
|
75
|
+
$max: breakpoint-max($name, $breakpoints);
|
|
76
|
+
@if $max {
|
|
77
|
+
@media (max-width: $max) {
|
|
78
|
+
@content;
|
|
79
|
+
}
|
|
80
|
+
} @else {
|
|
81
|
+
@content;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Media that spans multiple breakpoint widths.
|
|
86
|
+
// Makes the @content apply between the min and max breakpoints
|
|
87
|
+
@mixin media-breakpoint-between($lower, $upper, $breakpoints: $nav-breakpoints) {
|
|
88
|
+
$min: breakpoint-min($lower, $breakpoints);
|
|
89
|
+
$max: breakpoint-max($upper, $breakpoints);
|
|
90
|
+
|
|
91
|
+
@if $min != null and $max != null {
|
|
92
|
+
@media (min-width: $min) and (max-width: $max) {
|
|
93
|
+
@content;
|
|
94
|
+
}
|
|
95
|
+
} @else if $max == null {
|
|
96
|
+
@include media-breakpoint-up($lower, $breakpoints) {
|
|
97
|
+
@content;
|
|
98
|
+
}
|
|
99
|
+
} @else if $min == null {
|
|
100
|
+
@include media-breakpoint-down($upper, $breakpoints) {
|
|
101
|
+
@content;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Media between the breakpoint's minimum and maximum widths.
|
|
107
|
+
// No minimum for the smallest breakpoint, and no maximum for the largest one.
|
|
108
|
+
// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.
|
|
109
|
+
@mixin media-breakpoint-only($name, $breakpoints: $nav-breakpoints) {
|
|
110
|
+
$min: breakpoint-min($name, $breakpoints);
|
|
111
|
+
$next: breakpoint-next($name, $breakpoints);
|
|
112
|
+
$max: breakpoint-max($next, $breakpoints);
|
|
113
|
+
|
|
114
|
+
@if $min != null and $max != null {
|
|
115
|
+
@media (min-width: $min) and (max-width: $max) {
|
|
116
|
+
@content;
|
|
117
|
+
}
|
|
118
|
+
} @else if $max == null {
|
|
119
|
+
@include media-breakpoint-up($name, $breakpoints) {
|
|
120
|
+
@content;
|
|
121
|
+
}
|
|
122
|
+
} @else if $min == null {
|
|
123
|
+
@include media-breakpoint-down($next, $breakpoints) {
|
|
124
|
+
@content;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|