@elogroup-sereduc/portal-aluno-tour 1.0.6 → 1.0.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Tour.d.ts","sourceRoot":"","sources":["../../src/components/Tour.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,MAAM,UAAU,CAAC;AAG/C;;GAEG;AACH,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,KAAK,EACL,WAAe,EACf,OAAY,EACZ,MAAM,EACN,UAAU,GACX,EAAE,SAAS,kDAkeX"}
1
+ {"version":3,"file":"Tour.d.ts","sourceRoot":"","sources":["../../src/components/Tour.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,MAAM,UAAU,CAAC;AAG/C;;GAEG;AACH,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,KAAK,EACL,WAAe,EACf,OAAY,EACZ,MAAM,EACN,UAAU,GACX,EAAE,SAAS,kDAqmBX"}
@@ -15,50 +15,141 @@ export function Tour({ enabled, steps, initialStep = 0, options = {}, onExit, on
15
15
  const isConfiguredRef = useRef(false);
16
16
  const { nextLabel = "Próximo", prevLabel = "Anterior", skipLabel = "Pular", doneLabel = "Concluir", showProgress = true, showBullets = true, exitOnOverlayClick = false, exitOnEsc = true, } = options;
17
17
  // Calcula a posição da tooltip baseado no elemento destacado
18
- const calculateTooltipPosition = useCallback((element, position = "bottom") => {
19
- // Recalcula o rect após possíveis mudanças de scroll
18
+ // Usa getBoundingClientRect() que retorna posições relativas à viewport
19
+ // Isso mantém a tooltip sempre visível mesmo durante scroll
20
+ const calculateTooltipPosition = useCallback((element, position = "auto") => {
20
21
  const rect = element.getBoundingClientRect();
21
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
22
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
23
22
  const viewportWidth = window.innerWidth;
24
23
  const viewportHeight = window.innerHeight;
25
24
  // Tamanho estimado da tooltip
26
25
  const tooltipWidth = 384; // max-w-sm = 384px
27
- const tooltipHeight = 250; // altura estimada com conteúdo
28
- const spacing = 8; // espaçamento menor para ficar mais próximo
26
+ const tooltipHeight = 250; // altura estimada
27
+ const spacing = 8; // espaçamento próximo ao elemento
29
28
  let top = 0;
30
29
  let left = 0;
31
30
  let finalPosition = position;
32
- // Calcula posição base - sempre próxima ao elemento
33
- switch (position) {
31
+ // Verifica se o elemento está visível na viewport
32
+ const isElementVisible = rect.top < viewportHeight &&
33
+ rect.bottom > 0 &&
34
+ rect.left < viewportWidth &&
35
+ rect.right > 0;
36
+ // Se o elemento não estiver visível, tenta posicionar próximo à borda mais próxima
37
+ if (!isElementVisible) {
38
+ // Elemento está fora da viewport - posiciona próximo à borda mais próxima
39
+ if (rect.bottom < 0) {
40
+ // Elemento está acima da viewport
41
+ top = 8;
42
+ left = Math.max(tooltipWidth / 2 + 8, Math.min(viewportWidth - tooltipWidth / 2 - 8, rect.left + rect.width / 2));
43
+ finalPosition = "top";
44
+ }
45
+ else if (rect.top > viewportHeight) {
46
+ // Elemento está abaixo da viewport
47
+ top = viewportHeight - tooltipHeight - 8;
48
+ left = Math.max(tooltipWidth / 2 + 8, Math.min(viewportWidth - tooltipWidth / 2 - 8, rect.left + rect.width / 2));
49
+ finalPosition = "bottom";
50
+ }
51
+ else {
52
+ // Elemento está à esquerda ou direita
53
+ top = Math.max(tooltipHeight / 2 + 8, Math.min(viewportHeight - tooltipHeight / 2 - 8, rect.top + rect.height / 2));
54
+ if (rect.right < 0) {
55
+ left = 8;
56
+ finalPosition = "left";
57
+ }
58
+ else {
59
+ left = viewportWidth - tooltipWidth - 8;
60
+ finalPosition = "right";
61
+ }
62
+ }
63
+ return { top, left, position: finalPosition };
64
+ }
65
+ // Calcula espaço disponível em todas as direções
66
+ const spaceAbove = rect.top;
67
+ const spaceBelow = viewportHeight - rect.bottom;
68
+ const spaceLeft = rect.left;
69
+ const spaceRight = viewportWidth - rect.right;
70
+ // Determina a melhor posição baseado no espaço disponível
71
+ let bestPosition;
72
+ // Se position for "auto" ou não especificado, calcula automaticamente a melhor posição
73
+ if (!position || position === "auto") {
74
+ // Escolhe automaticamente a direção com mais espaço disponível
75
+ const maxSpace = Math.max(spaceAbove, spaceBelow, spaceLeft, spaceRight);
76
+ if (maxSpace === spaceAbove)
77
+ bestPosition = "top";
78
+ else if (maxSpace === spaceBelow)
79
+ bestPosition = "bottom";
80
+ else if (maxSpace === spaceLeft)
81
+ bestPosition = "left";
82
+ else
83
+ bestPosition = "right";
84
+ }
85
+ else {
86
+ // Posição específica foi fornecida - tenta usar ela primeiro
87
+ // Verifica se a posição especificada tem espaço suficiente
88
+ const hasEnoughSpace = (position === "top" && spaceAbove >= tooltipHeight + spacing + 8) ||
89
+ (position === "bottom" && spaceBelow >= tooltipHeight + spacing + 8) ||
90
+ (position === "left" && spaceLeft >= tooltipWidth + spacing + 8) ||
91
+ (position === "right" && spaceRight >= tooltipWidth + spacing + 8);
92
+ if (hasEnoughSpace) {
93
+ // Tem espaço suficiente, usa a posição especificada
94
+ bestPosition = position;
95
+ }
96
+ else {
97
+ // Não tem espaço suficiente na posição especificada, escolhe a melhor alternativa
98
+ // Para posições verticais (top/bottom), compara entre elas
99
+ if (position === "top" || position === "bottom") {
100
+ bestPosition = spaceAbove > spaceBelow ? "top" : "bottom";
101
+ }
102
+ // Para posições horizontais (left/right), compara entre elas
103
+ else if (position === "left" || position === "right") {
104
+ bestPosition = spaceLeft > spaceRight ? "left" : "right";
105
+ }
106
+ // Fallback: escolhe a direção com mais espaço
107
+ else {
108
+ const maxSpace = Math.max(spaceAbove, spaceBelow, spaceLeft, spaceRight);
109
+ if (maxSpace === spaceAbove)
110
+ bestPosition = "top";
111
+ else if (maxSpace === spaceBelow)
112
+ bestPosition = "bottom";
113
+ else if (maxSpace === spaceLeft)
114
+ bestPosition = "left";
115
+ else
116
+ bestPosition = "right";
117
+ }
118
+ }
119
+ }
120
+ // Calcula posição base na melhor direção escolhida
121
+ switch (bestPosition) {
34
122
  case "top":
35
- top = rect.top + scrollTop - spacing;
36
- left = rect.left + scrollLeft + rect.width / 2;
123
+ top = rect.top - spacing;
124
+ left = rect.left + rect.width / 2;
37
125
  break;
38
126
  case "bottom":
39
- top = rect.bottom + scrollTop + spacing;
40
- left = rect.left + scrollLeft + rect.width / 2;
127
+ top = rect.bottom + spacing;
128
+ left = rect.left + rect.width / 2;
41
129
  break;
42
130
  case "left":
43
- top = rect.top + scrollTop + rect.height / 2;
44
- left = rect.left + scrollLeft - spacing;
131
+ top = rect.top + rect.height / 2;
132
+ // Para left, a tooltip fica à esquerda do elemento
133
+ // left é onde a tooltip termina (borda direita), então subtrai a largura
134
+ left = rect.left - tooltipWidth - spacing;
45
135
  break;
46
136
  case "right":
47
- top = rect.top + scrollTop + rect.height / 2;
48
- left = rect.right + scrollLeft + spacing;
137
+ top = rect.top + rect.height / 2;
138
+ left = rect.right + spacing;
49
139
  break;
50
140
  default:
51
- top = rect.bottom + scrollTop + spacing;
52
- left = rect.left + scrollLeft + rect.width / 2;
53
- finalPosition = "bottom";
141
+ top = rect.bottom + spacing;
142
+ left = rect.left + rect.width / 2;
143
+ bestPosition = "bottom";
54
144
  }
55
- // Ajusta posição apenas se necessário para não sair da viewport
56
- // Mas mantém o mais próximo possível do elemento
145
+ finalPosition = bestPosition;
146
+ // Ajusta horizontalmente para tooltips top/bottom - mantém próximo ao elemento
57
147
  if (finalPosition === "bottom" || finalPosition === "top") {
58
- // Ajusta horizontalmente apenas se necessário, mantendo próximo ao centro do elemento
59
- const elementCenterX = rect.left + scrollLeft + rect.width / 2;
60
- const minLeft = scrollLeft + tooltipWidth / 2 + 8;
61
- const maxLeft = scrollLeft + viewportWidth - tooltipWidth / 2 - 8;
148
+ // Centraliza no elemento, mas ajusta apenas se necessário para não sair da tela
149
+ const elementCenterX = rect.left + rect.width / 2;
150
+ const minLeft = tooltipWidth / 2 + 8;
151
+ const maxLeft = viewportWidth - tooltipWidth / 2 - 8;
152
+ // Mantém o mais próximo possível do centro do elemento
62
153
  if (elementCenterX < minLeft) {
63
154
  left = minLeft;
64
155
  }
@@ -68,31 +159,43 @@ export function Tour({ enabled, steps, initialStep = 0, options = {}, onExit, on
68
159
  else {
69
160
  left = elementCenterX; // Mantém centralizado no elemento
70
161
  }
71
- // Verifica se precisa mudar de posição vertical
162
+ // Verifica espaço vertical e ajusta posição se necessário
72
163
  if (finalPosition === "bottom") {
73
- const spaceBelow = viewportHeight - (rect.bottom - scrollTop);
74
- const spaceAbove = rect.top - scrollTop;
75
- if (spaceBelow < tooltipHeight + spacing && spaceAbove > spaceBelow) {
76
- // Muda para cima se houver mais espaço
77
- top = rect.top + scrollTop - spacing;
78
- finalPosition = "top";
164
+ const spaceBelow = viewportHeight - rect.bottom;
165
+ if (spaceBelow < tooltipHeight + spacing + 8) {
166
+ const spaceAbove = rect.top;
167
+ if (spaceAbove > spaceBelow && spaceAbove > tooltipHeight + spacing + 8) {
168
+ top = rect.top - spacing;
169
+ finalPosition = "top";
170
+ }
171
+ else {
172
+ // Mantém próximo ao elemento mesmo se não couber perfeitamente
173
+ top = rect.bottom + spacing;
174
+ }
79
175
  }
80
176
  }
81
177
  else if (finalPosition === "top") {
82
- const spaceAbove = rect.top - scrollTop;
83
- const spaceBelow = viewportHeight - (rect.bottom - scrollTop);
84
- if (spaceAbove < tooltipHeight + spacing && spaceBelow > spaceAbove) {
85
- // Muda para baixo se houver mais espaço
86
- top = rect.bottom + scrollTop + spacing;
87
- finalPosition = "bottom";
178
+ const spaceAbove = rect.top;
179
+ if (spaceAbove < tooltipHeight + spacing + 8) {
180
+ const spaceBelow = viewportHeight - rect.bottom;
181
+ if (spaceBelow > spaceAbove && spaceBelow > tooltipHeight + spacing + 8) {
182
+ top = rect.bottom + spacing;
183
+ finalPosition = "bottom";
184
+ }
185
+ else {
186
+ // Mantém próximo ao elemento mesmo se não couber perfeitamente
187
+ top = rect.top - spacing;
188
+ }
88
189
  }
89
190
  }
90
191
  }
192
+ // Ajusta verticalmente para tooltips left/right - mantém próximo ao elemento
91
193
  else if (finalPosition === "left" || finalPosition === "right") {
92
- // Ajusta verticalmente apenas se necessário
93
- const elementCenterY = rect.top + scrollTop + rect.height / 2;
94
- const minTop = scrollTop + tooltipHeight / 2 + 8;
95
- const maxTop = scrollTop + viewportHeight - tooltipHeight / 2 - 8;
194
+ // Centraliza no elemento verticalmente
195
+ const elementCenterY = rect.top + rect.height / 2;
196
+ const minTop = tooltipHeight / 2 + 8;
197
+ const maxTop = viewportHeight - tooltipHeight / 2 - 8;
198
+ // Mantém o mais próximo possível do centro do elemento
96
199
  if (elementCenterY < minTop) {
97
200
  top = minTop;
98
201
  }
@@ -102,22 +205,38 @@ export function Tour({ enabled, steps, initialStep = 0, options = {}, onExit, on
102
205
  else {
103
206
  top = elementCenterY; // Mantém centralizado no elemento
104
207
  }
105
- // Verifica se precisa mudar de posição horizontal
208
+ // Verifica espaço horizontal e ajusta posição se necessário
106
209
  if (finalPosition === "left") {
107
- const spaceLeft = rect.left - scrollLeft;
108
- const spaceRight = viewportWidth - (rect.right - scrollLeft);
109
- if (spaceLeft < tooltipWidth + spacing && spaceRight > spaceLeft) {
110
- left = rect.right + scrollLeft + spacing;
111
- finalPosition = "right";
210
+ const spaceLeft = rect.left;
211
+ if (spaceLeft < tooltipWidth + spacing + 8) {
212
+ const spaceRight = viewportWidth - rect.right;
213
+ if (spaceRight > spaceLeft && spaceRight > tooltipWidth + spacing + 8) {
214
+ // Muda para direita se houver mais espaço
215
+ left = rect.right + spacing;
216
+ finalPosition = "right";
217
+ }
218
+ else {
219
+ // Mantém à esquerda, mas ajusta para ficar visível
220
+ left = Math.max(8, rect.left - tooltipWidth - spacing);
221
+ }
112
222
  }
223
+ // Se tem espaço suficiente, o left já foi calculado corretamente no switch acima
113
224
  }
114
225
  else if (finalPosition === "right") {
115
- const spaceRight = viewportWidth - (rect.right - scrollLeft);
116
- const spaceLeft = rect.left - scrollLeft;
117
- if (spaceRight < tooltipWidth + spacing && spaceLeft > spaceRight) {
118
- left = rect.left + scrollLeft - spacing;
119
- finalPosition = "left";
226
+ const spaceRight = viewportWidth - rect.right;
227
+ if (spaceRight < tooltipWidth + spacing + 8) {
228
+ const spaceLeft = rect.left;
229
+ if (spaceLeft > spaceRight && spaceLeft > tooltipWidth + spacing + 8) {
230
+ // Muda para esquerda se houver mais espaço
231
+ left = rect.left - tooltipWidth - spacing;
232
+ finalPosition = "left";
233
+ }
234
+ else {
235
+ // Mantém à direita, mas ajusta para ficar visível
236
+ left = Math.min(viewportWidth - tooltipWidth - 8, rect.right + spacing);
237
+ }
120
238
  }
239
+ // Se tem espaço suficiente, o left já foi calculado corretamente no switch acima
121
240
  }
122
241
  }
123
242
  return { top, left, position: finalPosition };
@@ -155,16 +274,34 @@ export function Tour({ enabled, steps, initialStep = 0, options = {}, onExit, on
155
274
  setHighlightedElement(element);
156
275
  // Scroll para o elemento
157
276
  element.scrollIntoView({ behavior: "smooth", block: "center" });
158
- // Calcula posição da tooltip após um delay para garantir que o scroll terminou
159
- // Usa um delay maior para garantir que o scroll terminou completamente
160
- setTimeout(() => {
161
- // Recalcula o rect após o scroll
277
+ // Função para calcular e atualizar posição
278
+ const updatePosition = () => {
162
279
  const updatedRect = element.getBoundingClientRect();
163
280
  if (updatedRect.width > 0 && updatedRect.height > 0) {
164
- const position = calculateTooltipPosition(element, step.position || "bottom");
281
+ // Usa "auto" como default se position não for especificado
282
+ const position = calculateTooltipPosition(element, step.position || "auto");
165
283
  setTooltipPosition(position);
166
284
  }
167
- }, 400);
285
+ };
286
+ // Calcula posição inicial após um delay para garantir que o scroll terminou
287
+ setTimeout(updatePosition, 400);
288
+ // Adiciona listeners para recalcular posição em tempo real
289
+ const handleScroll = () => {
290
+ // Usa requestAnimationFrame para suavizar a atualização durante scroll
291
+ requestAnimationFrame(updatePosition);
292
+ };
293
+ const handleResize = () => {
294
+ updatePosition();
295
+ };
296
+ // Adiciona listeners em window e document para capturar todos os tipos de scroll
297
+ window.addEventListener("scroll", handleScroll, { passive: true, capture: true });
298
+ window.addEventListener("resize", handleResize, { passive: true });
299
+ document.addEventListener("scroll", handleScroll, { passive: true, capture: true });
300
+ return () => {
301
+ window.removeEventListener("scroll", handleScroll, true);
302
+ window.removeEventListener("resize", handleResize);
303
+ document.removeEventListener("scroll", handleScroll, true);
304
+ };
168
305
  }, [isVisible, currentStep, steps, calculateTooltipPosition]);
169
306
  // Adiciona overlay e highlight ao elemento
170
307
  useEffect(() => {
@@ -12,9 +12,11 @@ export interface TourStep {
12
12
  intro: string;
13
13
  /**
14
14
  * Posição da tooltip em relação ao elemento
15
- * @default "bottom"
15
+ * - "auto": Calcula automaticamente a melhor posição baseada no espaço disponível
16
+ * - "top" | "bottom" | "left" | "right": Posição específica (pode ser ajustada se não houver espaço)
17
+ * @default "auto"
16
18
  */
17
- position?: "top" | "bottom" | "left" | "right";
19
+ position?: "auto" | "top" | "bottom" | "left" | "right";
18
20
  /**
19
21
  * Título do passo (opcional)
20
22
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAE/C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAExD;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,QAAQ,EAAE,CAAC;IAElB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elogroup-sereduc/portal-aluno-tour",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Componente de tour guiado customizado usando HeroUI para o Portal do Aluno",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,55 +34,142 @@ export function Tour({
34
34
  } = options;
35
35
 
36
36
  // Calcula a posição da tooltip baseado no elemento destacado
37
- const calculateTooltipPosition = useCallback((element: HTMLElement, position: string = "bottom") => {
38
- // Recalcula o rect após possíveis mudanças de scroll
37
+ // Usa getBoundingClientRect() que retorna posições relativas à viewport
38
+ // Isso mantém a tooltip sempre visível mesmo durante scroll
39
+ const calculateTooltipPosition = useCallback((element: HTMLElement, position: string | undefined = "auto") => {
39
40
  const rect = element.getBoundingClientRect();
40
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
41
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
42
41
  const viewportWidth = window.innerWidth;
43
42
  const viewportHeight = window.innerHeight;
44
43
 
45
44
  // Tamanho estimado da tooltip
46
45
  const tooltipWidth = 384; // max-w-sm = 384px
47
- const tooltipHeight = 250; // altura estimada com conteúdo
48
- const spacing = 8; // espaçamento menor para ficar mais próximo
46
+ const tooltipHeight = 250; // altura estimada
47
+ const spacing = 8; // espaçamento próximo ao elemento
49
48
 
50
49
  let top = 0;
51
50
  let left = 0;
52
51
  let finalPosition = position;
53
52
 
54
- // Calcula posição base - sempre próxima ao elemento
55
- switch (position) {
53
+ // Verifica se o elemento está visível na viewport
54
+ const isElementVisible =
55
+ rect.top < viewportHeight &&
56
+ rect.bottom > 0 &&
57
+ rect.left < viewportWidth &&
58
+ rect.right > 0;
59
+
60
+ // Se o elemento não estiver visível, tenta posicionar próximo à borda mais próxima
61
+ if (!isElementVisible) {
62
+ // Elemento está fora da viewport - posiciona próximo à borda mais próxima
63
+ if (rect.bottom < 0) {
64
+ // Elemento está acima da viewport
65
+ top = 8;
66
+ left = Math.max(tooltipWidth / 2 + 8, Math.min(viewportWidth - tooltipWidth / 2 - 8, rect.left + rect.width / 2));
67
+ finalPosition = "top";
68
+ } else if (rect.top > viewportHeight) {
69
+ // Elemento está abaixo da viewport
70
+ top = viewportHeight - tooltipHeight - 8;
71
+ left = Math.max(tooltipWidth / 2 + 8, Math.min(viewportWidth - tooltipWidth / 2 - 8, rect.left + rect.width / 2));
72
+ finalPosition = "bottom";
73
+ } else {
74
+ // Elemento está à esquerda ou direita
75
+ top = Math.max(tooltipHeight / 2 + 8, Math.min(viewportHeight - tooltipHeight / 2 - 8, rect.top + rect.height / 2));
76
+ if (rect.right < 0) {
77
+ left = 8;
78
+ finalPosition = "left";
79
+ } else {
80
+ left = viewportWidth - tooltipWidth - 8;
81
+ finalPosition = "right";
82
+ }
83
+ }
84
+ return { top, left, position: finalPosition };
85
+ }
86
+
87
+ // Calcula espaço disponível em todas as direções
88
+ const spaceAbove = rect.top;
89
+ const spaceBelow = viewportHeight - rect.bottom;
90
+ const spaceLeft = rect.left;
91
+ const spaceRight = viewportWidth - rect.right;
92
+
93
+ // Determina a melhor posição baseado no espaço disponível
94
+ let bestPosition: "top" | "bottom" | "left" | "right";
95
+
96
+ // Se position for "auto" ou não especificado, calcula automaticamente a melhor posição
97
+ if (!position || position === "auto") {
98
+ // Escolhe automaticamente a direção com mais espaço disponível
99
+ const maxSpace = Math.max(spaceAbove, spaceBelow, spaceLeft, spaceRight);
100
+ if (maxSpace === spaceAbove) bestPosition = "top";
101
+ else if (maxSpace === spaceBelow) bestPosition = "bottom";
102
+ else if (maxSpace === spaceLeft) bestPosition = "left";
103
+ else bestPosition = "right";
104
+ } else {
105
+ // Posição específica foi fornecida - tenta usar ela primeiro
106
+ // Verifica se a posição especificada tem espaço suficiente
107
+ const hasEnoughSpace =
108
+ (position === "top" && spaceAbove >= tooltipHeight + spacing + 8) ||
109
+ (position === "bottom" && spaceBelow >= tooltipHeight + spacing + 8) ||
110
+ (position === "left" && spaceLeft >= tooltipWidth + spacing + 8) ||
111
+ (position === "right" && spaceRight >= tooltipWidth + spacing + 8);
112
+
113
+ if (hasEnoughSpace) {
114
+ // Tem espaço suficiente, usa a posição especificada
115
+ bestPosition = position as "top" | "bottom" | "left" | "right";
116
+ } else {
117
+ // Não tem espaço suficiente na posição especificada, escolhe a melhor alternativa
118
+ // Para posições verticais (top/bottom), compara entre elas
119
+ if (position === "top" || position === "bottom") {
120
+ bestPosition = spaceAbove > spaceBelow ? "top" : "bottom";
121
+ }
122
+ // Para posições horizontais (left/right), compara entre elas
123
+ else if (position === "left" || position === "right") {
124
+ bestPosition = spaceLeft > spaceRight ? "left" : "right";
125
+ }
126
+ // Fallback: escolhe a direção com mais espaço
127
+ else {
128
+ const maxSpace = Math.max(spaceAbove, spaceBelow, spaceLeft, spaceRight);
129
+ if (maxSpace === spaceAbove) bestPosition = "top";
130
+ else if (maxSpace === spaceBelow) bestPosition = "bottom";
131
+ else if (maxSpace === spaceLeft) bestPosition = "left";
132
+ else bestPosition = "right";
133
+ }
134
+ }
135
+ }
136
+
137
+ // Calcula posição base na melhor direção escolhida
138
+ switch (bestPosition) {
56
139
  case "top":
57
- top = rect.top + scrollTop - spacing;
58
- left = rect.left + scrollLeft + rect.width / 2;
140
+ top = rect.top - spacing;
141
+ left = rect.left + rect.width / 2;
59
142
  break;
60
143
  case "bottom":
61
- top = rect.bottom + scrollTop + spacing;
62
- left = rect.left + scrollLeft + rect.width / 2;
144
+ top = rect.bottom + spacing;
145
+ left = rect.left + rect.width / 2;
63
146
  break;
64
147
  case "left":
65
- top = rect.top + scrollTop + rect.height / 2;
66
- left = rect.left + scrollLeft - spacing;
148
+ top = rect.top + rect.height / 2;
149
+ // Para left, a tooltip fica à esquerda do elemento
150
+ // left é onde a tooltip termina (borda direita), então subtrai a largura
151
+ left = rect.left - tooltipWidth - spacing;
67
152
  break;
68
153
  case "right":
69
- top = rect.top + scrollTop + rect.height / 2;
70
- left = rect.right + scrollLeft + spacing;
154
+ top = rect.top + rect.height / 2;
155
+ left = rect.right + spacing;
71
156
  break;
72
157
  default:
73
- top = rect.bottom + scrollTop + spacing;
74
- left = rect.left + scrollLeft + rect.width / 2;
75
- finalPosition = "bottom";
158
+ top = rect.bottom + spacing;
159
+ left = rect.left + rect.width / 2;
160
+ bestPosition = "bottom";
76
161
  }
77
162
 
78
- // Ajusta posição apenas se necessário para não sair da viewport
79
- // Mas mantém o mais próximo possível do elemento
163
+ finalPosition = bestPosition;
164
+
165
+ // Ajusta horizontalmente para tooltips top/bottom - mantém próximo ao elemento
80
166
  if (finalPosition === "bottom" || finalPosition === "top") {
81
- // Ajusta horizontalmente apenas se necessário, mantendo próximo ao centro do elemento
82
- const elementCenterX = rect.left + scrollLeft + rect.width / 2;
83
- const minLeft = scrollLeft + tooltipWidth / 2 + 8;
84
- const maxLeft = scrollLeft + viewportWidth - tooltipWidth / 2 - 8;
167
+ // Centraliza no elemento, mas ajusta apenas se necessário para não sair da tela
168
+ const elementCenterX = rect.left + rect.width / 2;
169
+ const minLeft = tooltipWidth / 2 + 8;
170
+ const maxLeft = viewportWidth - tooltipWidth / 2 - 8;
85
171
 
172
+ // Mantém o mais próximo possível do centro do elemento
86
173
  if (elementCenterX < minLeft) {
87
174
  left = minLeft;
88
175
  } else if (elementCenterX > maxLeft) {
@@ -91,32 +178,41 @@ export function Tour({
91
178
  left = elementCenterX; // Mantém centralizado no elemento
92
179
  }
93
180
 
94
- // Verifica se precisa mudar de posição vertical
181
+ // Verifica espaço vertical e ajusta posição se necessário
95
182
  if (finalPosition === "bottom") {
96
- const spaceBelow = viewportHeight - (rect.bottom - scrollTop);
97
- const spaceAbove = rect.top - scrollTop;
98
-
99
- if (spaceBelow < tooltipHeight + spacing && spaceAbove > spaceBelow) {
100
- // Muda para cima se houver mais espaço
101
- top = rect.top + scrollTop - spacing;
102
- finalPosition = "top";
183
+ const spaceBelow = viewportHeight - rect.bottom;
184
+ if (spaceBelow < tooltipHeight + spacing + 8) {
185
+ const spaceAbove = rect.top;
186
+ if (spaceAbove > spaceBelow && spaceAbove > tooltipHeight + spacing + 8) {
187
+ top = rect.top - spacing;
188
+ finalPosition = "top";
189
+ } else {
190
+ // Mantém próximo ao elemento mesmo se não couber perfeitamente
191
+ top = rect.bottom + spacing;
192
+ }
103
193
  }
104
194
  } else if (finalPosition === "top") {
105
- const spaceAbove = rect.top - scrollTop;
106
- const spaceBelow = viewportHeight - (rect.bottom - scrollTop);
107
-
108
- if (spaceAbove < tooltipHeight + spacing && spaceBelow > spaceAbove) {
109
- // Muda para baixo se houver mais espaço
110
- top = rect.bottom + scrollTop + spacing;
111
- finalPosition = "bottom";
195
+ const spaceAbove = rect.top;
196
+ if (spaceAbove < tooltipHeight + spacing + 8) {
197
+ const spaceBelow = viewportHeight - rect.bottom;
198
+ if (spaceBelow > spaceAbove && spaceBelow > tooltipHeight + spacing + 8) {
199
+ top = rect.bottom + spacing;
200
+ finalPosition = "bottom";
201
+ } else {
202
+ // Mantém próximo ao elemento mesmo se não couber perfeitamente
203
+ top = rect.top - spacing;
204
+ }
112
205
  }
113
206
  }
114
- } else if (finalPosition === "left" || finalPosition === "right") {
115
- // Ajusta verticalmente apenas se necessário
116
- const elementCenterY = rect.top + scrollTop + rect.height / 2;
117
- const minTop = scrollTop + tooltipHeight / 2 + 8;
118
- const maxTop = scrollTop + viewportHeight - tooltipHeight / 2 - 8;
207
+ }
208
+ // Ajusta verticalmente para tooltips left/right - mantém próximo ao elemento
209
+ else if (finalPosition === "left" || finalPosition === "right") {
210
+ // Centraliza no elemento verticalmente
211
+ const elementCenterY = rect.top + rect.height / 2;
212
+ const minTop = tooltipHeight / 2 + 8;
213
+ const maxTop = viewportHeight - tooltipHeight / 2 - 8;
119
214
 
215
+ // Mantém o mais próximo possível do centro do elemento
120
216
  if (elementCenterY < minTop) {
121
217
  top = minTop;
122
218
  } else if (elementCenterY > maxTop) {
@@ -125,23 +221,35 @@ export function Tour({
125
221
  top = elementCenterY; // Mantém centralizado no elemento
126
222
  }
127
223
 
128
- // Verifica se precisa mudar de posição horizontal
224
+ // Verifica espaço horizontal e ajusta posição se necessário
129
225
  if (finalPosition === "left") {
130
- const spaceLeft = rect.left - scrollLeft;
131
- const spaceRight = viewportWidth - (rect.right - scrollLeft);
132
-
133
- if (spaceLeft < tooltipWidth + spacing && spaceRight > spaceLeft) {
134
- left = rect.right + scrollLeft + spacing;
135
- finalPosition = "right";
226
+ const spaceLeft = rect.left;
227
+ if (spaceLeft < tooltipWidth + spacing + 8) {
228
+ const spaceRight = viewportWidth - rect.right;
229
+ if (spaceRight > spaceLeft && spaceRight > tooltipWidth + spacing + 8) {
230
+ // Muda para direita se houver mais espaço
231
+ left = rect.right + spacing;
232
+ finalPosition = "right";
233
+ } else {
234
+ // Mantém à esquerda, mas ajusta para ficar visível
235
+ left = Math.max(8, rect.left - tooltipWidth - spacing);
236
+ }
136
237
  }
238
+ // Se tem espaço suficiente, o left já foi calculado corretamente no switch acima
137
239
  } else if (finalPosition === "right") {
138
- const spaceRight = viewportWidth - (rect.right - scrollLeft);
139
- const spaceLeft = rect.left - scrollLeft;
140
-
141
- if (spaceRight < tooltipWidth + spacing && spaceLeft > spaceRight) {
142
- left = rect.left + scrollLeft - spacing;
143
- finalPosition = "left";
240
+ const spaceRight = viewportWidth - rect.right;
241
+ if (spaceRight < tooltipWidth + spacing + 8) {
242
+ const spaceLeft = rect.left;
243
+ if (spaceLeft > spaceRight && spaceLeft > tooltipWidth + spacing + 8) {
244
+ // Muda para esquerda se houver mais espaço
245
+ left = rect.left - tooltipWidth - spacing;
246
+ finalPosition = "left";
247
+ } else {
248
+ // Mantém à direita, mas ajusta para ficar visível
249
+ left = Math.min(viewportWidth - tooltipWidth - 8, rect.right + spacing);
250
+ }
144
251
  }
252
+ // Se tem espaço suficiente, o left já foi calculado corretamente no switch acima
145
253
  }
146
254
  }
147
255
 
@@ -187,16 +295,39 @@ export function Tour({
187
295
  // Scroll para o elemento
188
296
  element.scrollIntoView({ behavior: "smooth", block: "center" });
189
297
 
190
- // Calcula posição da tooltip após um delay para garantir que o scroll terminou
191
- // Usa um delay maior para garantir que o scroll terminou completamente
192
- setTimeout(() => {
193
- // Recalcula o rect após o scroll
298
+ // Função para calcular e atualizar posição
299
+ const updatePosition = () => {
194
300
  const updatedRect = element.getBoundingClientRect();
195
301
  if (updatedRect.width > 0 && updatedRect.height > 0) {
196
- const position = calculateTooltipPosition(element, step.position || "bottom");
302
+ // Usa "auto" como default se position não for especificado
303
+ const position = calculateTooltipPosition(element, step.position || "auto");
197
304
  setTooltipPosition(position);
198
305
  }
199
- }, 400);
306
+ };
307
+
308
+ // Calcula posição inicial após um delay para garantir que o scroll terminou
309
+ setTimeout(updatePosition, 400);
310
+
311
+ // Adiciona listeners para recalcular posição em tempo real
312
+ const handleScroll = () => {
313
+ // Usa requestAnimationFrame para suavizar a atualização durante scroll
314
+ requestAnimationFrame(updatePosition);
315
+ };
316
+
317
+ const handleResize = () => {
318
+ updatePosition();
319
+ };
320
+
321
+ // Adiciona listeners em window e document para capturar todos os tipos de scroll
322
+ window.addEventListener("scroll", handleScroll, { passive: true, capture: true });
323
+ window.addEventListener("resize", handleResize, { passive: true });
324
+ document.addEventListener("scroll", handleScroll, { passive: true, capture: true });
325
+
326
+ return () => {
327
+ window.removeEventListener("scroll", handleScroll, true);
328
+ window.removeEventListener("resize", handleResize);
329
+ document.removeEventListener("scroll", handleScroll, true);
330
+ };
200
331
  }, [isVisible, currentStep, steps, calculateTooltipPosition]);
201
332
 
202
333
  // Adiciona overlay e highlight ao elemento
@@ -15,9 +15,11 @@ export interface TourStep {
15
15
 
16
16
  /**
17
17
  * Posição da tooltip em relação ao elemento
18
- * @default "bottom"
18
+ * - "auto": Calcula automaticamente a melhor posição baseada no espaço disponível
19
+ * - "top" | "bottom" | "left" | "right": Posição específica (pode ser ajustada se não houver espaço)
20
+ * @default "auto"
19
21
  */
20
- position?: "top" | "bottom" | "left" | "right";
22
+ position?: "auto" | "top" | "bottom" | "left" | "right";
21
23
 
22
24
  /**
23
25
  * Título do passo (opcional)