@farm-investimentos/front-mfe-components-vue3 1.3.0 → 1.4.1
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 +420 -1146
- 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 +420 -1146
- 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 +3 -3
- package/src/components/Tooltip/Tooltip.scss +174 -22
- package/src/components/Tooltip/Tooltip.stories.js +264 -80
- package/src/components/Tooltip/Tooltip.vue +399 -119
- package/src/components/Tooltip/__tests__/Tooltip.spec.js +192 -9
- package/src/components/Tooltip/docs/Tooltip.md +313 -0
- package/src/components/Tooltip/index.ts +17 -2
- package/src/components/Tooltip/types.ts +43 -0
- package/src/main.ts +2 -0
|
@@ -1,134 +1,414 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
ref="
|
|
2
|
+
<div
|
|
3
|
+
class="tooltip-container"
|
|
4
|
+
ref="containerRef"
|
|
5
5
|
>
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@mouseover="
|
|
10
|
-
@mouseout="
|
|
6
|
+
<div
|
|
7
|
+
ref="activatorRef"
|
|
8
|
+
class="tooltip-activator"
|
|
9
|
+
@mouseover="show"
|
|
10
|
+
@mouseout="hide"
|
|
11
|
+
@mouseleave="hide"
|
|
11
12
|
>
|
|
12
13
|
<slot name="activator" />
|
|
13
|
-
</
|
|
14
|
-
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'farm-tooltip__popup--visible':
|
|
21
|
-
(!externalControl && showOver) || (externalControl && toggleComponent),
|
|
22
|
-
}"
|
|
23
|
-
:style="styles"
|
|
24
|
-
@mouseout="onOut"
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div
|
|
17
|
+
v-if="isVisible"
|
|
18
|
+
ref="tooltipRef"
|
|
19
|
+
:class="tooltipClasses"
|
|
20
|
+
:style="tooltipStyles"
|
|
25
21
|
>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
<div
|
|
23
|
+
v-if="hasTitle || showCloseButton"
|
|
24
|
+
class="tooltip-header"
|
|
25
|
+
>
|
|
26
|
+
<div
|
|
27
|
+
v-if="hasTitle"
|
|
28
|
+
class="tooltip-title"
|
|
29
|
+
>
|
|
30
|
+
<slot name="title" />
|
|
31
|
+
</div>
|
|
32
|
+
<span
|
|
33
|
+
v-if="showCloseButton"
|
|
34
|
+
class="tooltip-close"
|
|
35
|
+
@click="close"
|
|
36
|
+
>×</span
|
|
37
|
+
>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="tooltip-content">
|
|
41
|
+
<slot />
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div
|
|
45
|
+
class="tooltip-arrow"
|
|
46
|
+
:style="arrowStyles"
|
|
47
|
+
></div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
29
50
|
</template>
|
|
30
|
-
<script lang="ts">
|
|
31
|
-
import { PropType, ref, computed, reactive, onBeforeUnmount } from 'vue';
|
|
32
51
|
|
|
33
|
-
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import { ref, computed, onBeforeUnmount, nextTick, watch, useSlots } from 'vue';
|
|
34
54
|
|
|
35
|
-
|
|
55
|
+
defineOptions({
|
|
36
56
|
name: 'farm-tooltip',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export interface TooltipProps {
|
|
60
|
+
modelValue?: boolean;
|
|
61
|
+
trigger?: 'hover' | 'click' | 'manual';
|
|
62
|
+
placement?:
|
|
63
|
+
| 'top-left'
|
|
64
|
+
| 'top-center'
|
|
65
|
+
| 'top-right'
|
|
66
|
+
| 'bottom-left'
|
|
67
|
+
| 'bottom-center'
|
|
68
|
+
| 'bottom-right';
|
|
69
|
+
offset?: number;
|
|
70
|
+
variant?: 'dark' | 'light';
|
|
71
|
+
size?: 'sm' | 'md' | 'lg';
|
|
72
|
+
maxWidth?: string | number;
|
|
73
|
+
delay?: number | [number, number];
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
fluid?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const props = withDefaults(defineProps<TooltipProps>(), {
|
|
79
|
+
modelValue: undefined,
|
|
80
|
+
trigger: 'hover',
|
|
81
|
+
placement: 'top-center',
|
|
82
|
+
offset: 8,
|
|
83
|
+
variant: 'dark',
|
|
84
|
+
size: 'md',
|
|
85
|
+
maxWidth: undefined,
|
|
86
|
+
delay: () => [100, 50],
|
|
87
|
+
disabled: false,
|
|
88
|
+
fluid: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const emit = defineEmits<{
|
|
92
|
+
'update:modelValue': [value: boolean];
|
|
93
|
+
show: [];
|
|
94
|
+
hide: [];
|
|
95
|
+
}>();
|
|
96
|
+
|
|
97
|
+
const slots = useSlots();
|
|
98
|
+
|
|
99
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
100
|
+
const activatorRef = ref<HTMLElement | null>(null);
|
|
101
|
+
const tooltipRef = ref<HTMLElement | null>(null);
|
|
102
|
+
const scrollableElementsRef = ref<Element[] | null>(null);
|
|
103
|
+
|
|
104
|
+
const ARROW_OFFSET = 18;
|
|
105
|
+
const Z_INDEX_OFFSET = 1000;
|
|
106
|
+
const DEFAULT_Z_INDEX = 10001;
|
|
107
|
+
|
|
108
|
+
let modalCache: { modals: Element[]; timestamp: number } | null = null;
|
|
109
|
+
|
|
110
|
+
const isVisible = ref(false);
|
|
111
|
+
|
|
112
|
+
const isControlled = computed(() => props.modelValue !== undefined);
|
|
113
|
+
const hasTitle = computed(() => !!slots.title);
|
|
114
|
+
const showCloseButton = computed(() => isControlled.value && hasTitle.value);
|
|
115
|
+
|
|
116
|
+
const normalizedMaxWidth = computed(() => {
|
|
117
|
+
if (props.fluid) {
|
|
118
|
+
return '300px';
|
|
119
|
+
}
|
|
120
|
+
return props.maxWidth;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const tooltipClasses = computed(() => ({
|
|
124
|
+
'tooltip-popup': true,
|
|
125
|
+
'tooltip-popup--visible': isVisible.value,
|
|
126
|
+
[`tooltip-popup--${props.variant}`]: true,
|
|
127
|
+
[`tooltip-popup--${props.size}`]: true,
|
|
128
|
+
'tooltip-popup--has-title': hasTitle.value,
|
|
129
|
+
[`tooltip-popup--${props.placement}`]: true,
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
const tooltipStyles = computed(() => {
|
|
133
|
+
const styles: Record<string, string> = {
|
|
134
|
+
position: 'fixed',
|
|
135
|
+
zIndex: String(getTooltipZIndex()),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (normalizedMaxWidth.value) {
|
|
139
|
+
styles.maxWidth =
|
|
140
|
+
typeof normalizedMaxWidth.value === 'number'
|
|
141
|
+
? `${normalizedMaxWidth.value}px`
|
|
142
|
+
: normalizedMaxWidth.value;
|
|
143
|
+
styles.width = 'auto';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return styles;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const arrowStyles = computed(() => {
|
|
150
|
+
const [verticalPos] = props.placement.split('-');
|
|
151
|
+
const arrowColor = props.variant === 'light' ? '#ffffff' : '#333333';
|
|
152
|
+
|
|
153
|
+
const styles: Record<string, string> = {
|
|
154
|
+
position: 'absolute',
|
|
155
|
+
width: '0',
|
|
156
|
+
height: '0',
|
|
157
|
+
borderStyle: 'solid',
|
|
158
|
+
zIndex: 'inherit',
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (verticalPos === 'top') {
|
|
162
|
+
styles.bottom = '-6px';
|
|
163
|
+
styles.borderWidth = '6px 6px 0 6px';
|
|
164
|
+
styles.borderColor = `${arrowColor} transparent transparent transparent`;
|
|
165
|
+
} else {
|
|
166
|
+
styles.top = '-6px';
|
|
167
|
+
styles.borderWidth = '0 6px 6px 6px';
|
|
168
|
+
styles.borderColor = `transparent transparent ${arrowColor} transparent`;
|
|
169
|
+
}
|
|
170
|
+
styles.left = '50%';
|
|
171
|
+
styles.transform = 'translateX(-50%)';
|
|
172
|
+
|
|
173
|
+
return styles;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const getTooltipZIndex = () => {
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
let modals: Element[];
|
|
179
|
+
|
|
180
|
+
if (modalCache && now - modalCache.timestamp < 500) {
|
|
181
|
+
modals = modalCache.modals;
|
|
182
|
+
} else {
|
|
183
|
+
modals = Array.from(document.querySelectorAll('.farm-modal'));
|
|
184
|
+
modalCache = { modals, timestamp: now };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let maxModalZIndex = 0;
|
|
188
|
+
|
|
189
|
+
modals.forEach(modal => {
|
|
190
|
+
const htmlModal = modal as HTMLElement;
|
|
191
|
+
|
|
192
|
+
let zIndex = parseInt(htmlModal.style.zIndex, 10);
|
|
193
|
+
|
|
194
|
+
if (Number.isNaN(zIndex)) {
|
|
195
|
+
const computedZIndex = window.getComputedStyle(htmlModal).zIndex;
|
|
196
|
+
if (computedZIndex === 'auto') {
|
|
197
|
+
zIndex = 0;
|
|
198
|
+
} else {
|
|
199
|
+
zIndex = parseInt(computedZIndex, 10) || 0;
|
|
114
200
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (zIndex > maxModalZIndex) {
|
|
204
|
+
maxModalZIndex = zIndex;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return maxModalZIndex > 0 ? maxModalZIndex + Z_INDEX_OFFSET : DEFAULT_Z_INDEX;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const calculateTooltipPosition = (
|
|
212
|
+
activatorRect: DOMRect,
|
|
213
|
+
tooltipRect: DOMRect,
|
|
214
|
+
placement: string,
|
|
215
|
+
offset: number = 8
|
|
216
|
+
) => {
|
|
217
|
+
const [verticalPos, horizontalAlign] = placement.split('-');
|
|
218
|
+
|
|
219
|
+
let left = 0;
|
|
220
|
+
let top = 0;
|
|
221
|
+
|
|
222
|
+
if (verticalPos === 'top') {
|
|
223
|
+
top = activatorRect.top - tooltipRect.height - offset;
|
|
224
|
+
} else {
|
|
225
|
+
top = activatorRect.bottom + offset;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
switch (horizontalAlign) {
|
|
229
|
+
case 'left':
|
|
230
|
+
left = activatorRect.left + activatorRect.width / 2 - ARROW_OFFSET;
|
|
231
|
+
break;
|
|
232
|
+
case 'right':
|
|
233
|
+
left =
|
|
234
|
+
activatorRect.left + activatorRect.width / 2 - (tooltipRect.width - ARROW_OFFSET);
|
|
235
|
+
break;
|
|
236
|
+
case 'center':
|
|
237
|
+
default:
|
|
238
|
+
left = activatorRect.left + activatorRect.width / 2 - tooltipRect.width / 2;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (left < offset) left = offset;
|
|
243
|
+
if (left + tooltipRect.width > window.innerWidth - offset) {
|
|
244
|
+
left = window.innerWidth - tooltipRect.width - offset;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { left, top };
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const moveToBody = (element: HTMLElement): void => {
|
|
251
|
+
if (element.parentNode !== document.body) {
|
|
252
|
+
document.body.appendChild(element);
|
|
253
|
+
}
|
|
129
254
|
};
|
|
255
|
+
|
|
256
|
+
const moveToContainer = (element: HTMLElement, container: HTMLElement): void => {
|
|
257
|
+
if (element.parentNode === document.body) {
|
|
258
|
+
container.appendChild(element);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const show = () => {
|
|
263
|
+
if (props.disabled || isControlled.value) return;
|
|
264
|
+
|
|
265
|
+
isVisible.value = true;
|
|
266
|
+
emit('show');
|
|
267
|
+
|
|
268
|
+
nextTick(() => {
|
|
269
|
+
if (tooltipRef.value && activatorRef.value) {
|
|
270
|
+
moveToBody(tooltipRef.value);
|
|
271
|
+
updatePosition();
|
|
272
|
+
addScrollListener();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const hide = () => {
|
|
278
|
+
if (props.disabled || isControlled.value) return;
|
|
279
|
+
|
|
280
|
+
isVisible.value = false;
|
|
281
|
+
emit('hide');
|
|
282
|
+
removeScrollListener();
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const close = () => {
|
|
286
|
+
if (isControlled.value) {
|
|
287
|
+
emit('update:modelValue', false);
|
|
288
|
+
} else {
|
|
289
|
+
hide();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const updatePosition = () => {
|
|
294
|
+
if (!activatorRef.value || !tooltipRef.value) return;
|
|
295
|
+
|
|
296
|
+
const activatorRect = activatorRef.value.getBoundingClientRect();
|
|
297
|
+
const tooltipRect = tooltipRef.value.getBoundingClientRect();
|
|
298
|
+
|
|
299
|
+
const isActivatorVisible =
|
|
300
|
+
activatorRect.top < window.innerHeight &&
|
|
301
|
+
activatorRect.bottom > 0 &&
|
|
302
|
+
activatorRect.left < window.innerWidth &&
|
|
303
|
+
activatorRect.right > 0;
|
|
304
|
+
|
|
305
|
+
if (!isActivatorVisible && isVisible.value && !isControlled.value) {
|
|
306
|
+
hide();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const position = calculateTooltipPosition(
|
|
311
|
+
activatorRect,
|
|
312
|
+
tooltipRect,
|
|
313
|
+
props.placement,
|
|
314
|
+
props.offset
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
tooltipRef.value.style.left = `${position.left}px`;
|
|
318
|
+
tooltipRef.value.style.top = `${position.top}px`;
|
|
319
|
+
updateArrowPosition(activatorRect, position);
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const updateArrowPosition = (
|
|
323
|
+
activatorRect: DOMRect,
|
|
324
|
+
tooltipPosition: { left: number; top: number }
|
|
325
|
+
) => {
|
|
326
|
+
if (!tooltipRef.value) return;
|
|
327
|
+
|
|
328
|
+
const arrow = tooltipRef.value.querySelector('.tooltip-arrow') as HTMLElement;
|
|
329
|
+
if (!arrow) return;
|
|
330
|
+
|
|
331
|
+
const activatorCenterX = activatorRect.left + activatorRect.width / 2;
|
|
332
|
+
const arrowX = activatorCenterX - tooltipPosition.left;
|
|
333
|
+
const tooltipWidth = tooltipRef.value.offsetWidth;
|
|
334
|
+
const minArrowX = 12;
|
|
335
|
+
const maxArrowX = tooltipWidth - 12;
|
|
336
|
+
|
|
337
|
+
const clampedArrowX = Math.max(minArrowX, Math.min(maxArrowX, arrowX));
|
|
338
|
+
arrow.style.left = `${clampedArrowX}px`;
|
|
339
|
+
arrow.style.transform = 'translateX(-50%)';
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const getScrollableElements = () => {
|
|
343
|
+
if (!scrollableElementsRef.value) {
|
|
344
|
+
const nodeList = document.querySelectorAll(
|
|
345
|
+
'.farm-modal, .modal-content, [style*="overflow-y: auto"], [style*="overflow-y: scroll"]'
|
|
346
|
+
);
|
|
347
|
+
scrollableElementsRef.value = Array.from(nodeList);
|
|
348
|
+
}
|
|
349
|
+
return scrollableElementsRef.value;
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const addScrollListener = () => {
|
|
353
|
+
window.addEventListener('scroll', updatePosition, { passive: true });
|
|
354
|
+
|
|
355
|
+
const scrollableElements = getScrollableElements();
|
|
356
|
+
scrollableElements.forEach(element => {
|
|
357
|
+
element.addEventListener('scroll', updatePosition, { passive: true });
|
|
358
|
+
});
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const removeScrollListener = () => {
|
|
362
|
+
window.removeEventListener('scroll', updatePosition);
|
|
363
|
+
|
|
364
|
+
const scrollableElements = getScrollableElements();
|
|
365
|
+
scrollableElements.forEach(element => {
|
|
366
|
+
element.removeEventListener('scroll', updatePosition);
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
if (isControlled.value) {
|
|
371
|
+
isVisible.value = props.modelValue || false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
watch(
|
|
375
|
+
() => props.modelValue,
|
|
376
|
+
newValue => {
|
|
377
|
+
if (isControlled.value) {
|
|
378
|
+
isVisible.value = newValue || false;
|
|
379
|
+
|
|
380
|
+
if (isVisible.value) {
|
|
381
|
+
nextTick(() => {
|
|
382
|
+
if (tooltipRef.value) {
|
|
383
|
+
moveToBody(tooltipRef.value);
|
|
384
|
+
updatePosition();
|
|
385
|
+
addScrollListener();
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
} else {
|
|
389
|
+
removeScrollListener();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
onBeforeUnmount(() => {
|
|
396
|
+
if (tooltipRef.value && containerRef.value) {
|
|
397
|
+
moveToContainer(tooltipRef.value, containerRef.value);
|
|
398
|
+
}
|
|
399
|
+
removeScrollListener();
|
|
400
|
+
});
|
|
130
401
|
</script>
|
|
131
402
|
|
|
132
403
|
<style lang="scss" scoped>
|
|
133
|
-
@import './Tooltip';
|
|
404
|
+
@import './Tooltip.scss';
|
|
405
|
+
|
|
406
|
+
.tooltip-container {
|
|
407
|
+
display: inline-block;
|
|
408
|
+
position: relative;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.tooltip-activator {
|
|
412
|
+
display: inline-block;
|
|
413
|
+
}
|
|
134
414
|
</style>
|