@farm-investimentos/front-mfe-components 15.14.7 → 15.14.9
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/dist/front-mfe-components.common.js +244 -127
- package/dist/front-mfe-components.common.js.map +1 -1
- package/dist/front-mfe-components.css +1 -1
- package/dist/front-mfe-components.umd.js +244 -127
- package/dist/front-mfe-components.umd.js.map +1 -1
- package/dist/front-mfe-components.umd.min.js +1 -1
- package/dist/front-mfe-components.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Tooltip/Tooltip.vue +112 -18
- package/src/components/Tooltip/utils/tooltip.utils.ts +31 -17
package/package.json
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
<div
|
|
4
4
|
ref="activatorRef"
|
|
5
5
|
class="tooltip-activator"
|
|
6
|
-
@
|
|
7
|
-
@
|
|
8
|
-
@mouseleave="hide"
|
|
6
|
+
@mouseenter="onActivatorEnter"
|
|
7
|
+
@mouseleave="onActivatorLeave"
|
|
9
8
|
>
|
|
10
9
|
<slot name="activator" />
|
|
11
10
|
</div>
|
|
12
11
|
|
|
13
|
-
<div v-if="isVisible" ref="tooltipRef" :class="tooltipClasses" :style="tooltipStyles">
|
|
12
|
+
<div v-if="isVisible" ref="tooltipRef" :class="tooltipClasses" :style="tooltipStyles" @mouseenter="onTooltipEnter" @mouseleave="onTooltipLeave">
|
|
14
13
|
<div v-if="hasTitle || showCloseButton" class="tooltip-header">
|
|
15
14
|
<div v-if="hasTitle" class="tooltip-title">
|
|
16
15
|
<slot name="title" />
|
|
@@ -96,12 +95,22 @@ export default defineComponent({
|
|
|
96
95
|
type: String,
|
|
97
96
|
default: undefined,
|
|
98
97
|
},
|
|
98
|
+
anchorTo: {
|
|
99
|
+
type: String as PropType<'auto' | 'viewport' | 'modal'>,
|
|
100
|
+
default: 'auto',
|
|
101
|
+
},
|
|
102
|
+
hideOnScroll: {
|
|
103
|
+
type: Boolean,
|
|
104
|
+
default: true,
|
|
105
|
+
},
|
|
99
106
|
},
|
|
100
107
|
emits: ['input', 'show', 'hide'],
|
|
101
108
|
setup(props, { emit, slots }) {
|
|
102
109
|
const containerRef = ref<HTMLElement | null>(null);
|
|
103
110
|
const activatorRef = ref<HTMLElement | null>(null);
|
|
104
111
|
const tooltipRef = ref<HTMLElement | null>(null);
|
|
112
|
+
const anchorElRef = ref<HTMLElement | null>(null);
|
|
113
|
+
const anchoredToModal = ref(false);
|
|
105
114
|
const scrollableElementsRef = ref<Element[] | null>(null);
|
|
106
115
|
|
|
107
116
|
const Z_INDEX_OFFSET = 1000;
|
|
@@ -157,6 +166,12 @@ export default defineComponent({
|
|
|
157
166
|
return props.placement;
|
|
158
167
|
});
|
|
159
168
|
|
|
169
|
+
// Placement realmente utilizado (pode sofrer flip em runtime)
|
|
170
|
+
const currentPlacement = ref(normalizedPlacement.value);
|
|
171
|
+
watch(normalizedPlacement, (val) => {
|
|
172
|
+
currentPlacement.value = val;
|
|
173
|
+
});
|
|
174
|
+
|
|
160
175
|
const normalizedMaxWidth = computed(() => {
|
|
161
176
|
if (props.fluid) {
|
|
162
177
|
return '300px';
|
|
@@ -170,12 +185,12 @@ export default defineComponent({
|
|
|
170
185
|
[`tooltip-popup--${props.variant}`]: true,
|
|
171
186
|
[`tooltip-popup--${props.size}`]: true,
|
|
172
187
|
'tooltip-popup--has-title': hasTitle.value,
|
|
173
|
-
[`tooltip-popup--${
|
|
188
|
+
[`tooltip-popup--${currentPlacement.value}`]: true,
|
|
174
189
|
}));
|
|
175
190
|
|
|
176
191
|
const tooltipStyles = computed(() => {
|
|
177
192
|
const styles: Record<string, string> = {
|
|
178
|
-
position: 'fixed',
|
|
193
|
+
position: anchoredToModal.value ? 'absolute' : 'fixed',
|
|
179
194
|
zIndex: String(getTooltipZIndex()),
|
|
180
195
|
};
|
|
181
196
|
|
|
@@ -191,7 +206,7 @@ export default defineComponent({
|
|
|
191
206
|
});
|
|
192
207
|
|
|
193
208
|
const arrowStyles = computed(() => {
|
|
194
|
-
const [verticalPos, horizontalAlign] =
|
|
209
|
+
const [verticalPos, horizontalAlign] = currentPlacement.value.split('-');
|
|
195
210
|
|
|
196
211
|
const styles: Record<string, string> = {
|
|
197
212
|
position: 'absolute',
|
|
@@ -231,14 +246,29 @@ export default defineComponent({
|
|
|
231
246
|
|
|
232
247
|
nextTick(() => {
|
|
233
248
|
if (tooltipRef.value && activatorRef.value) {
|
|
234
|
-
|
|
249
|
+
// Resolver onde ancorar o tooltip
|
|
250
|
+
let anchorEl: HTMLElement | null = null;
|
|
251
|
+
if (props.anchorTo === 'viewport') {
|
|
252
|
+
anchorEl = document.body;
|
|
253
|
+
} else if (props.anchorTo === 'modal' || props.anchorTo === 'auto') {
|
|
254
|
+
const modalEl = activatorRef.value.closest('.farm-modal') as HTMLElement | null;
|
|
255
|
+
anchorEl = modalEl || document.body;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
anchorElRef.value = anchorEl;
|
|
259
|
+
anchoredToModal.value = !!anchorEl && anchorEl !== document.body;
|
|
260
|
+
if (anchoredToModal.value && anchorElRef.value && tooltipRef.value) {
|
|
261
|
+
anchorElRef.value.appendChild(tooltipRef.value);
|
|
262
|
+
} else {
|
|
263
|
+
moveToBody(tooltipRef.value);
|
|
264
|
+
}
|
|
235
265
|
updatePosition();
|
|
236
266
|
addScrollListener();
|
|
237
267
|
}
|
|
238
268
|
});
|
|
239
269
|
};
|
|
240
270
|
|
|
241
|
-
|
|
271
|
+
const hide = () => {
|
|
242
272
|
if (props.disabled || isControlled.value) return;
|
|
243
273
|
|
|
244
274
|
isVisible.value = false;
|
|
@@ -246,6 +276,38 @@ export default defineComponent({
|
|
|
246
276
|
removeScrollListener();
|
|
247
277
|
};
|
|
248
278
|
|
|
279
|
+
// Hover management to avoid flicker when moving from activator to tooltip
|
|
280
|
+
let hoverInside = false;
|
|
281
|
+
let hideTimeout: number | null = null;
|
|
282
|
+
|
|
283
|
+
const clearHideTimeout = () => {
|
|
284
|
+
if (hideTimeout) {
|
|
285
|
+
window.clearTimeout(hideTimeout);
|
|
286
|
+
hideTimeout = null;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const onActivatorEnter = () => {
|
|
291
|
+
clearHideTimeout();
|
|
292
|
+
show();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const onActivatorLeave = () => {
|
|
296
|
+
hideTimeout = window.setTimeout(() => {
|
|
297
|
+
if (!hoverInside) hide();
|
|
298
|
+
}, (Array.isArray(props.delay) ? (props.delay[1] || 50) : 50) as number);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const onTooltipEnter = () => {
|
|
302
|
+
hoverInside = true;
|
|
303
|
+
clearHideTimeout();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const onTooltipLeave = () => {
|
|
307
|
+
hoverInside = false;
|
|
308
|
+
hide();
|
|
309
|
+
};
|
|
310
|
+
|
|
249
311
|
const close = () => {
|
|
250
312
|
if (isControlled.value) {
|
|
251
313
|
emit('input', false);
|
|
@@ -274,12 +336,25 @@ export default defineComponent({
|
|
|
274
336
|
const position = calculateTooltipPosition(
|
|
275
337
|
activatorRect,
|
|
276
338
|
tooltipRect,
|
|
277
|
-
|
|
339
|
+
currentPlacement.value,
|
|
278
340
|
props.offset
|
|
279
341
|
);
|
|
280
342
|
|
|
281
|
-
|
|
282
|
-
|
|
343
|
+
// Atualiza placement efetivo (com flip) para setinha/classes
|
|
344
|
+
currentPlacement.value = position.placementUsed as TooltipPlacement;
|
|
345
|
+
|
|
346
|
+
if (anchoredToModal.value && anchorElRef.value) {
|
|
347
|
+
const containerRect = anchorElRef.value.getBoundingClientRect();
|
|
348
|
+
const scrollLeft = anchorElRef.value.scrollLeft || 0;
|
|
349
|
+
const scrollTop = anchorElRef.value.scrollTop || 0;
|
|
350
|
+
const left = position.left - containerRect.left + scrollLeft;
|
|
351
|
+
const top = position.top - containerRect.top + scrollTop;
|
|
352
|
+
tooltipRef.value.style.left = `${left}px`;
|
|
353
|
+
tooltipRef.value.style.top = `${top}px`;
|
|
354
|
+
} else {
|
|
355
|
+
tooltipRef.value.style.left = `${position.left}px`;
|
|
356
|
+
tooltipRef.value.style.top = `${position.top}px`;
|
|
357
|
+
}
|
|
283
358
|
};
|
|
284
359
|
|
|
285
360
|
const getScrollableElements = () => {
|
|
@@ -292,21 +367,36 @@ export default defineComponent({
|
|
|
292
367
|
return scrollableElementsRef.value;
|
|
293
368
|
};
|
|
294
369
|
|
|
370
|
+
|
|
371
|
+
const onAnyScroll = () => {
|
|
372
|
+
if (props.disabled || isControlled.value) return;
|
|
373
|
+
if (props.hideOnScroll) {
|
|
374
|
+
hide();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
updatePosition();
|
|
378
|
+
};
|
|
379
|
+
|
|
295
380
|
const addScrollListener = () => {
|
|
296
|
-
window.addEventListener('scroll',
|
|
381
|
+
window.addEventListener('scroll', onAnyScroll, { passive: true });
|
|
382
|
+
// Opcionalmente também reagir a wheel/touchmove para UX mais fluida
|
|
383
|
+
window.addEventListener('wheel', onAnyScroll, { passive: true });
|
|
384
|
+
window.addEventListener('touchmove', onAnyScroll, { passive: true });
|
|
297
385
|
|
|
298
386
|
const scrollableElements = getScrollableElements();
|
|
299
387
|
scrollableElements.forEach(element => {
|
|
300
|
-
element.addEventListener('scroll',
|
|
388
|
+
element.addEventListener('scroll', onAnyScroll, { passive: true });
|
|
301
389
|
});
|
|
302
390
|
};
|
|
303
391
|
|
|
304
392
|
const removeScrollListener = () => {
|
|
305
|
-
window.removeEventListener('scroll',
|
|
393
|
+
window.removeEventListener('scroll', onAnyScroll);
|
|
394
|
+
window.removeEventListener('wheel', onAnyScroll);
|
|
395
|
+
window.removeEventListener('touchmove', onAnyScroll);
|
|
306
396
|
|
|
307
397
|
const scrollableElements = getScrollableElements();
|
|
308
398
|
scrollableElements.forEach(element => {
|
|
309
|
-
element.removeEventListener('scroll',
|
|
399
|
+
element.removeEventListener('scroll', onAnyScroll);
|
|
310
400
|
});
|
|
311
401
|
};
|
|
312
402
|
|
|
@@ -353,8 +443,12 @@ export default defineComponent({
|
|
|
353
443
|
tooltipClasses,
|
|
354
444
|
tooltipStyles,
|
|
355
445
|
arrowStyles,
|
|
356
|
-
|
|
357
|
-
|
|
446
|
+
show,
|
|
447
|
+
hide,
|
|
448
|
+
onActivatorEnter,
|
|
449
|
+
onActivatorLeave,
|
|
450
|
+
onTooltipEnter,
|
|
451
|
+
onTooltipLeave,
|
|
358
452
|
close,
|
|
359
453
|
};
|
|
360
454
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface TooltipPosition {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
left: number;
|
|
3
|
+
top: number;
|
|
4
|
+
placementUsed: string; // ex.: 'top-left', 'bottom-center'
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
export interface TooltipRect {
|
|
@@ -20,16 +21,19 @@ export function calculateTooltipPosition(
|
|
|
20
21
|
placement: string,
|
|
21
22
|
offset: number = 8
|
|
22
23
|
): TooltipPosition {
|
|
23
|
-
|
|
24
|
+
const parts = placement.split('-');
|
|
25
|
+
let verticalPos = parts[0];
|
|
26
|
+
const horizontalAlign = parts[1];
|
|
24
27
|
|
|
25
28
|
let left = 0;
|
|
26
29
|
let top = 0;
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const computeTop = (vert: string) =>
|
|
32
|
+
vert === 'top'
|
|
33
|
+
? activatorRect.top - tooltipRect.height - offset
|
|
34
|
+
: activatorRect.bottom + offset;
|
|
35
|
+
|
|
36
|
+
top = computeTop(verticalPos);
|
|
33
37
|
|
|
34
38
|
switch (horizontalAlign) {
|
|
35
39
|
case 'left':
|
|
@@ -45,16 +49,26 @@ export function calculateTooltipPosition(
|
|
|
45
49
|
break;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
// Flip vertical if not enough space
|
|
53
|
+
const tooHigh = top < offset;
|
|
54
|
+
const tooLow = top + tooltipRect.height > window.innerHeight - offset;
|
|
55
|
+
if ((verticalPos === 'top' && tooHigh) || (verticalPos === 'bottom' && tooLow)) {
|
|
56
|
+
verticalPos = verticalPos === 'top' ? 'bottom' : 'top';
|
|
57
|
+
top = computeTop(verticalPos);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Clamp within viewport horizontally and vertically
|
|
61
|
+
if (left < offset) left = offset;
|
|
62
|
+
if (left + tooltipRect.width > window.innerWidth - offset) {
|
|
63
|
+
left = window.innerWidth - tooltipRect.width - offset;
|
|
64
|
+
}
|
|
65
|
+
if (top < offset) top = offset;
|
|
66
|
+
if (top + tooltipRect.height > window.innerHeight - offset) {
|
|
67
|
+
top = window.innerHeight - tooltipRect.height - offset;
|
|
68
|
+
}
|
|
56
69
|
|
|
57
|
-
|
|
70
|
+
const placementUsed = `${verticalPos}-${horizontalAlign || 'center'}`;
|
|
71
|
+
return { left, top, placementUsed };
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
export function moveToBody(element: HTMLElement): void {
|