Fix Tooltip clipping in overflow-hidden containers — use fixed positioning

Switches from absolute (clipped by ancestor overflow:hidden) to fixed
with viewport-relative coords from getBoundingClientRect().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-16 15:22:27 +02:00
parent 35a34eb300
commit cbf0e4aa51
+11 -21
View File
@@ -13,7 +13,8 @@
let wrapperEl = $state<HTMLDivElement | null>(null); let wrapperEl = $state<HTMLDivElement | null>(null);
let showTooltip = $state(false); let showTooltip = $state(false);
let placement = $state<"top" | "bottom">("top"); let placement = $state<"top" | "bottom">("top");
let leftPx = $state<number | null>(null); // null = centered (CSS), number = clamped px from wrapper left let fixedLeft = $state(0);
let fixedTop = $state(0);
function updatePosition() { function updatePosition() {
if (!wrapperEl) return; if (!wrapperEl) return;
@@ -25,20 +26,13 @@
const spaceBelow = vh - rect.bottom; const spaceBelow = vh - rect.bottom;
placement = placement =
preferredPlacement === "top" preferredPlacement === "top"
? spaceAbove >= spaceBelow ? spaceAbove >= spaceBelow ? "top" : "bottom"
? "top" : spaceBelow >= spaceAbove ? "bottom" : "top";
: "bottom"
: spaceBelow >= spaceAbove
? "bottom"
: "top";
const iconCenterX = rect.left + rect.width / 2; const iconCenterX = rect.left + rect.width / 2;
const idealLeft = iconCenterX - TOOLTIP_MAX_WIDTH_PX / 2; const idealLeft = iconCenterX - TOOLTIP_MAX_WIDTH_PX / 2;
const clampedLeft = Math.max( fixedLeft = Math.max(TOOLTIP_GAP, Math.min(idealLeft, vw - TOOLTIP_MAX_WIDTH_PX - TOOLTIP_GAP));
TOOLTIP_GAP, fixedTop = placement === "top" ? rect.top - TOOLTIP_GAP : rect.bottom + TOOLTIP_GAP;
Math.min(idealLeft, vw - TOOLTIP_MAX_WIDTH_PX - TOOLTIP_GAP),
);
leftPx = clampedLeft - rect.left;
} }
function handleShow() { function handleShow() {
@@ -48,16 +42,12 @@
function handleHide() { function handleHide() {
showTooltip = false; showTooltip = false;
leftPx = null;
} }
const placementClasses = $derived( const tooltipStyle = $derived(
placement === "top" placement === "top"
? "bottom-full mb-2" ? `position:fixed;top:${fixedTop}px;left:${fixedLeft}px;transform:translateY(-100%);`
: "top-full mt-2", : `position:fixed;top:${fixedTop}px;left:${fixedLeft}px;`,
);
const horizontalStyle = $derived(
leftPx != null ? `left: ${leftPx}px; transform: none;` : "left: 50%; transform: translateX(-50%);",
); );
</script> </script>
@@ -74,8 +64,8 @@
{#if showTooltip} {#if showTooltip}
<div <div
role="tooltip" role="tooltip"
class="absolute z-50 {placementClasses} px-3 py-2 text-xs font-normal text-stein-0 bg-stein-800 rounded-xs shadow-lg min-w-56 max-w-88 text-center pointer-events-none whitespace-normal" class="z-[9999] px-3 py-2 text-xs font-normal text-stein-0 bg-stein-800 rounded-xs shadow-lg min-w-56 max-w-88 text-center pointer-events-none whitespace-normal"
style={horizontalStyle} style={tooltipStyle}
> >
{content} {content}
</div> </div>