vgapp 1.2.3 → 1.2.5
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 +43 -6
- package/README.md +2 -1
- package/app/modules/vgalert/js/vgalert.js +133 -202
- package/app/modules/vgalert/readme.md +105 -46
- package/app/modules/vgalert/scss/vgalert.scss +18 -0
- package/app/modules/vgmodal/js/vgmodal.js +278 -118
- package/app/modules/vgmodal/js/vgmodal.resize.js +410 -408
- package/app/modules/vgmodal/scss/vgmodal.scss +14 -13
- package/app/modules/vgtoast/js/vgtoast.js +111 -111
- package/app/modules/vgtooltip/index.js +3 -0
- package/app/modules/vgtooltip/js/vgtooltip.js +493 -0
- package/app/modules/vgtooltip/readme.md +181 -0
- package/app/modules/vgtooltip/scss/_variables.scss +15 -0
- package/app/modules/vgtooltip/scss/vgtooltip.scss +64 -0
- package/app/utils/js/components/backdrop.js +17 -23
- package/app/utils/js/components/placement.js +112 -41
- package/build/vgapp.css +3246 -2
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js +30 -2
- package/index.js +18 -17
- package/index.scss +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import BaseModule from "../../base-module";
|
|
2
|
+
import {isDisabled, makeRandomString, mergeDeepObject} from "../../../utils/js/functions";
|
|
3
|
+
import EventHandler from "../../../utils/js/dom/event";
|
|
4
|
+
import Selectors from "../../../utils/js/dom/selectors";
|
|
5
|
+
import Placement from "../../../utils/js/components/placement";
|
|
6
|
+
|
|
7
|
+
const NAME = 'tooltip';
|
|
8
|
+
const NAME_KEY = 'vg.tooltip';
|
|
9
|
+
|
|
10
|
+
const SELECTOR_DATA_TOGGLE = '[data-vg-toggle="tooltip"]';
|
|
11
|
+
const SELECTOR_DATA_TOGGLE_POPOVER = '[data-vg-toggle="popover"]';
|
|
12
|
+
|
|
13
|
+
const CLASS_NAME_OPEN = 'vg-tooltip-open';
|
|
14
|
+
const CLASS_NAME_SHOW = 'show';
|
|
15
|
+
const CLASS_NAME_SHOWN = 'shown';
|
|
16
|
+
|
|
17
|
+
const SELECTOR_OPEN_TRIGGER = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}, ${SELECTOR_DATA_TOGGLE_POPOVER}.${CLASS_NAME_SHOW}`;
|
|
18
|
+
|
|
19
|
+
const EVENT_KEY_HIDE = `${NAME_KEY}.hide`;
|
|
20
|
+
const EVENT_KEY_HIDDEN = `${NAME_KEY}.hidden`;
|
|
21
|
+
const EVENT_KEY_SHOW = `${NAME_KEY}.show`;
|
|
22
|
+
const EVENT_KEY_SHOWN = `${NAME_KEY}.shown`;
|
|
23
|
+
|
|
24
|
+
const EVENT_KEY_CLICK_DATA_API = `click.${NAME_KEY}.data.api`;
|
|
25
|
+
const EVENT_KEY_MOUSEENTER = `mouseenter.${NAME_KEY}`;
|
|
26
|
+
const EVENT_KEY_MOUSELEAVE = `mouseleave.${NAME_KEY}`;
|
|
27
|
+
const EVENT_KEY_CLICK_DISMISS = `click.dismiss.${NAME_KEY}`;
|
|
28
|
+
const EVENT_KEY_KEYDOWN = `keydown.dismiss.${NAME_KEY}`;
|
|
29
|
+
|
|
30
|
+
const PLACEMENT_CLASSES = [
|
|
31
|
+
'top',
|
|
32
|
+
'top-start',
|
|
33
|
+
'top-end',
|
|
34
|
+
'bottom',
|
|
35
|
+
'bottom-start',
|
|
36
|
+
'bottom-end',
|
|
37
|
+
'left',
|
|
38
|
+
'left-start',
|
|
39
|
+
'left-end',
|
|
40
|
+
'right',
|
|
41
|
+
'right-start',
|
|
42
|
+
'right-end'
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const defaultParams = {
|
|
46
|
+
title: '',
|
|
47
|
+
content: '',
|
|
48
|
+
trigger: 'hover',
|
|
49
|
+
placement: 'top',
|
|
50
|
+
container: 'body',
|
|
51
|
+
popover: false,
|
|
52
|
+
html: false,
|
|
53
|
+
offset: [8, 8],
|
|
54
|
+
autoFlip: true,
|
|
55
|
+
overflowProtection: true,
|
|
56
|
+
fallbackPlacements: ['bottom', 'right', 'left'],
|
|
57
|
+
closeOnOutsideClick: true,
|
|
58
|
+
keyboard: true,
|
|
59
|
+
closeOther: true,
|
|
60
|
+
delay: {
|
|
61
|
+
show: 100,
|
|
62
|
+
hide: 100
|
|
63
|
+
},
|
|
64
|
+
arrow: {
|
|
65
|
+
padding: 8
|
|
66
|
+
},
|
|
67
|
+
custom: {
|
|
68
|
+
class: ''
|
|
69
|
+
},
|
|
70
|
+
animation: {
|
|
71
|
+
enable: true,
|
|
72
|
+
in: 'animate__backInUp',
|
|
73
|
+
out: 'animate__backOutDown',
|
|
74
|
+
delay: 300,
|
|
75
|
+
effect: 'none',
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
class VGTooltip extends BaseModule {
|
|
80
|
+
constructor(element, params = {}) {
|
|
81
|
+
super(element, params);
|
|
82
|
+
|
|
83
|
+
this._params = this._getParams(element, mergeDeepObject(defaultParams, params));
|
|
84
|
+
this._tooltip = null;
|
|
85
|
+
this._isHiding = false;
|
|
86
|
+
this._showTimeout = null;
|
|
87
|
+
this._hideTimeout = null;
|
|
88
|
+
|
|
89
|
+
this._fixTitle();
|
|
90
|
+
|
|
91
|
+
this._handleUpdate = () => {
|
|
92
|
+
if (this._isShown()) {
|
|
93
|
+
this._setPlacement();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static get NAME() {
|
|
99
|
+
return NAME;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static get NAME_KEY() {
|
|
103
|
+
return NAME_KEY;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
toggle(relatedTarget) {
|
|
107
|
+
return this._isShown() ? this.hide() : this.show(relatedTarget);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
show(relatedTarget) {
|
|
111
|
+
if (isDisabled(this._element) || this._isShown()) return;
|
|
112
|
+
|
|
113
|
+
this._clearTimeouts();
|
|
114
|
+
|
|
115
|
+
this._showTimeout = setTimeout(() => {
|
|
116
|
+
this._showTimeout = null;
|
|
117
|
+
|
|
118
|
+
const showEvent = EventHandler.trigger(this._element, EVENT_KEY_SHOW, { relatedTarget });
|
|
119
|
+
if (showEvent.defaultPrevented) return;
|
|
120
|
+
|
|
121
|
+
if (this._params.closeOther) {
|
|
122
|
+
this._hideOpenedTooltips();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this._isHiding = false;
|
|
126
|
+
this._createTooltip();
|
|
127
|
+
|
|
128
|
+
if (!this._tooltip) return;
|
|
129
|
+
|
|
130
|
+
this._tooltip.classList.remove(CLASS_NAME_SHOWN);
|
|
131
|
+
this._tooltip.classList.add(CLASS_NAME_SHOW);
|
|
132
|
+
this._element.classList.add(CLASS_NAME_SHOW);
|
|
133
|
+
document.body.classList.add(CLASS_NAME_OPEN);
|
|
134
|
+
|
|
135
|
+
this._addWindowListeners();
|
|
136
|
+
|
|
137
|
+
requestAnimationFrame(() => {
|
|
138
|
+
if (!this._tooltip) return;
|
|
139
|
+
|
|
140
|
+
this._setPlacement();
|
|
141
|
+
|
|
142
|
+
this._queueCallback(() => {
|
|
143
|
+
if (!this._tooltip || this._isHiding) return;
|
|
144
|
+
|
|
145
|
+
this._tooltip.classList.add(CLASS_NAME_SHOWN);
|
|
146
|
+
EventHandler.trigger(this._element, EVENT_KEY_SHOWN, { relatedTarget });
|
|
147
|
+
}, this._tooltip, true, this._params.animation.delay);
|
|
148
|
+
});
|
|
149
|
+
}, this._params.delay.show);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
hide() {
|
|
153
|
+
if (!this._isShown() || this._isHiding || !this._tooltip) return;
|
|
154
|
+
|
|
155
|
+
this._clearTimeouts();
|
|
156
|
+
|
|
157
|
+
this._hideTimeout = setTimeout(() => {
|
|
158
|
+
this._hideTimeout = null;
|
|
159
|
+
|
|
160
|
+
const hideEvent = EventHandler.trigger(this._element, EVENT_KEY_HIDE);
|
|
161
|
+
if (hideEvent.defaultPrevented) return;
|
|
162
|
+
|
|
163
|
+
this._removeWindowListeners();
|
|
164
|
+
|
|
165
|
+
this._isHiding = true;
|
|
166
|
+
this._tooltip.classList.remove(CLASS_NAME_SHOWN);
|
|
167
|
+
|
|
168
|
+
this._queueCallback(() => {
|
|
169
|
+
if (!this._tooltip) return;
|
|
170
|
+
|
|
171
|
+
this._tooltip.classList.remove(CLASS_NAME_SHOW);
|
|
172
|
+
this._element.classList.remove(CLASS_NAME_SHOW);
|
|
173
|
+
this._element.removeAttribute('aria-describedby');
|
|
174
|
+
|
|
175
|
+
this._tooltip.remove();
|
|
176
|
+
this._tooltip = null;
|
|
177
|
+
this._isHiding = false;
|
|
178
|
+
|
|
179
|
+
if (!Selectors.find('.vg-tooltip.show')) {
|
|
180
|
+
document.body.classList.remove(CLASS_NAME_OPEN);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
EventHandler.trigger(this._element, EVENT_KEY_HIDDEN);
|
|
184
|
+
}, this._tooltip, false, this._params.animation.delay);
|
|
185
|
+
}, this._params.delay.hide);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
dispose() {
|
|
189
|
+
this._clearTimeouts();
|
|
190
|
+
this._removeWindowListeners();
|
|
191
|
+
|
|
192
|
+
if (this._tooltip) {
|
|
193
|
+
this._tooltip.remove();
|
|
194
|
+
this._tooltip = null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const originalTitle = this._element.dataset.vgOriginalTitle;
|
|
198
|
+
if (originalTitle) {
|
|
199
|
+
this._element.setAttribute('title', originalTitle);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
super.dispose();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_isShown() {
|
|
206
|
+
return !!this._element && this._element.classList.contains(CLASS_NAME_SHOW);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_createTooltip() {
|
|
210
|
+
if (this._tooltip) return;
|
|
211
|
+
|
|
212
|
+
const title = this._getTitle();
|
|
213
|
+
if (!title) return;
|
|
214
|
+
|
|
215
|
+
const id = `vg-tooltip-${makeRandomString()}`;
|
|
216
|
+
const tooltip = document.createElement('div');
|
|
217
|
+
|
|
218
|
+
tooltip.id = id;
|
|
219
|
+
tooltip.className = `vg-tooltip ${this._params.placement} ${this._params.custom.class || ''}`;
|
|
220
|
+
tooltip.setAttribute('role', this._params.popover ? 'dialog' : 'tooltip');
|
|
221
|
+
|
|
222
|
+
if (this._params.popover) {
|
|
223
|
+
tooltip.classList.add('vg-tooltip-popover');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const inner = document.createElement('div');
|
|
227
|
+
inner.classList.add('vg-tooltip-inner');
|
|
228
|
+
|
|
229
|
+
if (this._params.content) {
|
|
230
|
+
const titleBlock = document.createElement('div');
|
|
231
|
+
titleBlock.classList.add('vg-tooltip-inner--title');
|
|
232
|
+
|
|
233
|
+
const contentBlock = document.createElement('div');
|
|
234
|
+
contentBlock.classList.add('vg-tooltip-inner--content');
|
|
235
|
+
|
|
236
|
+
if (this._params.html) {
|
|
237
|
+
titleBlock.innerHTML = title;
|
|
238
|
+
contentBlock.innerHTML = this._params.content;
|
|
239
|
+
} else {
|
|
240
|
+
titleBlock.textContent = title;
|
|
241
|
+
contentBlock.textContent = this._params.content;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
inner.append(titleBlock);
|
|
245
|
+
inner.append(contentBlock);
|
|
246
|
+
} else {
|
|
247
|
+
if (this._params.html) {
|
|
248
|
+
inner.innerHTML = title;
|
|
249
|
+
} else {
|
|
250
|
+
inner.textContent = title;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const arrow = document.createElement('div');
|
|
255
|
+
arrow.classList.add('vg-tooltip-arrow');
|
|
256
|
+
|
|
257
|
+
inner.append(arrow);
|
|
258
|
+
tooltip.append(inner);
|
|
259
|
+
|
|
260
|
+
const container = this._params.container === 'body'
|
|
261
|
+
? document.body
|
|
262
|
+
: Selectors.find(this._params.container);
|
|
263
|
+
|
|
264
|
+
if (!container) return;
|
|
265
|
+
|
|
266
|
+
container.append(tooltip);
|
|
267
|
+
|
|
268
|
+
this._element.setAttribute('aria-describedby', id);
|
|
269
|
+
this._tooltip = tooltip;
|
|
270
|
+
|
|
271
|
+
this._animation(this._tooltip, VGTooltip.NAME_KEY, this._params.animation);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_getTitle() {
|
|
275
|
+
return (
|
|
276
|
+
this._params.title ||
|
|
277
|
+
this._element.dataset.vgTitle ||
|
|
278
|
+
this._element.dataset.vgOriginalTitle ||
|
|
279
|
+
''
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_fixTitle() {
|
|
284
|
+
const title = this._element.getAttribute('title');
|
|
285
|
+
|
|
286
|
+
if (!title) return;
|
|
287
|
+
|
|
288
|
+
this._element.setAttribute('data-vg-original-title', title);
|
|
289
|
+
this._element.removeAttribute('title');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_setPlacement() {
|
|
293
|
+
if (!this._tooltip) return;
|
|
294
|
+
|
|
295
|
+
const placement = new Placement({
|
|
296
|
+
reference: this._element,
|
|
297
|
+
drop: this._tooltip,
|
|
298
|
+
placement: this._params.placement,
|
|
299
|
+
offset: this._params.offset,
|
|
300
|
+
autoFlip: this._params.autoFlip,
|
|
301
|
+
overflowProtection: this._params.overflowProtection,
|
|
302
|
+
fallbackPlacements: this._params.fallbackPlacements,
|
|
303
|
+
clamp: true,
|
|
304
|
+
isMerge: false
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
placement._setPlacement();
|
|
308
|
+
|
|
309
|
+
this._syncPlacementClass();
|
|
310
|
+
this._setArrowPlacement();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
_setArrowPlacement() {
|
|
314
|
+
if (!this._tooltip) return;
|
|
315
|
+
|
|
316
|
+
const inner = this._tooltip.querySelector('.vg-tooltip-inner');
|
|
317
|
+
const arrow = this._tooltip.querySelector('.vg-tooltip-arrow');
|
|
318
|
+
|
|
319
|
+
if (!inner || !arrow) return;
|
|
320
|
+
|
|
321
|
+
const placement = this._tooltip.getAttribute('data-vg-placement') || this._params.placement;
|
|
322
|
+
|
|
323
|
+
const triggerRect = this._element.getBoundingClientRect();
|
|
324
|
+
const innerRect = inner.getBoundingClientRect();
|
|
325
|
+
|
|
326
|
+
const triggerCenterX = triggerRect.left + triggerRect.width / 2;
|
|
327
|
+
const triggerCenterY = triggerRect.top + triggerRect.height / 2;
|
|
328
|
+
|
|
329
|
+
const arrowPadding = this._params.arrow.padding;
|
|
330
|
+
|
|
331
|
+
arrow.style.left = '';
|
|
332
|
+
arrow.style.right = '';
|
|
333
|
+
arrow.style.top = '';
|
|
334
|
+
arrow.style.bottom = '';
|
|
335
|
+
|
|
336
|
+
if (placement.startsWith('top') || placement.startsWith('bottom')) {
|
|
337
|
+
let arrowLeft = triggerCenterX - innerRect.left;
|
|
338
|
+
|
|
339
|
+
arrowLeft = Math.max(
|
|
340
|
+
arrowPadding,
|
|
341
|
+
Math.min(arrowLeft, innerRect.width - arrowPadding)
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
arrow.style.left = `${arrowLeft}px`;
|
|
345
|
+
arrow.style.transform = 'translateX(-50%) rotate(45deg)';
|
|
346
|
+
|
|
347
|
+
if (placement.startsWith('top')) {
|
|
348
|
+
arrow.style.bottom = '-4px';
|
|
349
|
+
} else {
|
|
350
|
+
arrow.style.top = '-4px';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (placement.startsWith('left') || placement.startsWith('right')) {
|
|
355
|
+
let arrowTop = triggerCenterY - innerRect.top;
|
|
356
|
+
|
|
357
|
+
arrowTop = Math.max(
|
|
358
|
+
arrowPadding,
|
|
359
|
+
Math.min(arrowTop, innerRect.height - arrowPadding)
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
arrow.style.top = `${arrowTop}px`;
|
|
363
|
+
arrow.style.transform = 'translateY(-50%) rotate(45deg)';
|
|
364
|
+
|
|
365
|
+
if (placement.startsWith('left')) {
|
|
366
|
+
arrow.style.right = '-4px';
|
|
367
|
+
} else {
|
|
368
|
+
arrow.style.left = '-4px';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
_syncPlacementClass() {
|
|
374
|
+
if (!this._tooltip) return;
|
|
375
|
+
|
|
376
|
+
const placement = this._tooltip.getAttribute('data-vg-placement');
|
|
377
|
+
|
|
378
|
+
this._tooltip.classList.remove(...PLACEMENT_CLASSES);
|
|
379
|
+
|
|
380
|
+
if (placement) {
|
|
381
|
+
this._tooltip.classList.add(placement);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
_hideOpenedTooltips() {
|
|
386
|
+
Selectors.findAll('.vg-tooltip.show').forEach(tooltip => {
|
|
387
|
+
const id = tooltip.id;
|
|
388
|
+
const trigger = Selectors.find(`[aria-describedby="${id}"]`);
|
|
389
|
+
|
|
390
|
+
if (!trigger || trigger === this._element) return;
|
|
391
|
+
|
|
392
|
+
const instance = VGTooltip.getInstance(trigger);
|
|
393
|
+
instance?.hide();
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
_addWindowListeners() {
|
|
398
|
+
window.addEventListener('resize', this._handleUpdate);
|
|
399
|
+
window.addEventListener('scroll', this._handleUpdate, true);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
_removeWindowListeners() {
|
|
403
|
+
window.removeEventListener('resize', this._handleUpdate);
|
|
404
|
+
window.removeEventListener('scroll', this._handleUpdate, true);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
_clearTimeouts() {
|
|
408
|
+
if (this._showTimeout) {
|
|
409
|
+
clearTimeout(this._showTimeout);
|
|
410
|
+
this._showTimeout = null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (this._hideTimeout) {
|
|
414
|
+
clearTimeout(this._hideTimeout);
|
|
415
|
+
this._hideTimeout = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const getShownInstances = () => {
|
|
421
|
+
return Selectors.findAll(SELECTOR_OPEN_TRIGGER)
|
|
422
|
+
.map(element => VGTooltip.getInstance(element))
|
|
423
|
+
.filter(Boolean);
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
EventHandler.on(document, EVENT_KEY_KEYDOWN, event => {
|
|
427
|
+
if (event.key !== 'Escape') return;
|
|
428
|
+
|
|
429
|
+
getShownInstances().forEach(instance => {
|
|
430
|
+
if (!instance._params.keyboard || !instance._isShown()) return;
|
|
431
|
+
|
|
432
|
+
instance.hide();
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
EventHandler.on(document, EVENT_KEY_CLICK_DISMISS, event => {
|
|
437
|
+
getShownInstances().forEach(instance => {
|
|
438
|
+
if (!instance._params.closeOnOutsideClick) return;
|
|
439
|
+
if (!instance._params.trigger.includes('click') && !instance._params.popover) return;
|
|
440
|
+
if (!instance._isShown()) return;
|
|
441
|
+
|
|
442
|
+
if (
|
|
443
|
+
instance._tooltip?.contains(event.target) ||
|
|
444
|
+
instance._element.contains(event.target)
|
|
445
|
+
) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
instance.hide();
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
EventHandler.on(document, EVENT_KEY_MOUSEENTER, SELECTOR_DATA_TOGGLE, function () {
|
|
454
|
+
const instance = VGTooltip.getOrCreateInstance(this);
|
|
455
|
+
|
|
456
|
+
if (instance._params.trigger.includes('hover')) {
|
|
457
|
+
instance.show(this);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
EventHandler.on(document, EVENT_KEY_MOUSELEAVE, SELECTOR_DATA_TOGGLE, function () {
|
|
462
|
+
const instance = VGTooltip.getOrCreateInstance(this);
|
|
463
|
+
|
|
464
|
+
if (instance._params.trigger.includes('hover')) {
|
|
465
|
+
instance.hide();
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
|
|
470
|
+
const instance = VGTooltip.getOrCreateInstance(this);
|
|
471
|
+
|
|
472
|
+
if (instance._params.popover) {
|
|
473
|
+
instance._params.trigger = 'click';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (instance._params.trigger.includes('click')) {
|
|
477
|
+
event.preventDefault();
|
|
478
|
+
instance.toggle(this);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
EventHandler.on(document, EVENT_KEY_CLICK_DATA_API, SELECTOR_DATA_TOGGLE_POPOVER, function (event) {
|
|
483
|
+
event.preventDefault();
|
|
484
|
+
|
|
485
|
+
const instance = VGTooltip.getOrCreateInstance(this);
|
|
486
|
+
|
|
487
|
+
instance._params.popover = true;
|
|
488
|
+
instance._params.trigger = 'click';
|
|
489
|
+
|
|
490
|
+
instance.toggle(this);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
export default VGTooltip;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# VGTooltip — модуль подсказок и popover
|
|
2
|
+
|
|
3
|
+
`VGTooltip` — модуль всплывающих подсказок для Vegas App. Поддерживает tooltip и popover режимы, Data API и JS API, автопозиционирование, анимации и управление через события.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✅ Возможности
|
|
8
|
+
|
|
9
|
+
- 📦 **Легковесный и автономный** — работает как модуль без внешнего UI-фреймворка.
|
|
10
|
+
- 🎯 **Два режима работы** — обычный tooltip и `popover` с заголовком и контентом.
|
|
11
|
+
- 🧭 **Умное позиционирование** — поддерживает `top`, `bottom`, `left`, `right` и авто-переворот.
|
|
12
|
+
- 🖱 **Гибкие триггеры** — `hover`, `click` или их комбинация.
|
|
13
|
+
- 🔁 **Автозакрытие других подсказок** — при `closeOther: true`.
|
|
14
|
+
- ⌨ **Поддержка `Esc`** и закрытия по клику вне элемента.
|
|
15
|
+
- 🎬 **Анимации показа и скрытия** через общий animation API.
|
|
16
|
+
- 📱 **Адаптивное поведение** — перерасчёт позиции при `resize` и `scroll`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 📦 Установка
|
|
21
|
+
|
|
22
|
+
Модуль подключается как часть системы модулей. Для работы должны быть доступны:
|
|
23
|
+
- `BaseModule`
|
|
24
|
+
- `EventHandler`
|
|
25
|
+
- `Selectors`
|
|
26
|
+
- `Placement`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🧰 Быстрое использование
|
|
31
|
+
|
|
32
|
+
### Через JavaScript
|
|
33
|
+
```js
|
|
34
|
+
const button = document.querySelector('#tooltip-demo');
|
|
35
|
+
|
|
36
|
+
const tooltip = new VGTooltip(button, {
|
|
37
|
+
title: 'Подсказка',
|
|
38
|
+
placement: 'right'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
tooltip.show();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Через Data API
|
|
45
|
+
```html
|
|
46
|
+
<button
|
|
47
|
+
data-vg-toggle="tooltip"
|
|
48
|
+
data-vg-title="Текст подсказки">
|
|
49
|
+
Наведи на меня
|
|
50
|
+
</button>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Popover
|
|
54
|
+
```html
|
|
55
|
+
<button
|
|
56
|
+
data-vg-toggle="popover"
|
|
57
|
+
data-vg-title="Заголовок"
|
|
58
|
+
data-vg-content="Подробный текст">
|
|
59
|
+
Открыть popover
|
|
60
|
+
</button>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ⚙️ Параметры
|
|
66
|
+
|
|
67
|
+
| Параметр | Тип | По умолчанию | Описание |
|
|
68
|
+
|--------|------|-------------|--------|
|
|
69
|
+
| `title` | `string` | `''` | Заголовок tooltip/popover |
|
|
70
|
+
| `content` | `string` | `''` | Контент для режима `popover` |
|
|
71
|
+
| `trigger` | `string` | `'hover'` | Триггер показа: `hover`, `click` или комбинация |
|
|
72
|
+
| `placement` | `string` | `'top'` | Позиция: `top`, `bottom`, `left`, `right` и варианты |
|
|
73
|
+
| `container` | `string` | `'body'` | Контейнер, в который рендерится tooltip |
|
|
74
|
+
| `popover` | `boolean` | `false` | Режим popover |
|
|
75
|
+
| `html` | `boolean` | `false` | Разрешить HTML в `title` и `content` |
|
|
76
|
+
| `offset` | `array` | `[8, 8]` | Смещение относительно элемента |
|
|
77
|
+
| `autoFlip` | `boolean` | `true` | Автоматически менять сторону при нехватке места |
|
|
78
|
+
| `overflowProtection` | `boolean` | `true` | Защита от выхода за границы окна |
|
|
79
|
+
| `fallbackPlacements` | `array` | `['bottom', 'right', 'left']` | Порядок запасных позиций |
|
|
80
|
+
| `closeOnOutsideClick` | `boolean` | `true` | Закрывать по клику вне tooltip |
|
|
81
|
+
| `keyboard` | `boolean` | `true` | Закрывать по `Esc` |
|
|
82
|
+
| `closeOther` | `boolean` | `true` | Скрывать другие открытые tooltip |
|
|
83
|
+
| `delay.show` | `number` | `100` | Задержка перед показом, мс |
|
|
84
|
+
| `delay.hide` | `number` | `100` | Задержка перед скрытием, мс |
|
|
85
|
+
| `arrow.padding` | `number` | `8` | Отступ стрелки от краёв |
|
|
86
|
+
| `custom.class` | `string` | `''` | Дополнительный CSS-класс |
|
|
87
|
+
| `animation.enable` | `boolean` | `true` | Включить анимации |
|
|
88
|
+
| `animation.in` | `string` | `'animate__backInUp'` | Анимация появления |
|
|
89
|
+
| `animation.out` | `string` | `'animate__backOutDown'` | Анимация скрытия |
|
|
90
|
+
| `animation.delay` | `number` | `300` | Длительность анимации, мс |
|
|
91
|
+
| `animation.effect` | `string` | `'none'` | Дополнительный CSS-эффект |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 💡 Расширенные примеры
|
|
96
|
+
|
|
97
|
+
### 1. Tooltip по клику
|
|
98
|
+
```js
|
|
99
|
+
new VGTooltip(document.querySelector('#help'), {
|
|
100
|
+
title: 'Подсказка по клику',
|
|
101
|
+
trigger: 'click',
|
|
102
|
+
placement: 'bottom'
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. HTML-контент
|
|
107
|
+
```js
|
|
108
|
+
new VGTooltip(document.querySelector('#html-tooltip'), {
|
|
109
|
+
title: '<strong>Важно</strong>',
|
|
110
|
+
content: 'Можно передавать <em>HTML</em>',
|
|
111
|
+
html: true,
|
|
112
|
+
popover: true,
|
|
113
|
+
trigger: 'click'
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. Кастомный контейнер и класс
|
|
118
|
+
```js
|
|
119
|
+
new VGTooltip(document.querySelector('#custom-tooltip'), {
|
|
120
|
+
title: 'Подсказка внутри контейнера',
|
|
121
|
+
container: '.demo-area',
|
|
122
|
+
custom: {
|
|
123
|
+
class: 'tooltip-accent'
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🧩 Data API
|
|
131
|
+
|
|
132
|
+
- `data-vg-toggle="tooltip"` — обычная подсказка.
|
|
133
|
+
- `data-vg-toggle="popover"` — popover-режим.
|
|
134
|
+
- `data-vg-title` — текст заголовка.
|
|
135
|
+
- `data-vg-content` — текст контента.
|
|
136
|
+
- Если у элемента есть `title`, модуль переносит его в `data-vg-original-title` и использует как источник текста.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🧪 События
|
|
141
|
+
|
|
142
|
+
| Событие | Описание |
|
|
143
|
+
|-------|--------|
|
|
144
|
+
| `vg.tooltip.show` | Перед показом |
|
|
145
|
+
| `vg.tooltip.shown` | После показа |
|
|
146
|
+
| `vg.tooltip.hide` | Перед скрытием |
|
|
147
|
+
| `vg.tooltip.hidden` | После скрытия |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 🔄 Методы
|
|
152
|
+
|
|
153
|
+
| Метод | Описание |
|
|
154
|
+
|------|--------|
|
|
155
|
+
| `VGTooltip.getInstance(element)` | Получить существующий экземпляр |
|
|
156
|
+
| `VGTooltip.getOrCreateInstance(element, params)` | Получить или создать экземпляр |
|
|
157
|
+
| `tooltip.show(relatedTarget)` | Показать tooltip |
|
|
158
|
+
| `tooltip.hide()` | Скрыть tooltip |
|
|
159
|
+
| `tooltip.toggle(relatedTarget)` | Переключить состояние |
|
|
160
|
+
| `tooltip.dispose()` | Удалить tooltip и очистить обработчики |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 🌐 Глобальные классы
|
|
165
|
+
|
|
166
|
+
- `.vg-tooltip-open` — добавляется к `<body>`, пока открыт хотя бы один tooltip.
|
|
167
|
+
- `.vg-tooltip.show` — tooltip находится в DOM и видим.
|
|
168
|
+
- `.vg-tooltip.shown` — tooltip завершил фазу показа.
|
|
169
|
+
- `.vg-tooltip-popover` — оформление для режима popover.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 📝 Лицензия
|
|
174
|
+
|
|
175
|
+
MIT. Свободно использовать и модифицировать.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
📌 *Разработано в рамках фронтенд-системы VG Modules.*
|
|
180
|
+
> 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
|
|
181
|
+
> 📍 Поддерживается в проектах VEGAS
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
$tooltip-map: (
|
|
2
|
+
zindex: 1080,
|
|
3
|
+
max-width: 260px,
|
|
4
|
+
padding: 8px 12px,
|
|
5
|
+
font-size: 13px,
|
|
6
|
+
line-height: 1.4,
|
|
7
|
+
color: #fff,
|
|
8
|
+
background: rgba(30, 30, 30, .95),
|
|
9
|
+
border-radius: 6px,
|
|
10
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, .18),
|
|
11
|
+
word-break: break-word,
|
|
12
|
+
arrow-width: 8px,
|
|
13
|
+
arrow-height: 8px,
|
|
14
|
+
border-color: $border-color,
|
|
15
|
+
);
|