diff --git a/src/hooks/useAlign.ts b/src/hooks/useAlign.ts index 679d0560..7e7839dd 100644 --- a/src/hooks/useAlign.ts +++ b/src/hooks/useAlign.ts @@ -189,7 +189,6 @@ export default function useAlign( const originOverflowX = popupElement.style.overflowX; const originOverflowY = popupElement.style.overflowY; - // Placement const placementInfo: AlignType = { ...builtinPlacements[placement], @@ -666,6 +665,19 @@ export default function useAlign( nextOffsetY += targetRect.y - visibleRegionArea.bottom + numShiftY; } } + + // 目标仍在平移范围内时,避免方向偏移量将弹层再次推到视口外。 + if ( + targetRect.y + targetHeight >= visibleRegionArea.top + numShiftY && + targetRect.y <= visibleRegionArea.bottom - numShiftY + ) { + const minOffsetY = visibleRegionArea.top - popupRect.y; + const maxOffsetY = Math.max( + minOffsetY, + visibleRegionArea.bottom - popupHeight - popupRect.y, + ); + nextOffsetY = Math.min(Math.max(nextOffsetY, minOffsetY), maxOffsetY); + } } // ============================ Arrow ============================ diff --git a/tests/flipShift.test.tsx b/tests/flipShift.test.tsx index 57e88e77..668a5953 100644 --- a/tests/flipShift.test.tsx +++ b/tests/flipShift.test.tsx @@ -80,8 +80,10 @@ const builtinPlacements = { describe('Trigger.Flip+Shift', () => { let spanRect = { x: 0, y: 0, left: 0, top: 0, width: 0, height: 0 }; + let popupHeight = 300; beforeEach(() => { + popupHeight = 300; spanRect = { x: 0, y: 100, @@ -118,7 +120,7 @@ describe('Trigger.Flip+Shift', () => { left: 0, top: 0, width: 100, - height: 300, + height: popupHeight, }; }, }); @@ -170,6 +172,59 @@ describe('Trigger.Flip+Shift', () => { }); }); + it.each([ + { placement: 'top', offset: -4, targetY: 100, top: '100px' }, + { placement: 'bottom', offset: 4, targetY: 100, top: '100px' }, + { placement: 'top', offset: -4, targetY: 200, top: '0px' }, + { placement: 'bottom', offset: 4, targetY: 200, top: '0px' }, + ])( + '$placement 在目标位于 $targetY 时,平移不应被偏移量推回视口外', + async ({ placement, offset, targetY, top }) => { + spanRect.y = targetY; + spanRect.top = targetY; + + render( + 日期面板} + > + + , + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(document.querySelector('.rc-trigger-popup')).toHaveStyle({ top }); + }, + ); + + it('面板高于视口时优先保留顶部内容', async () => { + popupHeight = 500; + render( + 日期面板} + > + + , + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect(document.querySelector('.rc-trigger-popup')).toHaveStyle({ + top: '0px', + }); + }); + it('top with visibleFirst region', async () => { spanRect.x = -1000; document.documentElement.scrollLeft = 500;