@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.
- package/dist/components/Tour.d.ts.map +1 -1
- package/dist/components/Tour.js +196 -59
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Tour.tsx +195 -64
- package/src/types/index.ts +4 -2
|
@@ -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,
|
|
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"}
|
package/dist/components/Tour.js
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
|
28
|
-
const spacing = 8; // espaçamento
|
|
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
|
-
//
|
|
33
|
-
|
|
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
|
|
36
|
-
left = rect.left +
|
|
123
|
+
top = rect.top - spacing;
|
|
124
|
+
left = rect.left + rect.width / 2;
|
|
37
125
|
break;
|
|
38
126
|
case "bottom":
|
|
39
|
-
top = rect.bottom +
|
|
40
|
-
left = rect.left +
|
|
127
|
+
top = rect.bottom + spacing;
|
|
128
|
+
left = rect.left + rect.width / 2;
|
|
41
129
|
break;
|
|
42
130
|
case "left":
|
|
43
|
-
top = rect.top +
|
|
44
|
-
|
|
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 +
|
|
48
|
-
left = rect.right +
|
|
137
|
+
top = rect.top + rect.height / 2;
|
|
138
|
+
left = rect.right + spacing;
|
|
49
139
|
break;
|
|
50
140
|
default:
|
|
51
|
-
top = rect.bottom +
|
|
52
|
-
left = rect.left +
|
|
53
|
-
|
|
141
|
+
top = rect.bottom + spacing;
|
|
142
|
+
left = rect.left + rect.width / 2;
|
|
143
|
+
bestPosition = "bottom";
|
|
54
144
|
}
|
|
55
|
-
|
|
56
|
-
//
|
|
145
|
+
finalPosition = bestPosition;
|
|
146
|
+
// Ajusta horizontalmente para tooltips top/bottom - mantém próximo ao elemento
|
|
57
147
|
if (finalPosition === "bottom" || finalPosition === "top") {
|
|
58
|
-
//
|
|
59
|
-
const elementCenterX = rect.left +
|
|
60
|
-
const minLeft =
|
|
61
|
-
const maxLeft =
|
|
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
|
|
162
|
+
// Verifica espaço vertical e ajusta posição se necessário
|
|
72
163
|
if (finalPosition === "bottom") {
|
|
73
|
-
const spaceBelow = viewportHeight -
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
93
|
-
const elementCenterY = rect.top +
|
|
94
|
-
const minTop =
|
|
95
|
-
const maxTop =
|
|
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
|
|
208
|
+
// Verifica espaço horizontal e ajusta posição se necessário
|
|
106
209
|
if (finalPosition === "left") {
|
|
107
|
-
const spaceLeft = rect.left
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 -
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
//
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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(() => {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
package/src/components/Tour.tsx
CHANGED
|
@@ -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
|
-
|
|
38
|
-
|
|
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
|
|
48
|
-
const spacing = 8; // espaçamento
|
|
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
|
-
//
|
|
55
|
-
|
|
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
|
|
58
|
-
left = rect.left +
|
|
140
|
+
top = rect.top - spacing;
|
|
141
|
+
left = rect.left + rect.width / 2;
|
|
59
142
|
break;
|
|
60
143
|
case "bottom":
|
|
61
|
-
top = rect.bottom +
|
|
62
|
-
left = rect.left +
|
|
144
|
+
top = rect.bottom + spacing;
|
|
145
|
+
left = rect.left + rect.width / 2;
|
|
63
146
|
break;
|
|
64
147
|
case "left":
|
|
65
|
-
top = rect.top +
|
|
66
|
-
|
|
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 +
|
|
70
|
-
left = rect.right +
|
|
154
|
+
top = rect.top + rect.height / 2;
|
|
155
|
+
left = rect.right + spacing;
|
|
71
156
|
break;
|
|
72
157
|
default:
|
|
73
|
-
top = rect.bottom +
|
|
74
|
-
left = rect.left +
|
|
75
|
-
|
|
158
|
+
top = rect.bottom + spacing;
|
|
159
|
+
left = rect.left + rect.width / 2;
|
|
160
|
+
bestPosition = "bottom";
|
|
76
161
|
}
|
|
77
162
|
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
//
|
|
82
|
-
const elementCenterX = rect.left +
|
|
83
|
-
const minLeft =
|
|
84
|
-
const maxLeft =
|
|
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
|
|
181
|
+
// Verifica espaço vertical e ajusta posição se necessário
|
|
95
182
|
if (finalPosition === "bottom") {
|
|
96
|
-
const spaceBelow = viewportHeight -
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
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
|
|
224
|
+
// Verifica espaço horizontal e ajusta posição se necessário
|
|
129
225
|
if (finalPosition === "left") {
|
|
130
|
-
const spaceLeft = rect.left
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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 -
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
//
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
package/src/types/index.ts
CHANGED
|
@@ -15,9 +15,11 @@ export interface TourStep {
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Posição da tooltip em relação ao elemento
|
|
18
|
-
*
|
|
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)
|