vgapp 1.5.8 → 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.
@@ -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;