@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farm-investimentos/front-mfe-components",
3
- "version": "15.14.7",
3
+ "version": "15.14.9",
4
4
  "author": "farm investimentos",
5
5
  "private": false,
6
6
  "main": "./dist/front-mfe-components.common.js",
@@ -3,14 +3,13 @@
3
3
  <div
4
4
  ref="activatorRef"
5
5
  class="tooltip-activator"
6
- @mouseover="show"
7
- @mouseout="hide"
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--${normalizedPlacement.value}`]: true,
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] = normalizedPlacement.value.split('-');
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
- moveToBody(tooltipRef.value);
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
- const hide = () => {
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
- normalizedPlacement.value,
339
+ currentPlacement.value,
278
340
  props.offset
279
341
  );
280
342
 
281
- tooltipRef.value.style.left = `${position.left}px`;
282
- tooltipRef.value.style.top = `${position.top}px`;
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', updatePosition, { passive: true });
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', updatePosition, { passive: true });
388
+ element.addEventListener('scroll', onAnyScroll, { passive: true });
301
389
  });
302
390
  };
303
391
 
304
392
  const removeScrollListener = () => {
305
- window.removeEventListener('scroll', updatePosition);
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', updatePosition);
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
- show,
357
- hide,
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
- left: number;
3
- top: number;
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
- const [verticalPos, horizontalAlign] = placement.split('-');
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
- if (verticalPos === 'top') {
29
- top = activatorRect.top - tooltipRect.height - offset;
30
- } else {
31
- top = activatorRect.bottom + offset;
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
- if (left < offset) left = offset;
49
- if (left + tooltipRect.width > window.innerWidth - offset) {
50
- left = window.innerWidth - tooltipRect.width - offset;
51
- }
52
- if (top < offset) top = offset;
53
- if (top + tooltipRect.height > window.innerHeight - offset) {
54
- top = window.innerHeight - tooltipRect.height - offset;
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
- return { left, top };
70
+ const placementUsed = `${verticalPos}-${horizontalAlign || 'center'}`;
71
+ return { left, top, placementUsed };
58
72
  }
59
73
 
60
74
  export function moveToBody(element: HTMLElement): void {