Keep centered popovers inside the viewport and place them before paint
Measure the popover in a hidden, out-of-flow state and position it in a layout effect, so opening it no longer grows the page for a frame. Centered (top/bottom) popovers are clamped to stay within the viewport. Also ignore /test and /.tmp. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -14,6 +14,17 @@ type Props = TWUI_MODAL_PROPS & {
|
||||
popoverContentActiveRef: React.MutableRefObject<boolean>;
|
||||
};
|
||||
|
||||
/**
|
||||
* How the popover renders before it has been placed: out of the flow and
|
||||
* invisible, so it can be measured without moving anything.
|
||||
*/
|
||||
const UnplacedStyle: React.CSSProperties = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
visibility: "hidden",
|
||||
};
|
||||
|
||||
/**
|
||||
* # Modal Main Component
|
||||
*/
|
||||
@@ -30,16 +41,32 @@ export default function PopoverComponent({
|
||||
isPopover,
|
||||
...props
|
||||
}: Props) {
|
||||
const [style, setStyle] = React.useState({});
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const [style, setStyle] = React.useState<React.CSSProperties | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open && targetElRef?.current) {
|
||||
const popoverStyle = twuiGrabPopoverStyles({
|
||||
/**
|
||||
* Placed before the browser paints, against the popover's own size:
|
||||
* until then it is rendered out of the flow and hidden, so opening it
|
||||
* never grows the page for a frame (a scrollbar appearing and the
|
||||
* layout jumping).
|
||||
*/
|
||||
React.useLayoutEffect(() => {
|
||||
if (!open) {
|
||||
setStyle(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetElRef?.current) return;
|
||||
|
||||
setStyle(
|
||||
twuiGrabPopoverStyles({
|
||||
position,
|
||||
targetElRef,
|
||||
});
|
||||
setStyle(popoverStyle);
|
||||
}
|
||||
contentEl: contentRef.current,
|
||||
}),
|
||||
);
|
||||
}, [open, targetElRef, position]);
|
||||
|
||||
const closeTimeoutRef = React.useRef<any>(null);
|
||||
@@ -79,7 +106,8 @@ export default function PopoverComponent({
|
||||
"twui-popover-content",
|
||||
props.className,
|
||||
)}
|
||||
style={{ ...style, ...props.style }}
|
||||
componentRef={contentRef}
|
||||
style={{ ...(style || UnplacedStyle), ...props.style }}
|
||||
onMouseEnter={
|
||||
trigger === "hover" ? popoverEnterFn : props.onMouseEnter
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user