diff --git a/packages/client-mobile/src/components/OnboardingTour.vue b/packages/client-mobile/src/components/OnboardingTour.vue index d24dadc..f0e5e64 100644 --- a/packages/client-mobile/src/components/OnboardingTour.vue +++ b/packages/client-mobile/src/components/OnboardingTour.vue @@ -46,7 +46,7 @@ const highlightStyle = computed(() => { }; }); -// Tooltip position +// Tooltip position with boundary check const tooltipStyle = computed(() => { const step = props.steps[props.currentStep]; if (!step) return {}; @@ -63,24 +63,41 @@ const tooltipStyle = computed(() => { const pos = step.position || 'bottom'; const rect = targetRect.value; const gap = 16; + const margin = 16; // Screen edge margin + const tooltipWidth = 300; + const screenWidth = window.innerWidth; + + // Calculate horizontal position with boundary check + function getHorizontalPosition() { + const centerX = rect.left + rect.width / 2; + let left = centerX - tooltipWidth / 2; + + // Check left boundary + if (left < margin) { + left = margin; + } + // Check right boundary + if (left + tooltipWidth > screenWidth - margin) { + left = screenWidth - margin - tooltipWidth; + } + return `${left}px`; + } switch (pos) { case 'top': return { bottom: `${window.innerHeight - rect.top + gap}px`, - left: `${rect.left + rect.width / 2}px`, - transform: 'translateX(-50%)', + left: getHorizontalPosition(), }; case 'bottom': return { top: `${rect.bottom + gap}px`, - left: `${rect.left + rect.width / 2}px`, - transform: 'translateX(-50%)', + left: getHorizontalPosition(), }; case 'left': return { top: `${rect.top + rect.height / 2}px`, - right: `${window.innerWidth - rect.left + gap}px`, + right: `${screenWidth - rect.left + gap}px`, transform: 'translateY(-50%)', }; case 'right':