zero-tooltip 1.3.1 → 1.4.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/README.md +150 -148
- package/dist/composables/useHideOnResize.d.ts +2 -2
- package/dist/composables/useHideOnScroll.d.ts +1 -0
- package/dist/types/scrollContainer.d.ts +5 -0
- package/dist/types/tooltipLocalConfig.d.ts +1 -0
- package/dist/zero-tooltip.js +198 -181
- package/dist/zero-tooltip.umd.cjs +1 -1
- package/package.json +65 -65
- package/src/composables/useHideOnResize.ts +38 -36
- package/src/composables/useHideOnScroll.ts +60 -49
- package/src/index.ts +27 -27
- package/src/tooltip.ts +626 -576
- package/src/types/scrollContainer.ts +6 -0
- package/src/types/tooltipConfig.ts +23 -23
- package/src/types/tooltipLocalConfig.ts +8 -7
- package/src/types/tooltipPosition.ts +4 -4
- package/src/types/tooltipPositions.ts +10 -10
package/src/tooltip.ts
CHANGED
|
@@ -1,576 +1,626 @@
|
|
|
1
|
-
import { Directive, isReactive, watch } from "vue"
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid'
|
|
3
|
-
import TooltipConfig from "./types/tooltipConfig"
|
|
4
|
-
import TooltipPosition from "./types/tooltipPosition"
|
|
5
|
-
import TooltipPositions from "./types/tooltipPositions"
|
|
6
|
-
import TooltipLocalConfig from "./types/tooltipLocalConfig"
|
|
7
|
-
import useHideOnScroll from './composables/useHideOnScroll'
|
|
8
|
-
import useHideOnResize from "./composables/useHideOnResize"
|
|
9
|
-
|
|
10
|
-
const { handleHideOnScroll } = useHideOnScroll()
|
|
11
|
-
const { handleHideOnResize, resetResizeReferences } = useHideOnResize()
|
|
12
|
-
|
|
13
|
-
const tooltipElementClass = 'zero-tooltip__container'
|
|
14
|
-
const textElementClass = 'zero-tooltip__text'
|
|
15
|
-
const arrowElementClass = 'zero-tooltip__arrow'
|
|
16
|
-
|
|
17
|
-
// For each TooltipPosition define sequence of positions that will be checked when determining where to render Tooltip
|
|
18
|
-
// Meant as fallback positions in case Tooltip do not have enough space in originally set position
|
|
19
|
-
const defaultTooltipPositions: TooltipPositions = {
|
|
20
|
-
left: ['left', 'right', 'top', 'bottom'],
|
|
21
|
-
top: ['top', 'bottom', 'right', 'left'],
|
|
22
|
-
right: ['right', 'left', 'top', 'bottom'],
|
|
23
|
-
bottom: ['bottom', 'top', 'right', 'left'],
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const defaultAppendTo: string = 'body'
|
|
27
|
-
const defaultTooltipPosition: TooltipPosition = 'top'
|
|
28
|
-
const defaultTooltipOffsetFromSource = 10
|
|
29
|
-
const defaultTooltipOffsetFromViewport = 20
|
|
30
|
-
const defaultTooltipMinWidth = 100
|
|
31
|
-
const defaultTooltipMaxWidth = 250
|
|
32
|
-
const defaultTooltipBorderWidth = 0
|
|
33
|
-
const defaultTooltipClasses = 'zt-fixed zt-opacity-0 zt-inline-block zt-w-fit zt-py-1.5 zt-px-2.5 zt-rounded-md zt-bg-[#495057] zt-shadow-[0_2px_12px_0_rgba(0,0,0,0.1)] zt-box-border'
|
|
34
|
-
const defaultTextClasses = 'zt-text-sm zt-text-white zt-whitespace-pre-wrap zt-break-words'
|
|
35
|
-
const defaultArrowSize = 5
|
|
36
|
-
const defaultArrowClasses = 'zt-absolute zt-border-solid zt-border-[#495057]'
|
|
37
|
-
const defaultMinArrowOffsetFromTooltipCorner = 6
|
|
38
|
-
const defaultZIndex = 1
|
|
39
|
-
const defaultShouldShow = true
|
|
40
|
-
const defaultShowDelay = 0
|
|
41
|
-
const defaultHideDelay = 0
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
let
|
|
99
|
-
let
|
|
100
|
-
let
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
let
|
|
108
|
-
let
|
|
109
|
-
let
|
|
110
|
-
let
|
|
111
|
-
let
|
|
112
|
-
let
|
|
113
|
-
let
|
|
114
|
-
let
|
|
115
|
-
let
|
|
116
|
-
let
|
|
117
|
-
let
|
|
118
|
-
let
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (localConfig.
|
|
127
|
-
|
|
128
|
-
if (localConfig.positions?.
|
|
129
|
-
if (localConfig.positions?.
|
|
130
|
-
|
|
131
|
-
if (localConfig.
|
|
132
|
-
|
|
133
|
-
if (localConfig.
|
|
134
|
-
if (localConfig.
|
|
135
|
-
if (localConfig.
|
|
136
|
-
if (localConfig.
|
|
137
|
-
if (localConfig.
|
|
138
|
-
if (localConfig.
|
|
139
|
-
if (localConfig.
|
|
140
|
-
if (localConfig.
|
|
141
|
-
if (localConfig.
|
|
142
|
-
if (localConfig.
|
|
143
|
-
if (localConfig.
|
|
144
|
-
if (localConfig.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
anchorElement,
|
|
208
|
-
|
|
209
|
-
tooltipElement,
|
|
210
|
-
mouseEnterEventControllers
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
tooltipTop =
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
case "
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
case "right":
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
1
|
+
import { Directive, isReactive, watch } from "vue"
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
3
|
+
import TooltipConfig from "./types/tooltipConfig"
|
|
4
|
+
import TooltipPosition from "./types/tooltipPosition"
|
|
5
|
+
import TooltipPositions from "./types/tooltipPositions"
|
|
6
|
+
import TooltipLocalConfig from "./types/tooltipLocalConfig"
|
|
7
|
+
import useHideOnScroll from './composables/useHideOnScroll'
|
|
8
|
+
import useHideOnResize from "./composables/useHideOnResize"
|
|
9
|
+
|
|
10
|
+
const { handleHideOnScroll, removeHideOnScrollListeners } = useHideOnScroll()
|
|
11
|
+
const { handleHideOnResize, resetResizeReferences } = useHideOnResize()
|
|
12
|
+
|
|
13
|
+
const tooltipElementClass = 'zero-tooltip__container'
|
|
14
|
+
const textElementClass = 'zero-tooltip__text'
|
|
15
|
+
const arrowElementClass = 'zero-tooltip__arrow'
|
|
16
|
+
|
|
17
|
+
// For each TooltipPosition define sequence of positions that will be checked when determining where to render Tooltip
|
|
18
|
+
// Meant as fallback positions in case Tooltip do not have enough space in originally set position
|
|
19
|
+
const defaultTooltipPositions: TooltipPositions = {
|
|
20
|
+
left: ['left', 'right', 'top', 'bottom'],
|
|
21
|
+
top: ['top', 'bottom', 'right', 'left'],
|
|
22
|
+
right: ['right', 'left', 'top', 'bottom'],
|
|
23
|
+
bottom: ['bottom', 'top', 'right', 'left'],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const defaultAppendTo: string = 'body'
|
|
27
|
+
const defaultTooltipPosition: TooltipPosition = 'top'
|
|
28
|
+
const defaultTooltipOffsetFromSource = 10
|
|
29
|
+
const defaultTooltipOffsetFromViewport = 20
|
|
30
|
+
const defaultTooltipMinWidth = 100
|
|
31
|
+
const defaultTooltipMaxWidth = 250
|
|
32
|
+
const defaultTooltipBorderWidth = 0
|
|
33
|
+
const defaultTooltipClasses = 'zt-fixed zt-opacity-0 zt-inline-block zt-w-fit zt-py-1.5 zt-px-2.5 zt-rounded-md zt-bg-[#495057] zt-shadow-[0_2px_12px_0_rgba(0,0,0,0.1)] zt-box-border'
|
|
34
|
+
const defaultTextClasses = 'zt-text-sm zt-text-white zt-whitespace-pre-wrap zt-break-words'
|
|
35
|
+
const defaultArrowSize = 5
|
|
36
|
+
const defaultArrowClasses = 'zt-absolute zt-border-solid zt-border-[#495057]'
|
|
37
|
+
const defaultMinArrowOffsetFromTooltipCorner = 6
|
|
38
|
+
const defaultZIndex = 1
|
|
39
|
+
const defaultShouldShow = true
|
|
40
|
+
const defaultShowDelay = 0
|
|
41
|
+
const defaultHideDelay = 0
|
|
42
|
+
const defaultAlwaysOn = false
|
|
43
|
+
|
|
44
|
+
const tooltips: {[key: string]: ReturnType<typeof initTooltip>} = {}
|
|
45
|
+
|
|
46
|
+
const ZeroTooltip = (globalConfig?: TooltipConfig): Directive => {
|
|
47
|
+
return {
|
|
48
|
+
created: (targetElement: HTMLElement, binding, vnode) => {
|
|
49
|
+
const uuid = uuidv4()
|
|
50
|
+
vnode.el.$_tooltip = { uuid: uuid }
|
|
51
|
+
|
|
52
|
+
buildTooltip(binding.value, globalConfig, binding.arg, targetElement, uuid)
|
|
53
|
+
|
|
54
|
+
if (typeof(binding.value) !== 'string' && isReactive(binding.value)) {
|
|
55
|
+
watch(binding.value, (newBindingValue) => {
|
|
56
|
+
if (tooltips[uuid]) {
|
|
57
|
+
destroyTooltip(tooltips[uuid])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
buildTooltip(newBindingValue, globalConfig, binding.arg, targetElement, uuid)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
updated: (targetElement: HTMLElement, binding, vnode) => {
|
|
66
|
+
const uuid = vnode.el.$_tooltip.uuid
|
|
67
|
+
|
|
68
|
+
if (tooltips[uuid]) {
|
|
69
|
+
destroyTooltip(tooltips[uuid])
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
buildTooltip(binding.value, globalConfig, binding.arg, targetElement, uuid)
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
beforeUnmount: (_, __, vnode) => {
|
|
76
|
+
const uuid = vnode.el.$_tooltip.uuid
|
|
77
|
+
|
|
78
|
+
if (tooltips[uuid]) {
|
|
79
|
+
destroyTooltip(tooltips[uuid])
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildTooltip(bindingValue: any, globalConfig: TooltipConfig | undefined, bindingArgument: string | undefined, targetElement: HTMLElement, uuid: string) {
|
|
86
|
+
let tooltipConfig = getTooltipConfig(bindingValue as string | TooltipLocalConfig, globalConfig, bindingArgument as TooltipPosition)
|
|
87
|
+
const tooltip = initTooltip(targetElement, tooltipConfig, uuid)
|
|
88
|
+
|
|
89
|
+
tooltips[uuid] = tooltip
|
|
90
|
+
|
|
91
|
+
if (targetElement.matches(':hover')) {
|
|
92
|
+
targetElement.dispatchEvent(new Event('mouseenter'))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getTooltipConfig(localConfig: string | TooltipLocalConfig, globalConfig?: TooltipConfig, position?: TooltipPosition) {
|
|
97
|
+
// Tooltip config
|
|
98
|
+
let appendTo = globalConfig?.appendTo ?? defaultAppendTo
|
|
99
|
+
let tooltipText = getTooltipText(localConfig)
|
|
100
|
+
let tooltipPosition = position ?? globalConfig?.defaultPosition ?? defaultTooltipPosition
|
|
101
|
+
let tooltipPositions: TooltipPositions = {
|
|
102
|
+
left: globalConfig?.positions?.left ?? defaultTooltipPositions.left,
|
|
103
|
+
top: globalConfig?.positions?.top ?? defaultTooltipPositions.top,
|
|
104
|
+
right: globalConfig?.positions?.right ?? defaultTooltipPositions.right,
|
|
105
|
+
bottom: globalConfig?.positions?.bottom ?? defaultTooltipPositions.bottom,
|
|
106
|
+
}
|
|
107
|
+
let tooltipOffsetFromSource = globalConfig?.offsetFromSource ?? defaultTooltipOffsetFromSource
|
|
108
|
+
let tooltipOffsetFromViewport = globalConfig?.offsetFromViewport ?? defaultTooltipOffsetFromViewport
|
|
109
|
+
let tooltipMinWidth = globalConfig?.minWidth ?? defaultTooltipMinWidth
|
|
110
|
+
let tooltipMaxWidth = globalConfig?.maxWidth ?? defaultTooltipMaxWidth
|
|
111
|
+
let tooltipBorderWidth = globalConfig?.tooltipBorderWidth ?? defaultTooltipBorderWidth
|
|
112
|
+
let tooltipClasses = tooltipElementClass + ' ' + defaultTooltipClasses + ' ' + (globalConfig?.tooltipClasses ?? '')
|
|
113
|
+
let textClasses = textElementClass + ' ' + defaultTextClasses + ' ' + (globalConfig?.textClasses ?? '')
|
|
114
|
+
let arrowSize = globalConfig?.arrowSize ?? defaultArrowSize
|
|
115
|
+
let arrowClasses = globalConfig?.arrowClasses ?? ''
|
|
116
|
+
let arrowMinOffsetFromTooltipCorner = globalConfig?.arrowMinOffsetFromTooltipCorner ?? defaultMinArrowOffsetFromTooltipCorner
|
|
117
|
+
let zIndex = globalConfig?.zIndex ?? defaultZIndex
|
|
118
|
+
let shouldShow = defaultShouldShow
|
|
119
|
+
let showDelay = globalConfig?.showDelay ?? defaultShowDelay
|
|
120
|
+
let hideDelay = globalConfig?.hideDelay ?? defaultHideDelay
|
|
121
|
+
let alwaysOn = defaultAlwaysOn
|
|
122
|
+
|
|
123
|
+
// Check if local config is defined (it's defined when local config is Object and not a string, because string means that just Tooltip text is given)
|
|
124
|
+
if (typeof(localConfig) !== 'string') {
|
|
125
|
+
if (localConfig.appendTo !== undefined) appendTo = localConfig.appendTo
|
|
126
|
+
if (position === undefined && localConfig.defaultPosition !== undefined) tooltipPosition = localConfig.defaultPosition
|
|
127
|
+
|
|
128
|
+
if (localConfig.positions?.left !== undefined) tooltipPositions.left = localConfig.positions.left
|
|
129
|
+
if (localConfig.positions?.top !== undefined) tooltipPositions.top = localConfig.positions.top
|
|
130
|
+
if (localConfig.positions?.right !== undefined) tooltipPositions.right = localConfig.positions.right
|
|
131
|
+
if (localConfig.positions?.bottom !== undefined) tooltipPositions.bottom = localConfig.positions.bottom
|
|
132
|
+
|
|
133
|
+
if (localConfig.offsetFromSource !== undefined) tooltipOffsetFromSource = localConfig.offsetFromSource
|
|
134
|
+
if (localConfig.offsetFromViewport !== undefined) tooltipOffsetFromViewport = localConfig.offsetFromViewport
|
|
135
|
+
if (localConfig.minWidth !== undefined) tooltipMinWidth = localConfig.minWidth
|
|
136
|
+
if (localConfig.maxWidth !== undefined) tooltipMaxWidth = localConfig.maxWidth
|
|
137
|
+
if (localConfig.tooltipBorderWidth !== undefined) tooltipBorderWidth = localConfig.tooltipBorderWidth
|
|
138
|
+
if (localConfig.tooltipClasses !== undefined) tooltipClasses = tooltipElementClass + ' ' + defaultTooltipClasses + ' ' + localConfig.tooltipClasses
|
|
139
|
+
if (localConfig.textClasses !== undefined) textClasses = textElementClass + ' ' + defaultTextClasses + ' ' + localConfig.textClasses
|
|
140
|
+
if (localConfig.arrowSize !== undefined) arrowSize = localConfig.arrowSize
|
|
141
|
+
if (localConfig.arrowClasses !== undefined) arrowClasses = localConfig.arrowClasses
|
|
142
|
+
if (localConfig.arrowMinOffsetFromTooltipCorner !== undefined) arrowMinOffsetFromTooltipCorner = localConfig.arrowMinOffsetFromTooltipCorner
|
|
143
|
+
if (localConfig.zIndex !== undefined) zIndex = localConfig.zIndex
|
|
144
|
+
if (localConfig.show !== undefined) shouldShow = localConfig.show
|
|
145
|
+
if (localConfig.showDelay !== undefined) showDelay = localConfig.showDelay
|
|
146
|
+
if (localConfig.hideDelay !== undefined) hideDelay = localConfig.hideDelay
|
|
147
|
+
if (localConfig.alwaysOn !== undefined) alwaysOn = localConfig.alwaysOn
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
appendTo,
|
|
152
|
+
tooltipText,
|
|
153
|
+
tooltipPosition,
|
|
154
|
+
tooltipPositions,
|
|
155
|
+
tooltipOffsetFromSource,
|
|
156
|
+
tooltipOffsetFromViewport,
|
|
157
|
+
tooltipMinWidth,
|
|
158
|
+
tooltipMaxWidth,
|
|
159
|
+
tooltipBorderWidth,
|
|
160
|
+
tooltipClasses,
|
|
161
|
+
textClasses,
|
|
162
|
+
arrowSize,
|
|
163
|
+
arrowClasses,
|
|
164
|
+
arrowMinOffsetFromTooltipCorner,
|
|
165
|
+
zIndex,
|
|
166
|
+
shouldShow,
|
|
167
|
+
showDelay,
|
|
168
|
+
hideDelay,
|
|
169
|
+
alwaysOn
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getTooltipText(localConfig: string | TooltipLocalConfig) {
|
|
174
|
+
const tooltipText = typeof(localConfig) === 'string' ? localConfig : localConfig.content
|
|
175
|
+
|
|
176
|
+
if (!tooltipText) {
|
|
177
|
+
throw new Error("Please enter valid tooltip value");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return tooltipText
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function initTooltip(targetElement: HTMLElement, tooltipConfig: ReturnType<typeof getTooltipConfig>, uuid: string) {
|
|
184
|
+
let anchorElement = targetElement
|
|
185
|
+
|
|
186
|
+
// Create Tooltip element
|
|
187
|
+
let tooltipTextElement = createTextElement(tooltipConfig.textClasses, tooltipConfig.tooltipText)
|
|
188
|
+
let tooltipElement = createTooltipContainerElement(tooltipConfig.tooltipClasses, tooltipConfig.tooltipBorderWidth)
|
|
189
|
+
tooltipElement.append(tooltipTextElement)
|
|
190
|
+
tooltipElement.dataset.uuid = uuid
|
|
191
|
+
|
|
192
|
+
const mouseEventState = {
|
|
193
|
+
currentInstanceId: Date.now(),
|
|
194
|
+
isHoveringOverAnchorElement: false,
|
|
195
|
+
lastTooltipMouseLeaveTimestamp: 0,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const mouseEnterEventControllers = {
|
|
199
|
+
anchorElementMouseEnter: new AbortController(),
|
|
200
|
+
anchorElementMouseLeave: new AbortController(),
|
|
201
|
+
tooltipElementMouseEnter: new AbortController(),
|
|
202
|
+
tooltipElementMouseLeave: new AbortController(),
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!tooltipConfig.alwaysOn) {
|
|
206
|
+
anchorElement.addEventListener('mouseenter', () => onMouseEnter(anchorElement, tooltipConfig, tooltipElement, uuid), { signal: mouseEnterEventControllers.anchorElementMouseEnter.signal })
|
|
207
|
+
anchorElement.addEventListener('mouseleave', () => onMouseLeave(tooltipConfig, uuid), { signal: mouseEnterEventControllers.anchorElementMouseLeave.signal })
|
|
208
|
+
|
|
209
|
+
tooltipElement.addEventListener('mouseenter', () => onMouseEnter(anchorElement, tooltipConfig, tooltipElement, uuid, { isTooltip: true }), { signal: mouseEnterEventControllers.tooltipElementMouseEnter.signal })
|
|
210
|
+
tooltipElement.addEventListener('mouseleave', () => onMouseLeave(tooltipConfig, uuid, { isTooltip: true }), { signal: mouseEnterEventControllers.tooltipElementMouseLeave.signal })
|
|
211
|
+
} else {
|
|
212
|
+
setTimeout(() => mountTooltipElement(anchorElement, tooltipConfig, tooltipElement, 'absolute'), 0)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
anchorElement,
|
|
217
|
+
tooltipConfig,
|
|
218
|
+
tooltipElement,
|
|
219
|
+
mouseEnterEventControllers,
|
|
220
|
+
mouseEventState,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createTextElement(textClasses: string, tooltipText: string) {
|
|
225
|
+
let tooltipTextElement = document.createElement('p')
|
|
226
|
+
tooltipTextElement.classList.add(...textClasses.trim().split(' '))
|
|
227
|
+
tooltipTextElement.innerHTML = tooltipText
|
|
228
|
+
|
|
229
|
+
return tooltipTextElement
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function createTooltipContainerElement(tooltipClasses: string, tooltipBorderWidth: number) {
|
|
233
|
+
let tooltipElement = document.createElement('div')
|
|
234
|
+
tooltipElement.classList.add(...tooltipClasses.trim().split(' '))
|
|
235
|
+
tooltipElement.style.borderWidth = `${tooltipBorderWidth}px`
|
|
236
|
+
|
|
237
|
+
return tooltipElement
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function onMouseEnter(
|
|
241
|
+
anchorElement: HTMLElement,
|
|
242
|
+
tooltipConfig: ReturnType<typeof getTooltipConfig>,
|
|
243
|
+
tooltipElement: HTMLDivElement,
|
|
244
|
+
uuid: string,
|
|
245
|
+
options?: { isTooltip?: boolean }
|
|
246
|
+
) {
|
|
247
|
+
if (!tooltipConfig.shouldShow) return
|
|
248
|
+
|
|
249
|
+
let _showDelay = options?.isTooltip ? 0 : tooltipConfig.showDelay
|
|
250
|
+
|
|
251
|
+
// If mouse leaves from Tooltip and enters to Anchor element in short time, show Tooltip immediately
|
|
252
|
+
const mouseLeaveFromTooltipBufferTime = 100
|
|
253
|
+
if (!options?.isTooltip && Date.now() - tooltips[uuid].mouseEventState.lastTooltipMouseLeaveTimestamp <= mouseLeaveFromTooltipBufferTime) {
|
|
254
|
+
_showDelay = 0
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const currentInstanceId = Date.now()
|
|
258
|
+
tooltips[uuid].mouseEventState.currentInstanceId = currentInstanceId
|
|
259
|
+
tooltips[uuid].mouseEventState.isHoveringOverAnchorElement = true
|
|
260
|
+
|
|
261
|
+
if (_showDelay > 0) {
|
|
262
|
+
await new Promise(resolve => setTimeout(resolve, _showDelay))
|
|
263
|
+
|
|
264
|
+
if (!tooltips[uuid].mouseEventState.isHoveringOverAnchorElement || tooltips[uuid].mouseEventState.currentInstanceId !== currentInstanceId) return
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const didMountTooltip = mountTooltipElement(anchorElement, tooltipConfig, tooltipElement)
|
|
268
|
+
|
|
269
|
+
if (didMountTooltip) {
|
|
270
|
+
handleHideOnScroll(anchorElement, () => hideTooltip(uuid))
|
|
271
|
+
handleHideOnResize(uuid, anchorElement, () => hideTooltip(uuid))
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function mountTooltipElement(
|
|
276
|
+
anchorElement: HTMLElement,
|
|
277
|
+
tooltipConfig: ReturnType<typeof getTooltipConfig>,
|
|
278
|
+
tooltipElement: HTMLDivElement,
|
|
279
|
+
positionStrategy?: 'fixed' | 'absolute'
|
|
280
|
+
) {
|
|
281
|
+
let scrollOffset = { x: 0, y: 0 }
|
|
282
|
+
|
|
283
|
+
if (positionStrategy === 'absolute') {
|
|
284
|
+
tooltipElement.classList.replace('zt-fixed', 'zt-absolute')
|
|
285
|
+
scrollOffset.x = window.scrollX
|
|
286
|
+
scrollOffset.y = window.scrollY
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const anchorElementRect = anchorElement.getBoundingClientRect()
|
|
290
|
+
|
|
291
|
+
// Mount Tooltip element to target element (default is `body`)
|
|
292
|
+
const appendToTarget = document.querySelector(tooltipConfig.appendTo)
|
|
293
|
+
appendToTarget?.appendChild(tooltipElement)
|
|
294
|
+
|
|
295
|
+
// Find suitable Tooltip position
|
|
296
|
+
let hasNeededDisplaySpace = false
|
|
297
|
+
let currentTooltipPosition = tooltipConfig.tooltipPosition
|
|
298
|
+
for (let i = 0; i < 4; i++) {
|
|
299
|
+
currentTooltipPosition = tooltipConfig.tooltipPositions[tooltipConfig.tooltipPosition][i]
|
|
300
|
+
|
|
301
|
+
if (currentTooltipPosition === 'left') {
|
|
302
|
+
hasNeededDisplaySpace = tryMountTooltipOnLeft(anchorElementRect, tooltipConfig, tooltipElement, scrollOffset)
|
|
303
|
+
} else if (currentTooltipPosition === 'top') {
|
|
304
|
+
hasNeededDisplaySpace = tryMountTooltipOnTop(anchorElementRect, tooltipConfig, tooltipElement, scrollOffset)
|
|
305
|
+
} else if (currentTooltipPosition === 'right') {
|
|
306
|
+
hasNeededDisplaySpace = tryMountTooltipOnRight(anchorElementRect, tooltipConfig, tooltipElement, scrollOffset)
|
|
307
|
+
} else if (currentTooltipPosition === 'bottom') {
|
|
308
|
+
hasNeededDisplaySpace = tryMountTooltipOnBottom(anchorElementRect, tooltipConfig, tooltipElement, scrollOffset)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (hasNeededDisplaySpace) break
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (hasNeededDisplaySpace) {
|
|
315
|
+
drawArrow(anchorElementRect, currentTooltipPosition, tooltipConfig, tooltipElement)
|
|
316
|
+
|
|
317
|
+
tooltipElement.style.opacity = '1'
|
|
318
|
+
tooltipElement.style.zIndex = typeof(tooltipConfig.zIndex) === 'string' ? tooltipConfig.zIndex : tooltipConfig.zIndex.toString();
|
|
319
|
+
|
|
320
|
+
return true
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return false
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function onMouseLeave(tooltipConfig: ReturnType<typeof getTooltipConfig>, uuid: string, options?: { isTooltip?: boolean }) {
|
|
327
|
+
if (options?.isTooltip) {
|
|
328
|
+
tooltips[uuid].mouseEventState.lastTooltipMouseLeaveTimestamp = Date.now()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const currentInstanceId = Date.now()
|
|
332
|
+
tooltips[uuid].mouseEventState.currentInstanceId = currentInstanceId
|
|
333
|
+
tooltips[uuid].mouseEventState.isHoveringOverAnchorElement = false
|
|
334
|
+
|
|
335
|
+
if (tooltipConfig.hideDelay > 0) {
|
|
336
|
+
await new Promise(resolve => setTimeout(resolve, tooltipConfig.hideDelay))
|
|
337
|
+
|
|
338
|
+
if (tooltips[uuid].mouseEventState.isHoveringOverAnchorElement || tooltips[uuid].mouseEventState.currentInstanceId !== currentInstanceId) return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
hideTooltip(uuid)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function tryMountTooltipOnLeft(
|
|
345
|
+
anchorElementRect: DOMRect,
|
|
346
|
+
tooltipConfig: ReturnType<typeof getTooltipConfig>,
|
|
347
|
+
tooltipElement: HTMLDivElement,
|
|
348
|
+
scrollOffset: { x: number, y: number },
|
|
349
|
+
) {
|
|
350
|
+
// Check if Tooltip has enough available horizontal space, top and bottom offset from viewport
|
|
351
|
+
const tooltipAvailableMaxWidth = Math.min(anchorElementRect.left - tooltipConfig.tooltipOffsetFromSource - tooltipConfig.tooltipOffsetFromViewport, tooltipConfig.tooltipMaxWidth)
|
|
352
|
+
const isAnchorElementTopLowerThanOffsetFromViewport = anchorElementRect.top >= tooltipConfig.tooltipOffsetFromViewport
|
|
353
|
+
const isAnchorElementBottomHigherThanOffsetFromViewport = (window.innerHeight - anchorElementRect.bottom) >= tooltipConfig.tooltipOffsetFromViewport
|
|
354
|
+
|
|
355
|
+
if (tooltipAvailableMaxWidth < tooltipConfig.tooltipMinWidth || !isAnchorElementTopLowerThanOffsetFromViewport || !isAnchorElementBottomHigherThanOffsetFromViewport) return false
|
|
356
|
+
|
|
357
|
+
// Set Tooltip maxWidth
|
|
358
|
+
tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
|
|
359
|
+
|
|
360
|
+
// Calculate Tooltip position
|
|
361
|
+
const tooltipElementRect = tooltipElement.getBoundingClientRect()
|
|
362
|
+
let tooltipTop = anchorElementRect.top + (anchorElementRect.height / 2) - (tooltipElementRect.height / 2)
|
|
363
|
+
|
|
364
|
+
if (tooltipTop < tooltipConfig.tooltipOffsetFromViewport) {
|
|
365
|
+
tooltipTop = tooltipConfig.tooltipOffsetFromViewport
|
|
366
|
+
} else if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipConfig.tooltipOffsetFromViewport) {
|
|
367
|
+
tooltipTop = window.innerHeight - tooltipConfig.tooltipOffsetFromViewport - tooltipElementRect.height
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const tooltipLeft = anchorElementRect.left - tooltipConfig.tooltipOffsetFromSource - tooltipElementRect.width
|
|
371
|
+
|
|
372
|
+
// Check if anchor element is directly on right of Tooltip
|
|
373
|
+
if (anchorElementRect.bottom < tooltipTop + tooltipConfig.arrowMinOffsetFromTooltipCorner * 2
|
|
374
|
+
|| anchorElementRect.top > tooltipTop + tooltipElementRect.height - tooltipConfig.arrowMinOffsetFromTooltipCorner * 2) return false
|
|
375
|
+
|
|
376
|
+
// Set Tooltip position
|
|
377
|
+
tooltipElement.style.top = `${tooltipTop + scrollOffset.y}px`
|
|
378
|
+
tooltipElement.style.left = `${tooltipLeft + scrollOffset.x}px`
|
|
379
|
+
|
|
380
|
+
return true
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function tryMountTooltipOnRight(
|
|
384
|
+
anchorElementRect: DOMRect,
|
|
385
|
+
tooltipConfig: ReturnType<typeof getTooltipConfig>,
|
|
386
|
+
tooltipElement: HTMLDivElement,
|
|
387
|
+
scrollOffset: { x: number, y: number },
|
|
388
|
+
) {
|
|
389
|
+
// Check if Tooltip has enough available horizontal space, top and bottom offset from viewport
|
|
390
|
+
const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (anchorElementRect.right + tooltipConfig.tooltipOffsetFromSource) - tooltipConfig.tooltipOffsetFromViewport, tooltipConfig.tooltipMaxWidth)
|
|
391
|
+
const isAnchorElementTopLowerThanOffsetFromViewport = anchorElementRect.top >= tooltipConfig.tooltipOffsetFromViewport
|
|
392
|
+
const isAnchorElementBottomHigherThanOffsetFromViewport = (window.innerHeight - anchorElementRect.bottom) >= tooltipConfig.tooltipOffsetFromViewport
|
|
393
|
+
|
|
394
|
+
if (tooltipAvailableMaxWidth < tooltipConfig.tooltipMinWidth || !isAnchorElementTopLowerThanOffsetFromViewport || !isAnchorElementBottomHigherThanOffsetFromViewport) return false
|
|
395
|
+
|
|
396
|
+
// Set tooltip maxWidth
|
|
397
|
+
tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
|
|
398
|
+
|
|
399
|
+
// Calculate Tooltip position
|
|
400
|
+
const tooltipElementRect = tooltipElement.getBoundingClientRect()
|
|
401
|
+
|
|
402
|
+
let tooltipTop = anchorElementRect.top + (anchorElementRect.height / 2) - (tooltipElementRect.height / 2)
|
|
403
|
+
|
|
404
|
+
if (tooltipTop < tooltipConfig.tooltipOffsetFromViewport) {
|
|
405
|
+
tooltipTop = tooltipConfig.tooltipOffsetFromViewport
|
|
406
|
+
} else if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipConfig.tooltipOffsetFromViewport) {
|
|
407
|
+
tooltipTop = window.innerHeight - tooltipConfig.tooltipOffsetFromViewport - tooltipElementRect.height
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const tooltipLeft = anchorElementRect.right + tooltipConfig.tooltipOffsetFromSource
|
|
411
|
+
|
|
412
|
+
// Check if anchor element is directly on left of Tooltip
|
|
413
|
+
if (anchorElementRect.bottom < tooltipTop + tooltipConfig.arrowMinOffsetFromTooltipCorner * 2
|
|
414
|
+
|| anchorElementRect.top > tooltipTop + tooltipElementRect.height - tooltipConfig.arrowMinOffsetFromTooltipCorner * 2) return false
|
|
415
|
+
|
|
416
|
+
// Set Tooltip position
|
|
417
|
+
tooltipElement.style.top = `${tooltipTop + scrollOffset.y}px`
|
|
418
|
+
tooltipElement.style.left = `${tooltipLeft + scrollOffset.x}px`
|
|
419
|
+
|
|
420
|
+
return true
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function tryMountTooltipOnTop(
|
|
424
|
+
anchorElementRect: DOMRect,
|
|
425
|
+
tooltipConfig: ReturnType<typeof getTooltipConfig>,
|
|
426
|
+
tooltipElement: HTMLDivElement,
|
|
427
|
+
scrollOffset: { x: number, y: number },
|
|
428
|
+
) {
|
|
429
|
+
// Calculate and set Tooltip width
|
|
430
|
+
const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (tooltipConfig.tooltipOffsetFromViewport * 2), tooltipConfig.tooltipMaxWidth)
|
|
431
|
+
tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
|
|
432
|
+
|
|
433
|
+
// Calculate Tooltip top position
|
|
434
|
+
const tooltipElementRect = tooltipElement.getBoundingClientRect()
|
|
435
|
+
let tooltipTop = anchorElementRect.top - tooltipConfig.tooltipOffsetFromSource - tooltipElementRect.height
|
|
436
|
+
|
|
437
|
+
// Check if Tooltip has enough available on top
|
|
438
|
+
if (tooltipTop < tooltipConfig.tooltipOffsetFromViewport) return false
|
|
439
|
+
|
|
440
|
+
// Calculate Tooltip left position
|
|
441
|
+
let tooltipLeft = anchorElementRect.left + (anchorElementRect.width / 2) - (tooltipElementRect.width / 2)
|
|
442
|
+
|
|
443
|
+
if (tooltipLeft < tooltipConfig.tooltipOffsetFromViewport) {
|
|
444
|
+
tooltipLeft = tooltipConfig.tooltipOffsetFromViewport
|
|
445
|
+
} else if (tooltipLeft + tooltipElementRect.width > window.innerWidth - tooltipConfig.tooltipOffsetFromViewport) {
|
|
446
|
+
tooltipLeft = window.innerWidth - tooltipConfig.tooltipOffsetFromViewport - tooltipElementRect.width
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Check if anchor element is directly on below of Tooltip
|
|
450
|
+
if (anchorElementRect.left > tooltipLeft + tooltipElementRect.width - tooltipConfig.arrowMinOffsetFromTooltipCorner * 2
|
|
451
|
+
|| anchorElementRect.right < tooltipLeft + tooltipConfig.arrowMinOffsetFromTooltipCorner * 2) return false
|
|
452
|
+
|
|
453
|
+
// Set Tooltip position
|
|
454
|
+
tooltipElement.style.top = `${tooltipTop + scrollOffset.y}px`
|
|
455
|
+
tooltipElement.style.left = `${tooltipLeft + scrollOffset.x}px`
|
|
456
|
+
|
|
457
|
+
return true
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function tryMountTooltipOnBottom(
|
|
461
|
+
anchorElementRect: DOMRect,
|
|
462
|
+
tooltipConfig: ReturnType<typeof getTooltipConfig>,
|
|
463
|
+
tooltipElement: HTMLDivElement,
|
|
464
|
+
scrollOffset: { x: number, y: number },
|
|
465
|
+
) {
|
|
466
|
+
// Calculate and set Tooltip width
|
|
467
|
+
const tooltipAvailableMaxWidth = Math.min(window.innerWidth - (tooltipConfig.tooltipOffsetFromViewport * 2), tooltipConfig.tooltipMaxWidth)
|
|
468
|
+
tooltipElement.style.maxWidth = `${tooltipAvailableMaxWidth}px`
|
|
469
|
+
|
|
470
|
+
// Calculate Tooltip top position
|
|
471
|
+
const tooltipElementRect = tooltipElement.getBoundingClientRect()
|
|
472
|
+
let tooltipTop = anchorElementRect.bottom + tooltipConfig.tooltipOffsetFromSource
|
|
473
|
+
|
|
474
|
+
// Check if Tooltip has enough available on bottom
|
|
475
|
+
if (tooltipTop + tooltipElementRect.height > window.innerHeight - tooltipConfig.tooltipOffsetFromViewport) return false
|
|
476
|
+
|
|
477
|
+
// Calculate Tooltip left position
|
|
478
|
+
let tooltipLeft = anchorElementRect.left + (anchorElementRect.width / 2) - (tooltipElementRect.width / 2)
|
|
479
|
+
|
|
480
|
+
if (tooltipLeft < tooltipConfig.tooltipOffsetFromViewport) {
|
|
481
|
+
tooltipLeft = tooltipConfig.tooltipOffsetFromViewport
|
|
482
|
+
} else if (tooltipLeft + tooltipElementRect.width > window.innerWidth - tooltipConfig.tooltipOffsetFromViewport) {
|
|
483
|
+
tooltipLeft = window.innerWidth - tooltipConfig.tooltipOffsetFromViewport - tooltipElementRect.width
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Check if anchor element is directly on top of Tooltip
|
|
487
|
+
if (anchorElementRect.left > tooltipLeft + tooltipElementRect.width - tooltipConfig.arrowMinOffsetFromTooltipCorner * 2
|
|
488
|
+
|| anchorElementRect.right < tooltipLeft + tooltipConfig.arrowMinOffsetFromTooltipCorner * 2) return false
|
|
489
|
+
|
|
490
|
+
// Set Tooltip position
|
|
491
|
+
tooltipElement.style.top = `${tooltipTop + scrollOffset.y}px`
|
|
492
|
+
tooltipElement.style.left = `${tooltipLeft + scrollOffset.x}px`
|
|
493
|
+
|
|
494
|
+
return true
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function drawArrow(anchorElementRect: DOMRect, currentTooltipPosition: TooltipPosition, tooltipConfig: ReturnType<typeof getTooltipConfig>, tooltipElement: HTMLDivElement) {
|
|
498
|
+
// Create Arrow element
|
|
499
|
+
const arrowElement = document.createElement('div')
|
|
500
|
+
|
|
501
|
+
// Calculate Arrow element size, positions and style/angle classes
|
|
502
|
+
const tooltipElementRect = tooltipElement.getBoundingClientRect()
|
|
503
|
+
const arrowHalfLengthOfLongSide = Math.sin(45 * (180 / Math.PI)) * tooltipConfig.arrowSize
|
|
504
|
+
|
|
505
|
+
// Adjusts arrow position by `x` pixels to handle browsers sometimes not rendering border in it's full width, e.g., 4.8px instead of 5px
|
|
506
|
+
const arrowPositionAdjuster = 1;
|
|
507
|
+
|
|
508
|
+
// Arrow top/left 0 is Tooltip top/left 0
|
|
509
|
+
let arrowTop = 0
|
|
510
|
+
let arrowLeft = 0
|
|
511
|
+
|
|
512
|
+
let arrowClassForCorrectAngle = ''
|
|
513
|
+
|
|
514
|
+
switch (currentTooltipPosition) {
|
|
515
|
+
case "left":
|
|
516
|
+
arrowClassForCorrectAngle = '!zt-border-y-transparent !zt-border-r-transparent'
|
|
517
|
+
arrowTop = anchorElementRect.top - tooltipElementRect.top + (anchorElementRect.height / 2) - arrowHalfLengthOfLongSide - tooltipConfig.tooltipBorderWidth
|
|
518
|
+
arrowLeft = tooltipElementRect.width - tooltipConfig.tooltipBorderWidth - arrowPositionAdjuster
|
|
519
|
+
break;
|
|
520
|
+
case "top":
|
|
521
|
+
arrowClassForCorrectAngle = '!zt-border-x-transparent !zt-border-b-transparent'
|
|
522
|
+
arrowTop = tooltipElementRect.height - tooltipConfig.tooltipBorderWidth - arrowPositionAdjuster
|
|
523
|
+
arrowLeft = anchorElementRect.left - tooltipElementRect.left + (anchorElementRect.width / 2) - arrowHalfLengthOfLongSide - tooltipConfig.tooltipBorderWidth
|
|
524
|
+
break;
|
|
525
|
+
case "right":
|
|
526
|
+
arrowClassForCorrectAngle = '!zt-border-y-transparent !zt-border-l-transparent'
|
|
527
|
+
arrowTop = anchorElementRect.top - tooltipElementRect.top + (anchorElementRect.height / 2) - arrowHalfLengthOfLongSide - tooltipConfig.tooltipBorderWidth
|
|
528
|
+
arrowLeft = (-tooltipConfig.arrowSize * 2) - tooltipConfig.tooltipBorderWidth + arrowPositionAdjuster
|
|
529
|
+
break;
|
|
530
|
+
case "bottom":
|
|
531
|
+
arrowClassForCorrectAngle = '!zt-border-x-transparent !zt-border-t-transparent'
|
|
532
|
+
arrowTop = (-tooltipConfig.arrowSize * 2) - tooltipConfig.tooltipBorderWidth + arrowPositionAdjuster
|
|
533
|
+
arrowLeft = anchorElementRect.left - tooltipElementRect.left + (anchorElementRect.width / 2) - arrowHalfLengthOfLongSide - tooltipConfig.tooltipBorderWidth
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (currentTooltipPosition === 'left' || currentTooltipPosition === 'right') {
|
|
538
|
+
if (!isArrowPositionWithinLimits(currentTooltipPosition, tooltipElementRect, arrowTop, tooltipConfig)) {
|
|
539
|
+
arrowTop = getArrowPositionMinLimit(currentTooltipPosition, tooltipElementRect, arrowTop, tooltipConfig)
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
if (!isArrowPositionWithinLimits(currentTooltipPosition, tooltipElementRect, arrowLeft, tooltipConfig)) {
|
|
543
|
+
arrowLeft = getArrowPositionMinLimit(currentTooltipPosition, tooltipElementRect, arrowLeft, tooltipConfig)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Set Arrow element id, styling/angle
|
|
548
|
+
const adjustedArrowClasses = arrowElementClass + ' ' + defaultArrowClasses + ' ' + arrowClassForCorrectAngle + ' ' + tooltipConfig.arrowClasses
|
|
549
|
+
|
|
550
|
+
arrowElement.classList.add(...adjustedArrowClasses.trim().split(' '))
|
|
551
|
+
|
|
552
|
+
// Set Arrow element size and position
|
|
553
|
+
arrowElement.style.top = `${arrowTop}px`
|
|
554
|
+
arrowElement.style.left = `${arrowLeft}px`
|
|
555
|
+
arrowElement.style.borderWidth = `${tooltipConfig.arrowSize}px`
|
|
556
|
+
|
|
557
|
+
// Mount Arrow element
|
|
558
|
+
tooltipElement.appendChild(arrowElement)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function isArrowPositionWithinLimits(currentTooltipPosition: TooltipPosition, tooltipElementRect: DOMRect, arrowPosition: number, tooltipConfig: ReturnType<typeof getTooltipConfig>) {
|
|
562
|
+
switch (currentTooltipPosition) {
|
|
563
|
+
case "left":
|
|
564
|
+
case "right":
|
|
565
|
+
return arrowPosition > tooltipConfig.arrowMinOffsetFromTooltipCorner - tooltipConfig.tooltipBorderWidth
|
|
566
|
+
&& arrowPosition < tooltipElementRect.height + tooltipConfig.tooltipBorderWidth - tooltipConfig.arrowMinOffsetFromTooltipCorner - (tooltipConfig.arrowSize * 2)
|
|
567
|
+
case "top":
|
|
568
|
+
case "bottom":
|
|
569
|
+
return arrowPosition > tooltipConfig.arrowMinOffsetFromTooltipCorner - tooltipConfig.tooltipBorderWidth
|
|
570
|
+
&& arrowPosition < tooltipElementRect.width + tooltipConfig.tooltipBorderWidth - tooltipConfig.arrowMinOffsetFromTooltipCorner - (tooltipConfig.arrowSize * 2)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function getArrowPositionMinLimit(currentTooltipPosition: TooltipPosition, tooltipElementRect: DOMRect, arrowPosition: number, tooltipConfig: ReturnType<typeof getTooltipConfig>) {
|
|
575
|
+
switch (currentTooltipPosition) {
|
|
576
|
+
case "left":
|
|
577
|
+
case "right":
|
|
578
|
+
if (arrowPosition < tooltipConfig.arrowMinOffsetFromTooltipCorner - tooltipConfig.tooltipBorderWidth) {
|
|
579
|
+
// Arrow too close to viewport top
|
|
580
|
+
return tooltipConfig.arrowMinOffsetFromTooltipCorner - tooltipConfig.tooltipBorderWidth
|
|
581
|
+
} else {
|
|
582
|
+
// Arrow too close to viewport bottom
|
|
583
|
+
return tooltipElementRect.height - tooltipConfig.tooltipBorderWidth - tooltipConfig.arrowMinOffsetFromTooltipCorner - (tooltipConfig.arrowSize * 2)
|
|
584
|
+
}
|
|
585
|
+
case "top":
|
|
586
|
+
case "bottom":
|
|
587
|
+
if (arrowPosition < tooltipConfig.arrowMinOffsetFromTooltipCorner - tooltipConfig.tooltipBorderWidth) {
|
|
588
|
+
// Arrow too close to viewport left
|
|
589
|
+
return tooltipConfig.arrowMinOffsetFromTooltipCorner - tooltipConfig.tooltipBorderWidth
|
|
590
|
+
} else {
|
|
591
|
+
// Arrow too close to viewport right
|
|
592
|
+
return tooltipElementRect.width - tooltipConfig.tooltipBorderWidth - tooltipConfig.arrowMinOffsetFromTooltipCorner - (tooltipConfig.arrowSize * 2)
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function hideTooltip(uuid: string) {
|
|
598
|
+
const tooltipElement = tooltips[uuid]?.tooltipElement
|
|
599
|
+
|
|
600
|
+
resetResizeReferences(uuid)
|
|
601
|
+
removeHideOnScrollListeners()
|
|
602
|
+
|
|
603
|
+
// Remove Arrow element from Tooltip, because it needs to be rebuilt every time Tooltip is showed again
|
|
604
|
+
tooltipElement.querySelector(`.${arrowElementClass}`)?.remove()
|
|
605
|
+
|
|
606
|
+
// Reset position so that old position does not effect new position (when zooming old position could be off screen)
|
|
607
|
+
tooltipElement.style.left = '0'
|
|
608
|
+
tooltipElement.style.top = '0'
|
|
609
|
+
|
|
610
|
+
tooltipElement.remove()
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function destroyTooltip(tooltip: ReturnType<typeof initTooltip>) {
|
|
614
|
+
const uuid = tooltip.tooltipElement.dataset.uuid
|
|
615
|
+
|
|
616
|
+
if (uuid) {
|
|
617
|
+
hideTooltip(uuid)
|
|
618
|
+
delete tooltips[uuid]
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
for (const controller of Object.values(tooltip.mouseEnterEventControllers)) {
|
|
622
|
+
controller.abort()
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export default ZeroTooltip
|