Better fix on mobile tooltips

This commit is contained in:
Nathan Schneider
2025-11-29 17:10:02 -05:00
parent af52f32330
commit bcc6727917

View File

@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { tick } from 'svelte';
export let text: string; export let text: string;
let showTooltip = false; let showTooltip = false;
@@ -8,12 +10,13 @@
let wrapperElement: HTMLSpanElement; let wrapperElement: HTMLSpanElement;
let tooltipPosition = { align: 'center', vertical: 'top' }; let tooltipPosition = { align: 'center', vertical: 'top' };
function handleTouchStart() { async function handleTouchStart() {
isLongPress = false; isLongPress = false;
touchTimer = window.setTimeout(() => { touchTimer = window.setTimeout(async () => {
showTooltip = true; showTooltip = true;
isLongPress = true; isLongPress = true;
updateTooltipPosition(); await tick();
await updateTooltipPosition();
}, 500); // Show after 500ms press }, 500); // Show after 500ms press
} }
@@ -27,13 +30,14 @@
} }
} }
function handleClick() { async function handleClick() {
// Toggle tooltip on click/tap // Toggle tooltip on click/tap
if (!isLongPress) { if (!isLongPress) {
showTooltip = !showTooltip; showTooltip = !showTooltip;
// Auto-hide after 3 seconds // Auto-hide after 3 seconds
if (showTooltip) { if (showTooltip) {
updateTooltipPosition(); await tick();
await updateTooltipPosition();
setTimeout(() => { setTimeout(() => {
showTooltip = false; showTooltip = false;
}, 3000); }, 3000);
@@ -42,34 +46,48 @@
isLongPress = false; isLongPress = false;
} }
function handleMouseEnter() { async function handleMouseEnter() {
showTooltip = true; showTooltip = true;
// Use requestAnimationFrame to ensure DOM is updated before positioning await tick();
requestAnimationFrame(() => updateTooltipPosition()); await updateTooltipPosition();
} }
function handleMouseLeave() { function handleMouseLeave() {
showTooltip = false; showTooltip = false;
} }
function updateTooltipPosition() { async function updateTooltipPosition() {
// Wait for next frame to ensure rendering is complete
await new Promise(resolve => requestAnimationFrame(resolve));
if (!tooltipElement || !wrapperElement) return; if (!tooltipElement || !wrapperElement) return;
const tooltipRect = tooltipElement.getBoundingClientRect();
const wrapperRect = wrapperElement.getBoundingClientRect(); const wrapperRect = wrapperElement.getBoundingClientRect();
const tooltipRect = tooltipElement.getBoundingClientRect();
const viewportWidth = window.innerWidth; const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight; const viewportHeight = window.innerHeight;
const padding = 10; // Minimum padding from screen edge const padding = 10;
// Check horizontal overflow // Calculate tooltip width and position
const tooltipWidth = tooltipRect.width;
const wrapperCenterX = wrapperRect.left + wrapperRect.width / 2;
// Determine horizontal alignment
let align = 'center'; let align = 'center';
if (tooltipRect.left < padding) {
// Check if center alignment would overflow
const centerLeft = wrapperCenterX - tooltipWidth / 2;
const centerRight = wrapperCenterX + tooltipWidth / 2;
if (centerLeft < padding) {
// Would overflow left, align to left edge of wrapper
align = 'left'; align = 'left';
} else if (tooltipRect.right > viewportWidth - padding) { } else if (centerRight > viewportWidth - padding) {
// Would overflow right, align to right edge of wrapper
align = 'right'; align = 'right';
} }
// Check vertical overflow (if tooltip would go above viewport) // Check vertical overflow
let vertical = 'top'; let vertical = 'top';
if (tooltipRect.top < padding) { if (tooltipRect.top < padding) {
vertical = 'bottom'; vertical = 'bottom';
@@ -128,11 +146,13 @@
padding: 0.5rem; padding: 0.5rem;
z-index: 1000; z-index: 1000;
width: max-content; width: max-content;
max-width: 300px; max-width: min(300px, calc(100vw - 20px));
font-size: 0.85rem; font-size: 0.85rem;
line-height: 1.4; line-height: 1.4;
text-align: center; text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
word-wrap: break-word;
white-space: normal;
} }
/* Horizontal alignment variants */ /* Horizontal alignment variants */
@@ -143,6 +163,7 @@
.tooltip-text.align-left { .tooltip-text.align-left {
left: 0; left: 0;
right: auto;
transform: none; transform: none;
} }
@@ -218,7 +239,6 @@
@media (max-width: 768px) { @media (max-width: 768px) {
.tooltip-text { .tooltip-text {
max-width: calc(100vw - 20px);
font-size: 0.8rem; font-size: 0.8rem;
} }
} }