From f56ccf5ef545432b8a18962ad1b7742ecb84aafc Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 24 Aug 2026 12:55:00 +0200 Subject: [PATCH] fix(app): keep tooltips inside the fullscreen element (#1968) --- .../ui/components/slider/useSliderTooltip.ts | 12 ++++++++-- .../src/features/ui/tooltip/Tooltip.tsx | 22 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/fluxer_app/src/features/ui/components/slider/useSliderTooltip.ts b/fluxer_app/src/features/ui/components/slider/useSliderTooltip.ts index 358a918d7..337e8a1d7 100644 --- a/fluxer_app/src/features/ui/components/slider/useSliderTooltip.ts +++ b/fluxer_app/src/features/ui/components/slider/useSliderTooltip.ts @@ -2,7 +2,7 @@ import Accessibility from '@app/features/accessibility/state/Accessibility'; import {Logger} from '@app/features/platform/utils/AppLogger'; -import {useTooltipPortalRoot} from '@app/features/ui/tooltip/Tooltip'; +import {getFullscreenOverflowBoundary, useTooltipPortalRoot} from '@app/features/ui/tooltip/Tooltip'; import {appZoomCssPx, appZoomLayoutPx} from '@app/features/ui/utils/AppZoomUtils'; import { getReducedMotionProps, @@ -101,9 +101,17 @@ export function useSliderTooltip({ left: '-9999px', top: '-9999px', }); - const middleware = [offset(8), flip(), shift({padding: 8}), arrow({element: arrowRef})]; + const overflowBoundary = getFullscreenOverflowBoundary(target); + const boundaryOptions = overflowBoundary ? {boundary: overflowBoundary} : undefined; + const middleware = [ + offset(8), + flip(boundaryOptions), + shift({padding: 8, ...boundaryOptions}), + arrow({element: arrowRef}), + ]; const {x, y, middlewareData} = await computePosition(target, tooltip, { placement: 'top', + strategy: 'fixed', middleware, }); Object.assign(tooltip.style, { diff --git a/fluxer_app/src/features/ui/tooltip/Tooltip.tsx b/fluxer_app/src/features/ui/tooltip/Tooltip.tsx index 0f022e7da..04b5ee513 100644 --- a/fluxer_app/src/features/ui/tooltip/Tooltip.tsx +++ b/fluxer_app/src/features/ui/tooltip/Tooltip.tsx @@ -26,6 +26,7 @@ import { subscribeWindowHoverControlsChange, } from '@app/features/ui/utils/WindowFocusInteractionGuard'; import {elementSupportsRef} from '@app/lib/react'; +import type {ExtendedDocument} from '@app/types/browser.d'; import {FloatingPortal} from '@floating-ui/react'; import {arrow, autoUpdate, computePosition, flip, offset, shift} from '@floating-ui/react-dom'; import {clsx} from 'clsx'; @@ -47,6 +48,20 @@ const TOOLTIP_AUTO_UPDATE_OPTIONS = { layoutShift: true, } as const; +export const getFullscreenOverflowBoundary = (target: HTMLElement): HTMLElement | null => { + const ownerDocument = target.ownerDocument as ExtendedDocument; + const ownerWindow = ownerDocument.defaultView; + if (!ownerWindow) return null; + const fullscreenElement = + ownerDocument.fullscreenElement ?? + ownerDocument.webkitFullscreenElement ?? + ownerDocument.mozFullScreenElement ?? + ownerDocument.msFullscreenElement ?? + null; + if (!(fullscreenElement instanceof ownerWindow.HTMLElement)) return null; + return fullscreenElement.contains(target) ? fullscreenElement : null; +}; + const tooltipPortalRoots = new WeakMap(); const getTooltipPortalRoot = (ownerDocument: Document): HTMLElement => { @@ -389,14 +404,17 @@ export const Tooltip = observer( left: '-9999px', top: '-9999px', } as CSSStyleDeclaration); + const overflowBoundary = getFullscreenOverflowBoundary(target); + const boundaryOptions = overflowBoundary ? {boundary: overflowBoundary} : undefined; const middleware = [ offset(padding + nudge), - flip(), - shift({padding: 8}), + flip(boundaryOptions), + shift({padding: 8, ...boundaryOptions}), ...(arrowRef.current ? [arrow({element: arrowRef.current})] : []), ]; const {x, y, placement, middlewareData} = await computePosition(target, tooltip, { placement: position, + strategy: 'fixed', middleware, }); if (