Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/hooks/useAlign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ export default function useAlign(
const resetFlipCache = () => {
prevFlipRef.current = {};
};
// The flip decision is relative to the visible area. When the viewport size
// changes (window resize, zoom, or a temporary viewport rewrite), the memo is
// meaningless - clear it so the next align is computed from scratch.
const prevViewportRef = React.useRef<{
width: number;
height: number;
} | null>(null);

if (!open) {
resetFlipCache();
Expand All @@ -189,7 +196,6 @@ export default function useAlign(
const originOverflowX = popupElement.style.overflowX;
const originOverflowY = popupElement.style.overflowY;


// Placement
const placementInfo: AlignType = {
...builtinPlacements[placement],
Expand Down Expand Up @@ -245,6 +251,15 @@ export default function useAlign(
scrollLeft,
} = doc.documentElement;

if (
!prevViewportRef.current ||
prevViewportRef.current.width !== clientWidth ||
prevViewportRef.current.height !== clientHeight
) {
prevViewportRef.current = { width: clientWidth, height: clientHeight };
resetFlipCache();
}

const popupHeight = popupRect.height;
const popupWidth = popupRect.width;

Expand Down
70 changes: 70 additions & 0 deletions tests/flip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,76 @@ describe('Trigger.Align', () => {
});
});

it('re-align when the viewport grows back after a flip', async () => {
// Popup 50px tall so that both sides fit once the viewport grows
popupRect = { x: 0, y: 0, left: 0, top: 0, width: 100, height: 50 };
spanRect.y = 90;

render(
<Trigger
popupVisible
popupPlacement="bottom"
builtinPlacements={builtinPlacements}
popup={<strong>trigger</strong>}
>
<span className="target" />
</Trigger>,
);

await flush();

// No room below in a 100px high viewport, so it flips above
expect(
document.querySelector('.rc-trigger-popup-placement-top'),
).toBeTruthy();

// Viewport grows: there is room below again, the flip must not be reused
eleRect = { width: 100, height: 200 };
act(() => {
window.dispatchEvent(new Event('resize'));
});
await flush();

expect(
document.querySelector('.rc-trigger-popup-placement-bottom'),
).toBeTruthy();
});

it('re-align horizontally when the viewport grows back after a flip', async () => {
// Popup 50px wide so that both sides fit once the viewport grows
popupRect = { x: 0, y: 0, left: 0, top: 0, width: 50, height: 100 };
spanRect.x = 90;

render(
<Trigger
popupVisible
popupPlacement="right"
builtinPlacements={builtinPlacements}
popup={<strong>trigger</strong>}
>
<span className="target" />
</Trigger>,
);

await flush();

// No room on the right in a 100px wide viewport, so it flips to the left
expect(
document.querySelector('.rc-trigger-popup-placement-left'),
).toBeTruthy();

// Viewport widens: there is room on the right again, the flip must not be reused
eleRect = { width: 200, height: 100 };
act(() => {
window.dispatchEvent(new Event('resize'));
});
await flush();

expect(
document.querySelector('.rc-trigger-popup-placement-right'),
).toBeTruthy();
});

// `getPopupContainer` sometime makes the popup 0/0 not start at left top.
// We need cal the real visible position
/*
Expand Down