diff --git a/docs/examples/container.tsx b/docs/examples/container.tsx index c1a17166..dd6fbb45 100644 --- a/docs/examples/container.tsx +++ b/docs/examples/container.tsx @@ -153,7 +153,7 @@ export default () => { } popupStyle={{ boxShadow: '0 0 5px red' }} - popupVisible + // popupVisible // getPopupContainer={() => popHolderRef.current} popupPlacement={popupPlacement} builtinPlacements={builtinPlacements} diff --git a/src/index.tsx b/src/index.tsx index 1e2c254f..a2541450 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,9 +17,9 @@ import useWatch from './hooks/useWatch'; import type { ActionType, AlignType, + AnimationType, ArrowType, ArrowTypeOuter, - AnimationType, BuildInPlacements, TransitionNameType, } from './interface'; @@ -27,7 +27,12 @@ import Popup from './Popup'; import TriggerWrapper from './TriggerWrapper'; import { getAlignPopupClassName, getMotion, getWin } from './util'; -export type { BuildInPlacements, AlignType, ActionType, ArrowTypeOuter as ArrowType }; +export type { + BuildInPlacements, + AlignType, + ActionType, + ArrowTypeOuter as ArrowType, +}; export interface TriggerRef { forceAlign: VoidFunction; @@ -495,8 +500,16 @@ export function generateTrigger( // Click to hide is special action since click popup element should not hide React.useEffect(() => { if (clickToHide && popupEle && (!mask || maskClosable)) { + let clickInside = false; + + // User may mouseDown inside and drag out of popup and mouse up + // Record here to prevent close + const onWindowMouseDown = ({ target }: MouseEvent) => { + clickInside = inPopupOrChild(target); + }; + const onWindowClick = ({ target }: MouseEvent) => { - if (openRef.current && !inPopupOrChild(target)) { + if (openRef.current && !clickInside && !inPopupOrChild(target)) { triggerOpen(false); } }; @@ -505,11 +518,16 @@ export function generateTrigger( const targetRoot = targetEle?.getRootNode(); + win.addEventListener('mousedown', onWindowMouseDown); win.addEventListener('click', onWindowClick); // shadow root const inShadow = targetRoot && targetRoot !== targetEle.ownerDocument; if (inShadow) { + (targetRoot as ShadowRoot).addEventListener( + 'mousedown', + onWindowMouseDown, + ); (targetRoot as ShadowRoot).addEventListener('click', onWindowClick); } @@ -524,9 +542,14 @@ export function generateTrigger( } return () => { + win.removeEventListener('mousedown', onWindowMouseDown); win.removeEventListener('click', onWindowClick); if (inShadow) { + (targetRoot as ShadowRoot).removeEventListener( + 'mousedown', + onWindowMouseDown, + ); (targetRoot as ShadowRoot).removeEventListener( 'click', onWindowClick, @@ -627,12 +650,14 @@ export function generateTrigger( ...passedProps, }); - const innerArrow: ArrowType = arrow ? { - // true and Object likely - ...(arrow !== true ? arrow : {}), - x: arrowX, - y: arrowY - }: null; + const innerArrow: ArrowType = arrow + ? { + // true and Object likely + ...(arrow !== true ? arrow : {}), + x: arrowX, + y: arrowY, + } + : null; // Render return ( diff --git a/tests/basic.test.jsx b/tests/basic.test.jsx index 93c128a8..69887c6a 100644 --- a/tests/basic.test.jsx +++ b/tests/basic.test.jsx @@ -936,4 +936,56 @@ describe('Trigger.Basic', () => { await awaitFakeTimer(); expect(document.querySelector('.rc-trigger-popup-hidden')).toBeTruthy(); }); + + describe('click window to hide', () => { + it('should hide', async () => { + const onPopupVisibleChange = jest.fn(); + + const { container } = render( + trigger} + > +
+ , + ); + + fireEvent.click(container.querySelector('.target')); + await awaitFakeTimer(); + expect(onPopupVisibleChange).toHaveBeenCalledWith(true); + onPopupVisibleChange.mockReset(); + + // Click outside to close + fireEvent.mouseDown(document.body); + fireEvent.click(document.body); + await awaitFakeTimer(); + expect(onPopupVisibleChange).toHaveBeenCalledWith(false); + }); + + it('should not hide when mouseDown inside but mouseUp outside', async () => { + const onPopupVisibleChange = jest.fn(); + + const { container } = render( + trigger} + > +
+ , + ); + + fireEvent.click(container.querySelector('.target')); + await awaitFakeTimer(); + expect(onPopupVisibleChange).toHaveBeenCalledWith(true); + onPopupVisibleChange.mockReset(); + + // Click outside to close + fireEvent.mouseDown(document.querySelector('strong')); + fireEvent.click(document.body); + await awaitFakeTimer(); + expect(onPopupVisibleChange).not.toHaveBeenCalled(); + }); + }); });