fix(app): keep tooltips inside the fullscreen element (#1968)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:55:00 +02:00
1 parent 51e2eee15a
commit f56ccf5ef5
2 files changed
+30 -4

No files matched your search

@@ -2,7 +2,7 @@
import Accessibility from '@app/features/accessibility/state/Accessibility'; import Accessibility from '@app/features/accessibility/state/Accessibility';
import {Logger} from '@app/features/platform/utils/AppLogger'; 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 {appZoomCssPx, appZoomLayoutPx} from '@app/features/ui/utils/AppZoomUtils';
import { import {
getReducedMotionProps, getReducedMotionProps,
@@ -101,9 +101,17 @@ export function useSliderTooltip({
left: '-9999px', left: '-9999px',
top: '-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, { const {x, y, middlewareData} = await computePosition(target, tooltip, {
placement: 'top', placement: 'top',
strategy: 'fixed',
middleware, middleware,
}); });
Object.assign(tooltip.style, { Object.assign(tooltip.style, {
+20 -2
View File
@@ -26,6 +26,7 @@ import {
subscribeWindowHoverControlsChange, subscribeWindowHoverControlsChange,
} from '@app/features/ui/utils/WindowFocusInteractionGuard'; } from '@app/features/ui/utils/WindowFocusInteractionGuard';
import {elementSupportsRef} from '@app/lib/react'; import {elementSupportsRef} from '@app/lib/react';
import type {ExtendedDocument} from '@app/types/browser.d';
import {FloatingPortal} from '@floating-ui/react'; import {FloatingPortal} from '@floating-ui/react';
import {arrow, autoUpdate, computePosition, flip, offset, shift} from '@floating-ui/react-dom'; import {arrow, autoUpdate, computePosition, flip, offset, shift} from '@floating-ui/react-dom';
import {clsx} from 'clsx'; import {clsx} from 'clsx';
@@ -47,6 +48,20 @@ const TOOLTIP_AUTO_UPDATE_OPTIONS = {
layoutShift: true, layoutShift: true,
} as const; } 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<Document, HTMLElement>(); const tooltipPortalRoots = new WeakMap<Document, HTMLElement>();
const getTooltipPortalRoot = (ownerDocument: Document): HTMLElement => { const getTooltipPortalRoot = (ownerDocument: Document): HTMLElement => {
@@ -389,14 +404,17 @@ export const Tooltip = observer(
left: '-9999px', left: '-9999px',
top: '-9999px', top: '-9999px',
} as CSSStyleDeclaration); } as CSSStyleDeclaration);
const overflowBoundary = getFullscreenOverflowBoundary(target);
const boundaryOptions = overflowBoundary ? {boundary: overflowBoundary} : undefined;
const middleware = [ const middleware = [
offset(padding + nudge), offset(padding + nudge),
flip(), flip(boundaryOptions),
shift({padding: 8}), shift({padding: 8, ...boundaryOptions}),
...(arrowRef.current ? [arrow({element: arrowRef.current})] : []), ...(arrowRef.current ? [arrow({element: arrowRef.current})] : []),
]; ];
const {x, y, placement, middlewareData} = await computePosition(target, tooltip, { const {x, y, placement, middlewareData} = await computePosition(target, tooltip, {
placement: position, placement: position,
strategy: 'fixed',
middleware, middleware,
}); });
if ( if (