vgapp 1.5.9 → 1.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.
@@ -23,7 +23,7 @@ const getSVG = (name) => {
23
23
  waiting: '<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M51 10.02C55.5248 12.6326 59.2888 16.3816 61.9194 20.8959C64.5501 25.4103 65.9561 30.5334 65.9983 35.7581C66.0405 40.9828 64.7174 46.1279 62.1601 50.6841C59.6027 55.2404 55.8997 59.0497 51.4177 61.735C46.9357 64.4203 41.8301 65.8885 36.6063 65.9942C31.3825 66.1 26.2216 64.8395 21.6347 62.3378C17.0477 59.836 13.1936 56.1796 10.454 51.7306C7.71439 47.2816 6.18423 42.1942 6.015 36.972L6 36L6.015 35.028C6.18301 29.847 7.69064 24.7979 10.3909 20.373C13.0912 15.9481 16.892 12.2984 21.4227 9.77969C25.9534 7.26098 31.0594 5.95923 36.243 6.00136C41.4266 6.04348 46.5108 7.42804 51 10.02ZM36 18C35.2652 18.0001 34.556 18.2699 34.0069 18.7582C33.4578 19.2465 33.107 19.9193 33.021 20.649L33 21V36L33.027 36.393C33.0954 36.9135 33.2992 37.407 33.618 37.824L33.879 38.124L42.879 47.124L43.161 47.37C43.6871 47.7782 44.3341 47.9998 45 47.9998C45.6659 47.9998 46.3129 47.7782 46.839 47.37L47.121 47.121L47.37 46.839C47.7782 46.3129 47.9998 45.6659 47.9998 45C47.9998 44.3341 47.7782 43.6872 47.37 43.161L47.121 42.879L39 34.755V21L38.979 20.649C38.893 19.9193 38.5422 19.2465 37.9931 18.7582C37.444 18.2699 36.7348 18.0001 36 18Z" fill="black"/></svg>',
24
24
  warning: '<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M36.5 5C44.8543 5 52.8665 8.31874 58.7739 14.2261C64.6813 20.1335 68 28.1457 68 36.5C68 44.8543 64.6813 52.8665 58.7739 58.7739C52.8665 64.6813 44.8543 68 36.5 68C28.1457 68 20.1335 64.6813 14.2261 58.7739C8.31874 52.8665 5 44.8543 5 36.5C5 28.1457 8.31874 20.1335 14.2261 14.2261C20.1335 8.31874 28.1457 5 36.5 5ZM36.5 18.5C35.9287 18.4995 35.3635 18.6181 34.8406 18.8484C34.3178 19.0786 33.8487 19.4154 33.4633 19.8372C33.0779 20.259 32.7848 20.7565 32.6025 21.298C32.4203 21.8395 32.353 22.413 32.405 22.982L34.0475 41.009C34.1112 41.6151 34.397 42.1761 34.8499 42.5839C35.3028 42.9917 35.8906 43.2173 36.5 43.2173C37.1094 43.2173 37.6972 42.9917 38.1501 42.5839C38.603 42.1761 38.8888 41.6151 38.9525 41.009L40.5905 22.982C40.6424 22.4134 40.5753 21.8403 40.3933 21.2991C40.2114 20.7579 39.9186 20.2606 39.5337 19.8389C39.1488 19.4171 38.6803 19.0803 38.1579 18.8498C37.6356 18.6192 37.0709 18.5001 36.5 18.5ZM36.5 54.5C37.4548 54.5 38.3705 54.1207 39.0456 53.4456C39.7207 52.7705 40.1 51.8548 40.1 50.9C40.1 49.9452 39.7207 49.0295 39.0456 48.3544C38.3705 47.6793 37.4548 47.3 36.5 47.3C35.5452 47.3 34.6295 47.6793 33.9544 48.3544C33.2793 49.0295 32.9 49.9452 32.9 50.9C32.9 51.8548 33.2793 52.7705 33.9544 53.4456C34.6295 54.1207 35.5452 54.5 36.5 54.5Z" fill="#000000"/></svg>',
25
25
  info: '<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M36 4.5C18.6047 4.5 4.5 18.6047 4.5 36C4.5 53.3953 18.6047 67.5 36 67.5C53.3953 67.5 67.5 53.3953 67.5 36C67.5 18.6047 53.3953 4.5 36 4.5ZM33.75 20.8125C33.75 20.5031 34.0031 20.25 34.3125 20.25H37.6875C37.9969 20.25 38.25 20.5031 38.25 20.8125V39.9375C38.25 40.2469 37.9969 40.5 37.6875 40.5H34.3125C34.0031 40.5 33.75 40.2469 33.75 39.9375V20.8125ZM36 51.75C35.1168 51.732 34.2759 51.3685 33.6577 50.7375C33.0395 50.1065 32.6932 49.2584 32.6932 48.375C32.6932 47.4916 33.0395 46.6435 33.6577 46.0125C34.2759 45.3815 35.1168 45.018 36 45C36.8832 45.018 37.7241 45.3815 38.3423 46.0125C38.9605 46.6435 39.3068 47.4916 39.3068 48.375C39.3068 49.2584 38.9605 50.1065 38.3423 50.7375C37.7241 51.3685 36.8832 51.732 36 51.75Z" fill="#000000"/></svg>',
26
- dots: '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 17C11.1046 17 12 16.1046 12 15C12 13.8954 11.1046 13 10 13C8.89543 13 8 13.8954 8 15C8 16.1046 8.89543 17 10 17Z" fill="black"/><path d="M10 12C11.1046 12 12 11.1046 12 10C12 8.89543 11.1046 8 10 8C8.89543 8 8 8.89543 8 10C8 11.1046 8.89543 12 10 12Z" fill="black"/><path d="M10 7C11.1046 7 12 6.10457 12 5C12 3.89543 11.1046 3 10 3C8.89543 3 8 3.89543 8 5C8 6.10457 8.89543 7 10 7Z" fill="black"/></svg>',
26
+ dots: '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10 17C11.1046 17 12 16.1046 12 15C12 13.8954 11.1046 13 10 13C8.89543 13 8 13.8954 8 15C8 16.1046 8.89543 17 10 17Z" fill="currentColor"/><path d="M10 12C11.1046 12 12 11.1046 12 10C12 8.89543 11.1046 8 10 8C8.89543 8 8 8.89543 8 10C8 11.1046 8.89543 12 10 12Z" fill="currentColor"/><path d="M10 7C11.1046 7 12 6.10457 12 5C12 3.89543 11.1046 3 10 3C8.89543 3 8 3.89543 8 5C8 6.10457 8.89543 7 10 7Z" fill="currentColor"/></svg>',
27
27
  remove: '<svg class="vg-icon-remove" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M7.05 5.636 12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414L12 13.414l-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636Z" fill="currentColor"/></svg>',
28
28
  cross: '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M15.3149 0.340332C15.4478 0.20752 15.6027 0.118978 15.7798 0.074707C15.9569 0.0249023 16.134 0.0249023 16.311 0.074707C16.4881 0.118978 16.6431 0.20752 16.7759 0.340332C16.9142 0.473145 17.0055 0.630859 17.0498 0.813477C17.0941 0.99056 17.0941 1.16764 17.0498 1.34473C17.0055 1.52181 16.917 1.67676 16.7842 1.80957L1.80127 16.8008C1.67399 16.9281 1.52181 17.0111 1.34473 17.0498C1.16764 17.0941 0.987793 17.0941 0.805176 17.0498C0.628092 17.0111 0.470378 16.9253 0.332031 16.7925C0.199219 16.6597 0.110677 16.502 0.0664062 16.3193C0.0221354 16.1423 0.0221354 15.9652 0.0664062 15.7881C0.110677 15.611 0.196452 15.4588 0.32373 15.3315L15.3149 0.340332ZM16.7842 15.3232C16.917 15.4561 17.0028 15.6082 17.0415 15.7798C17.0858 15.9569 17.0858 16.134 17.0415 16.311C17.0028 16.4937 16.9142 16.6541 16.7759 16.7925C16.6431 16.9253 16.4881 17.0138 16.311 17.0581C16.134 17.1024 15.9569 17.1024 15.7798 17.0581C15.6027 17.0138 15.4478 16.9253 15.3149 16.7925L0.32373 1.80127C0.201986 1.67952 0.116211 1.53011 0.0664062 1.35303C0.0221354 1.17594 0.0221354 0.996094 0.0664062 0.813477C0.116211 0.630859 0.204753 0.473145 0.332031 0.340332C0.464844 0.20752 0.622559 0.118978 0.805176 0.074707C0.987793 0.0304362 1.16488 0.0304362 1.33643 0.074707C1.51351 0.118978 1.66846 0.204753 1.80127 0.332031L16.7842 15.3232Z" fill="#000"/></svg>',
29
29
  'eye-open': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M288 80c-65.2 0-118.8 29.6-159.9 67.7C89.6 183.5 63 226 49.4 256c13.6 30 40.2 72.5 78.6 108.3C169.2 402.4 222.8 432 288 432s118.8-29.6 159.9-67.7C486.4 328.5 513 286 526.6 256c-13.6-30-40.2-72.5-78.6-108.3C406.8 109.6 353.2 80 288 80zM95.4 112.6C142.5 68.8 207.2 32 288 32s145.5 36.8 192.6 80.6c46.8 43.5 78.1 95.4 93 131.1c3.3 7.9 3.3 16.7 0 24.6c-14.9 35.7-46.2 87.7-93 131.1C433.5 443.2 368.8 480 288 480s-145.5-36.8-192.6-80.6C48.6 356 17.3 304 2.5 268.3c-3.3-7.9-3.3-16.7 0-24.6C17.3 208 48.6 156 95.4 112.6zM288 336c44.2 0 80-35.8 80-80s-35.8-80-80-80c-.7 0-1.3 0-2 0c1.3 5.1 2 10.5 2 16c0 35.3-28.7 64-64 64c-5.5 0-10.9-.7-16-2c0 .7 0 1.3 0 2c0 44.2 35.8 80 80 80zm0-208a128 128 0 1 1 0 256 128 128 0 1 1 0-256z"/></svg>',
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Описание: адаптивная навигация VGNav с выпадающими пунктами и гамбургером.
3
- * Возможности: click/hover, вложенные меню, позиционирование, события и интеграция с VGSidebar.
3
+ * Возможности: click/hover, overflow первого уровня, вложенные меню, позиционирование, события и интеграция с VGSidebar.
4
4
  */
5
5
  import BaseModule from "../../base-module";
6
6
  import Selectors from "../../../utils/js/dom/selectors";
@@ -15,7 +15,8 @@ import {
15
15
  } from "../../../utils/js/functions";
16
16
  import EventHandler from "../../../utils/js/dom/event";
17
17
  import {Manipulator} from "../../../utils/js/dom/manipulator";
18
- import Placement from "../../../utils/js/components/placement";
18
+ import Placement from "../../../utils/js/components/placement";
19
+ import {getSVG} from "../../module-fn";
19
20
 
20
21
  /**
21
22
  * Constants
@@ -60,8 +61,22 @@ class VGNav extends BaseModule {
60
61
  placement: 'horizontal',
61
62
  // Включает открытие выпадающих пунктов при наведении на десктопе.
62
63
  hover: true,
63
- // Включает ограничение высоты и overflow для списка внутри выпадающего меню.
64
- dropListScroll: true,
64
+ // Включает ограничение высоты и overflow для списка внутри выпадающего меню.
65
+ dropListScroll: true,
66
+ // Переносит лишние пункты первого уровня горизонтального меню в dropdown.
67
+ overflow: {
68
+ enable: false,
69
+ // Максимум видимых исходных пунктов; 0 оставляет только ограничение по ширине.
70
+ count: 0,
71
+ // Элементы, которые нельзя переносить (megamenu сохраняет геометрию первого уровня).
72
+ keep: '.dropdown-mega, [data-vg-nav-overflow="keep"]',
73
+ trigger: {
74
+ icon: 'dots',
75
+ body: null,
76
+ label: '',
77
+ ariaLabel: 'Другие разделы'
78
+ }
79
+ },
65
80
  // Настройки плавного переключения между соседними пунктами первого уровня.
66
81
  hoversmoothfirstlevel: {
67
82
  // Включает режим плавного перехода без мгновенного закрытия соседнего дропа.
@@ -105,8 +120,11 @@ class VGNav extends BaseModule {
105
120
  hamburgerAlways: 'vg-nav-hamburger-always',
106
121
  hamburger: 'vg-nav-hamburger',
107
122
  container: 'vg-nav-container',
108
- wrapper: 'vg-nav-wrapper',
109
- dropListScroll: 'vg-nav-drop-list-scroll',
123
+ wrapper: 'vg-nav-wrapper',
124
+ dropListScroll: 'vg-nav-drop-list-scroll',
125
+ overflowEnabled: 'vg-nav-overflow-enabled',
126
+ overflow: 'vg-nav-overflow',
127
+ overflowList: 'vg-nav-overflow-list',
110
128
  active: 'vg-nav-active',
111
129
  expand: 'vg-nav-expand',
112
130
  cloned: 'vg-nav-cloned',
@@ -131,8 +149,16 @@ class VGNav extends BaseModule {
131
149
  this._openDrops = new Map();
132
150
  this._pointerPosition = null;
133
151
  this._smoothSwitch = null;
134
- this._handleScroll = this._handleScroll.bind(this);
135
- this._handleResize = this._handleResize.bind(this);
152
+ this._handleScroll = this._handleScroll.bind(this);
153
+ this._handleResize = this._handleResize.bind(this);
154
+ this._handleOverflowResize = this._handleOverflowResize.bind(this);
155
+ this._overflowTrigger = null;
156
+ this._overflowList = null;
157
+ this._overflowItems = [];
158
+ this._overflowAnchors = new Map();
159
+ this._overflowObserver = null;
160
+ this._overflowFrame = null;
161
+ this._overflowWindowFallback = false;
136
162
  }
137
163
 
138
164
  static get NAME() {
@@ -162,7 +188,8 @@ class VGNav extends BaseModule {
162
188
  // Вешаем основные классы
163
189
  this._element.classList.add(classes.container);
164
190
  this._element.classList.add('vg-nav-' + params.placement);
165
- this._element.classList.toggle(classes.dropListScroll, !!params.dropListScroll);
191
+ this._element.classList.toggle(classes.dropListScroll, !!params.dropListScroll);
192
+ this._setupOverflow();
166
193
 
167
194
  if (!params.hamburger.always) {
168
195
  if (!params.breakpoint) {
@@ -202,7 +229,7 @@ class VGNav extends BaseModule {
202
229
  }
203
230
 
204
231
  // Устанавливаем указатель дропа
205
- if (params.toggle) {
232
+ if (params.toggle) {
206
233
  let $dropdown_a = [...Selectors.findAll('.dropdown > a', this._element)],
207
234
  toggle = '<span class="toggle">' + params.toggle + '</span>';
208
235
 
@@ -213,10 +240,12 @@ class VGNav extends BaseModule {
213
240
  elem.insertAdjacentHTML('beforeend', toggle);
214
241
  }
215
242
  });
216
- }
217
- }
218
-
219
- if ('afterInit' in this._params.callbacks) {
243
+ }
244
+ }
245
+
246
+ this.refresh();
247
+
248
+ if ('afterInit' in this._params.callbacks) {
220
249
  execute(this._params.callbacks.afterInit, [this]);
221
250
  }
222
251
  }
@@ -421,6 +450,205 @@ class VGNav extends BaseModule {
421
450
  window.setTimeout(callback, duration);
422
451
  }
423
452
 
453
+ /**
454
+ * Пересчитывает пункты первого уровня, перенесённые в overflow-dropdown.
455
+ * Полезно вызывать после динамического изменения состава или текста меню.
456
+ */
457
+ refresh() {
458
+ if (!this._isOverflowEnabled()) return;
459
+ this._collectOverflowItems();
460
+ this._redistributeOverflowItems();
461
+ }
462
+
463
+ _isOverflowEnabled() {
464
+ return !!this._params.overflow?.enable && this._params.placement === 'horizontal';
465
+ }
466
+
467
+ _setupOverflow() {
468
+ if (!this._isOverflowEnabled() || this._overflowTrigger) return;
469
+
470
+ this._element.classList.add(this._classes.overflowEnabled);
471
+ const triggerParams = this._params.overflow.trigger || {};
472
+ const item = document.createElement('li');
473
+ item.classList.add('dropdown', 'dots', this._classes.overflow);
474
+ item.hidden = true;
475
+
476
+ const link = document.createElement('a');
477
+ link.href = '#';
478
+ link.setAttribute('aria-expanded', 'false');
479
+ link.setAttribute('aria-label', triggerParams.ariaLabel || 'Другие разделы');
480
+
481
+ const icon = document.createElement('span');
482
+ icon.classList.add(this._classes.overflow + '-icon');
483
+ icon.setAttribute('aria-hidden', 'true');
484
+ icon.innerHTML = triggerParams.body !== null
485
+ ? triggerParams.body
486
+ : (getSVG(triggerParams.icon || 'dots') || '');
487
+ link.append(icon);
488
+
489
+ if (triggerParams.label) {
490
+ const label = document.createElement('span');
491
+ label.classList.add(this._classes.overflow + '-label');
492
+ label.textContent = triggerParams.label;
493
+ link.append(label);
494
+ }
495
+
496
+ const content = document.createElement('div');
497
+ content.classList.add('dropdown-content');
498
+ const list = document.createElement('ul');
499
+ list.classList.add('vg-nav-drop-list', this._classes.overflowList);
500
+ content.append(list);
501
+ item.append(link, content);
502
+ this.navigation.append(item);
503
+
504
+ this._overflowTrigger = item;
505
+ this._overflowList = list;
506
+ this._collectOverflowItems();
507
+ this._startOverflowObserver();
508
+ }
509
+
510
+ _collectOverflowItems() {
511
+ if (!this.navigation || !this._overflowTrigger) return;
512
+
513
+ [...this.navigation.children].forEach((item) => {
514
+ if (item === this._overflowTrigger || this._overflowAnchors.has(item)) return;
515
+ const anchor = document.createComment('vg-nav-overflow-item');
516
+ this.navigation.insertBefore(anchor, item);
517
+ this._overflowAnchors.set(item, anchor);
518
+ this._overflowItems.push(item);
519
+ });
520
+ }
521
+
522
+ _restoreOverflowItems(removeAnchors = false) {
523
+ this._overflowItems.forEach((item) => {
524
+ const anchor = this._overflowAnchors.get(item);
525
+ if (!anchor?.parentNode) return;
526
+ if (removeAnchors) {
527
+ anchor.replaceWith(item);
528
+ } else {
529
+ anchor.after(item);
530
+ }
531
+ });
532
+
533
+ if (removeAnchors) this._overflowAnchors.clear();
534
+ }
535
+
536
+ _redistributeOverflowItems() {
537
+ if (!this.navigation || !this._overflowTrigger || !this._overflowList) return;
538
+
539
+ this._closeOverflowDrops();
540
+ this._restoreOverflowItems();
541
+ this._overflowTrigger.hidden = true;
542
+
543
+ if (!this._isOverflowMeasurable()) return;
544
+
545
+ const moved = new Set();
546
+ const movable = this._overflowItems.filter((item) => !this._isOverflowKept(item));
547
+ const count = Math.max(0, Number.parseInt(this._params.overflow.count, 10) || 0);
548
+ let visibleCount = this._overflowItems.length;
549
+
550
+ if (count > 0) {
551
+ for (let index = movable.length - 1; index >= 0 && visibleCount > count; index--) {
552
+ moved.add(movable[index]);
553
+ this._overflowList.prepend(movable[index]);
554
+ visibleCount--;
555
+ }
556
+ }
557
+
558
+ if (moved.size || !this._overflowFits()) {
559
+ this._overflowTrigger.hidden = false;
560
+ }
561
+
562
+ for (let index = movable.length - 1; index >= 0 && !this._overflowFits(); index--) {
563
+ const item = movable[index];
564
+ if (moved.has(item)) continue;
565
+ moved.add(item);
566
+ this._overflowList.prepend(item);
567
+ }
568
+
569
+ this._overflowItems.forEach((item) => {
570
+ if (moved.has(item)) this._overflowList.append(item);
571
+ });
572
+
573
+ this._overflowTrigger.hidden = moved.size === 0;
574
+ this._element.classList.toggle(this._classes.overflow + '-active', moved.size > 0);
575
+ }
576
+
577
+ _isOverflowKept(item) {
578
+ const selector = String(this._params.overflow.keep || '').trim();
579
+ return !!selector && item.matches(selector);
580
+ }
581
+
582
+ _isOverflowMeasurable() {
583
+ if (!this._element.isConnected) return false;
584
+ if (this.navigation.getClientRects().length === 0) return false;
585
+ return this.navigation.clientWidth > 0 || this.navigation.getBoundingClientRect().width > 0;
586
+ }
587
+
588
+ _overflowFits() {
589
+ const width = this.navigation.clientWidth || this.navigation.getBoundingClientRect().width;
590
+ if (!width) return true;
591
+
592
+ const style = window.getComputedStyle(this.navigation);
593
+ const gap = Number.parseFloat(style.columnGap) || 0;
594
+ const children = [...this.navigation.children].filter((item) => !item.hidden);
595
+ const used = children.reduce((sum, item) => {
596
+ const itemStyle = window.getComputedStyle(item);
597
+ return sum + item.getBoundingClientRect().width
598
+ + (Number.parseFloat(itemStyle.marginLeft) || 0)
599
+ + (Number.parseFloat(itemStyle.marginRight) || 0);
600
+ }, 0) + Math.max(0, children.length - 1) * gap;
601
+
602
+ return used <= width + 0.5;
603
+ }
604
+
605
+ _closeOverflowDrops() {
606
+ [...this.navigation.querySelectorAll('.dropdown.active')].forEach((item) => {
607
+ this.hide({relatedTarget: item});
608
+ });
609
+ }
610
+
611
+ _startOverflowObserver() {
612
+ if (typeof ResizeObserver !== 'undefined') {
613
+ this._overflowObserver = new ResizeObserver(this._handleOverflowResize);
614
+ this._overflowObserver.observe(this.navigation);
615
+ this._overflowObserver.observe(this._element);
616
+ } else {
617
+ window.addEventListener('resize', this._handleOverflowResize);
618
+ this._overflowWindowFallback = true;
619
+ }
620
+
621
+ if (document.fonts?.ready) {
622
+ document.fonts.ready.then(() => {
623
+ if (this._element) this._handleOverflowResize();
624
+ });
625
+ }
626
+ }
627
+
628
+ _handleOverflowResize() {
629
+ if (this._overflowFrame !== null) return;
630
+ const schedule = typeof window.requestAnimationFrame === 'function'
631
+ ? window.requestAnimationFrame.bind(window)
632
+ : (callback) => window.setTimeout(callback, 0);
633
+
634
+ this._overflowFrame = schedule(() => {
635
+ this._overflowFrame = null;
636
+ this.refresh();
637
+ });
638
+ }
639
+
640
+ dispose() {
641
+ if (this._overflowObserver) this._overflowObserver.disconnect();
642
+ if (this._overflowWindowFallback) window.removeEventListener('resize', this._handleOverflowResize);
643
+ if (this._overflowFrame !== null && typeof window.cancelAnimationFrame === 'function') {
644
+ window.cancelAnimationFrame(this._overflowFrame);
645
+ }
646
+ this._restoreOverflowItems(true);
647
+ this._overflowTrigger?.remove();
648
+ this._element.classList.remove(this._classes.overflowEnabled, this._classes.overflow + '-active');
649
+ super.dispose();
650
+ }
651
+
424
652
  _startSmoothSwitch(currentDrop, previousDrop, dropContent) {
425
653
  const staleSwitch = this._smoothSwitch;
426
654
  const transfer = { currentDrop, previousDrop };
@@ -150,6 +150,41 @@
150
150
  }
151
151
  }
152
152
 
153
+ &.vg-nav-overflow-enabled.vg-nav-horizontal {
154
+ > .vg-nav-wrapper {
155
+ width: 100%;
156
+ min-width: 0;
157
+ flex-wrap: nowrap;
158
+
159
+ > li {
160
+ flex: 0 0 auto;
161
+ }
162
+ }
163
+
164
+ .vg-nav-overflow {
165
+ > a {
166
+ align-items: center;
167
+ justify-content: center;
168
+ }
169
+
170
+ &-icon {
171
+ display: inline-flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ line-height: 1;
175
+ }
176
+
177
+ &-label:not(:first-child) {
178
+ margin-left: .375rem;
179
+ }
180
+
181
+ > .dropdown-content > .vg-nav-overflow-list {
182
+ max-height: none;
183
+ overflow: visible;
184
+ }
185
+ }
186
+ }
187
+
153
188
  /** set placement **/
154
189
  @include placement.placement();
155
190
 
@@ -59,7 +59,7 @@
59
59
  background-color: var(--vg-table-fixed-cell-bg);
60
60
  background-image: none;
61
61
  box-shadow: inset 0 0 0 100vmax transparent;
62
- transition: box-shadow var(--vg-table-transition);
62
+ transition: var(--vg-table-transition);
63
63
  }
64
64
 
65
65
  .vg-table-striped tbody tr:nth-child(even) > [data-vg-table-fixed-side] {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Описание: всплывающие подсказки и информационные popover VGApp.
3
- * Возможности: Data API, позиционирование, события и очистка при удалении триггера.
3
+ * Возможности: Data API, подавление нативного title, позиционирование со стрелкой без зазоров и очистка при удалении триггера.
4
4
  */
5
5
  import BaseModule from "../../base-module";
6
6
  import {isDisabled, makeRandomString, mergeDeepObject} from "../../../utils/js/functions";
@@ -94,7 +94,8 @@ class VGTooltip extends BaseModule {
94
94
  this._isHiding = false;
95
95
  this._showTimeout = null;
96
96
  this._hideTimeout = null;
97
- this._triggerObserver = null;
97
+ this._triggerObserver = null;
98
+ this._titleSuppressed = false;
98
99
 
99
100
  this._fixTitle();
100
101
 
@@ -122,10 +123,15 @@ class VGTooltip extends BaseModule {
122
123
  return this._isShown() ? this.hide() : this.show(relatedTarget);
123
124
  }
124
125
 
125
- show(relatedTarget) {
126
- if (isDisabled(this._element) || this._isShown()) return;
127
-
128
- this._clearTimeouts();
126
+ show(relatedTarget) {
127
+ if (!this._element || isDisabled(this._element)) return;
128
+
129
+ this._fixTitle();
130
+ this._clearTimeouts();
131
+ if (this._isShown()) {
132
+ if (!this._isHiding) this._observeTrigger();
133
+ return;
134
+ }
129
135
 
130
136
  this._showTimeout = setTimeout(() => {
131
137
  this._showTimeout = null;
@@ -226,7 +232,9 @@ class VGTooltip extends BaseModule {
226
232
 
227
233
  if (Object.prototype.hasOwnProperty.call(this._element.dataset, 'vgOriginalTitle')) {
228
234
  this._element.setAttribute('title', this._element.dataset.vgOriginalTitle);
229
- this._element.removeAttribute('data-vg-original-title');
235
+ this._element.removeAttribute('data-vg-original-title');
236
+ } else {
237
+ this._element.removeAttribute('title');
230
238
  }
231
239
  }
232
240
 
@@ -319,10 +327,12 @@ class VGTooltip extends BaseModule {
319
327
  _fixTitle() {
320
328
  const title = this._element.getAttribute('title');
321
329
 
322
- if (title === null) return;
323
-
324
- this._element.setAttribute('data-vg-original-title', title);
325
- this._element.removeAttribute('title');
330
+ if (title !== null && (title !== '' || !this._titleSuppressed)) {
331
+ this._element.setAttribute('data-vg-original-title', title);
332
+ }
333
+ // Пустой title также блокирует нативную подсказку, унаследованную от предка.
334
+ this._element.setAttribute('title', '');
335
+ this._titleSuppressed = true;
326
336
  }
327
337
 
328
338
  _setPlacement() {
@@ -378,12 +388,12 @@ class VGTooltip extends BaseModule {
378
388
  );
379
389
 
380
390
  arrow.style.left = `${arrowLeft}px`;
381
- arrow.style.transform = 'translateX(-50%) rotate(45deg)';
391
+ arrow.style.transform = 'translateX(-50%)';
382
392
 
383
393
  if (placement.startsWith('top')) {
384
- arrow.style.bottom = '-4px';
394
+ arrow.style.bottom = 'calc(var(--vg-tooltip-arrow-height) / -2 + 1px)';
385
395
  } else {
386
- arrow.style.top = '-4px';
396
+ arrow.style.top = 'calc(var(--vg-tooltip-arrow-height) / -2 + 1px)';
387
397
  }
388
398
  }
389
399
 
@@ -396,12 +406,12 @@ class VGTooltip extends BaseModule {
396
406
  );
397
407
 
398
408
  arrow.style.top = `${arrowTop}px`;
399
- arrow.style.transform = 'translateY(-50%) rotate(45deg)';
409
+ arrow.style.transform = 'translateY(-50%)';
400
410
 
401
411
  if (placement.startsWith('left')) {
402
- arrow.style.right = '-4px';
412
+ arrow.style.right = 'calc(var(--vg-tooltip-arrow-width) / -2 + 1px)';
403
413
  } else {
404
- arrow.style.left = '-4px';
414
+ arrow.style.left = 'calc(var(--vg-tooltip-arrow-width) / -2 + 1px)';
405
415
  }
406
416
  }
407
417
  }
@@ -11,7 +11,7 @@ $tooltip-map: (
11
11
  border-radius: 6px,
12
12
  box-shadow: 0 6px 20px var(--vg-shadow-soft-color, rgba(0, 0, 0, .18)),
13
13
  word-break: break-word,
14
- arrow-width: 8px,
15
- arrow-height: 8px,
14
+ arrow-width: 10px,
15
+ arrow-height: 10px,
16
16
  border-color: var(--vg-border-color, #{vg.$border-color}),
17
17
  ) !default;
@@ -12,7 +12,9 @@
12
12
 
13
13
  .vg-tooltip {
14
14
  @include vg.mix-vars('tooltip', vars.$tooltip-map);
15
- position: absolute;
15
+ position: absolute;
16
+ // Координатами управляет Placement; общие data-vg-placement правила не должны задавать противоположный край.
17
+ inset: auto;
16
18
  z-index: var(--vg-tooltip-zindex);
17
19
  pointer-events: none;
18
20
  opacity: 0;
@@ -42,9 +44,28 @@
42
44
  position: absolute;
43
45
  width: var(--vg-tooltip-arrow-width);
44
46
  height: var(--vg-tooltip-arrow-height);
45
- background: var(--vg-tooltip-background, #222);
46
- transform: rotate(45deg);
47
- }
47
+ background: var(--vg-tooltip-background, #222);
48
+ }
49
+
50
+ &[data-vg-placement^="top"] &-arrow {
51
+ height: calc(var(--vg-tooltip-arrow-height) / 2);
52
+ clip-path: polygon(0 0, 100% 0, 50% 100%);
53
+ }
54
+
55
+ &[data-vg-placement^="bottom"] &-arrow {
56
+ height: calc(var(--vg-tooltip-arrow-height) / 2);
57
+ clip-path: polygon(50% 0, 100% 100%, 0 100%);
58
+ }
59
+
60
+ &[data-vg-placement^="left"] &-arrow {
61
+ width: calc(var(--vg-tooltip-arrow-width) / 2);
62
+ clip-path: polygon(0 0, 100% 50%, 0 100%);
63
+ }
64
+
65
+ &[data-vg-placement^="right"] &-arrow {
66
+ width: calc(var(--vg-tooltip-arrow-width) / 2);
67
+ clip-path: polygon(100% 0, 100% 100%, 0 50%);
68
+ }
48
69
 
49
70
  &-popover {
50
71
  .vg-tooltip-inner {
@@ -1,4 +1,6 @@
1
- /**
1
+ /**
2
+ * Описание: управление DOM-событиями VGApp.
3
+ * Возможности: делегирование, пространства имён, одноразовые подписки и границы mouseenter/mouseleave.
2
4
  * --------------------------------------------------------------------------
3
5
  * Bootstrap event.js (рефакторинг)
4
6
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
@@ -79,9 +81,10 @@ const getElementEvents = (element) => {
79
81
  * @param {boolean} isOneOff
80
82
  * @returns {Function}
81
83
  */
82
- const createHandler = (element, fn, isOneOff) => {
83
- const handler = function (event) {
84
- hydrateObj(event, { delegateTarget: element });
84
+ const createHandler = (element, fn, isOneOff, checkRelatedTarget) => {
85
+ const handler = function (event) {
86
+ hydrateObj(event, { delegateTarget: element });
87
+ if (checkRelatedTarget && isRelatedTargetInside(event)) return;
85
88
 
86
89
  if (isOneOff) {
87
90
  EventHandler.off(element, event.type, fn);
@@ -106,13 +109,14 @@ const createHandler = (element, fn, isOneOff) => {
106
109
  * @param {boolean} isOneOff
107
110
  * @returns {Function}
108
111
  */
109
- const createDelegatedHandler = (element, selector, fn, isOneOff) => {
112
+ const createDelegatedHandler = (element, selector, fn, isOneOff, checkRelatedTarget) => {
110
113
  const handler = function (event) {
111
114
  const candidates = Array.from(element.querySelectorAll(selector));
112
115
  for (let target = event.target; target && target !== element; target = target.parentNode) {
113
116
  if (!candidates.includes(target)) continue;
114
117
 
115
- hydrateObj(event, { delegateTarget: target });
118
+ hydrateObj(event, { delegateTarget: target });
119
+ if (checkRelatedTarget && isRelatedTargetInside(event)) return;
116
120
 
117
121
  if (isOneOff) {
118
122
  EventHandler.off(element, event.type, selector, fn);
@@ -131,19 +135,16 @@ const createDelegatedHandler = (element, selector, fn, isOneOff) => {
131
135
  };
132
136
 
133
137
  /**
134
- * Обёртка для mouseenter/mouseleave.
135
- * @param {Function} fn
136
- * @returns {Function}
137
- */
138
- const withRelatedTargetCheck = (fn) => {
139
- return function (event) {
140
- if (
141
- !event.relatedTarget ||
142
- (event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget))
143
- ) {
144
- return fn.call(this, event);
145
- }
146
- };
138
+ * Проверяет переход внутри одного триггера mouseenter/mouseleave.
139
+ * @param {Event} event
140
+ * @returns {boolean}
141
+ */
142
+ const isRelatedTargetInside = (event) => {
143
+ const relatedTarget = event.relatedTarget;
144
+ return !!relatedTarget && (
145
+ relatedTarget === event.delegateTarget ||
146
+ (relatedTarget.nodeType && event.delegateTarget.contains(relatedTarget))
147
+ );
147
148
  };
148
149
 
149
150
  /**
@@ -193,9 +194,10 @@ const addHandler = (element, originalTypeEvent, handler, delegationFunction, one
193
194
  return;
194
195
  }
195
196
 
196
- let fn = isDelegated
197
- ? createDelegatedHandler(element, handler, callable, oneOff)
198
- : createHandler(element, callable, oneOff);
197
+ const checkRelatedTarget = Boolean(customEvents[originalTypeEvent.replace(STRIP_NAME_REGEX, '')]);
198
+ let fn = isDelegated
199
+ ? createDelegatedHandler(element, handler, callable, oneOff, checkRelatedTarget)
200
+ : createHandler(element, callable, oneOff, checkRelatedTarget);
199
201
 
200
202
  const uid = makeEventUid(fn, originalTypeEvent.replace(NAMESPACE_REGEX, ''));
201
203
  fn.uidEvent = uid;
@@ -362,4 +364,4 @@ const EventHandler = {
362
364
  }
363
365
  };
364
366
 
365
- export default EventHandler;
367
+ export default EventHandler;