diff --git a/src/Dialog/index.tsx b/src/Dialog/index.tsx index 5e509097..f2237557 100644 --- a/src/Dialog/index.tsx +++ b/src/Dialog/index.tsx @@ -110,37 +110,31 @@ const Dialog: React.FC = (props) => { } // >>> Content - const contentClickRef = useRef(false); - const contentTimeoutRef = useRef>(null); - - // We need record content click in case content popup out of dialog - const onContentMouseDown: React.MouseEventHandler = () => { - clearTimeout(contentTimeoutRef.current); - contentClickRef.current = true; - }; - - const onContentMouseUp: React.MouseEventHandler = () => { - contentTimeoutRef.current = setTimeout(() => { - contentClickRef.current = false; - }); - }; + const mouseDownOnMaskRef = useRef(false); // >>> Wrapper // Close only when element not on dialog let onWrapperClick: (e: React.SyntheticEvent) => void = null; if (maskClosable) { onWrapperClick = (e) => { - if (contentClickRef.current) { - contentClickRef.current = false; - } else if (wrapperRef.current === e.target) { + if ( + onClose && + wrapperRef.current === e.target && + mouseDownOnMaskRef.current + ) { onInternalClose(e); } }; } + function onWrapperMouseDown(e: React.MouseEvent) { + mouseDownOnMaskRef.current = e.target === wrapperRef.current; + } + // ========================= Effect ========================= useEffect(() => { if (visible) { + mouseDownOnMaskRef.current = false; setAnimatedVisible(true); saveLastOutSideActiveElementRef(); @@ -158,14 +152,6 @@ const Dialog: React.FC = (props) => { } }, [visible]); - // Remove direct should also check the scroll bar update - useEffect( - () => () => { - clearTimeout(contentTimeoutRef.current); - }, - [], - ); - const mergedStyle: React.CSSProperties = { zIndex, ...wrapStyle, @@ -192,14 +178,13 @@ const Dialog: React.FC = (props) => { className={clsx(`${prefixCls}-wrap`, wrapClassName, modalClassNames?.wrapper)} ref={wrapperRef} onClick={onWrapperClick} + onMouseDown={onWrapperMouseDown} style={mergedStyle} {...wrapProps} > { const { rerender } = render(); // Mask close - fireEvent.click(document.querySelector('.rc-dialog-wrap')); + const mask = document.querySelector('.rc-dialog-wrap'); + fireEvent.mouseDown(mask); + fireEvent.mouseUp(mask); + fireEvent.click(mask); jest.runAllTimers(); expect(onClose).toHaveBeenCalled(); onClose.mockReset(); diff --git a/tests/mask-closable.spec.tsx b/tests/mask-closable.spec.tsx new file mode 100644 index 00000000..cb2b720e --- /dev/null +++ b/tests/mask-closable.spec.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { render, fireEvent, act } from '@testing-library/react'; +import Dialog from '../src'; + +describe('Dialog.MaskClosable', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should close when click on mask', () => { + const onClose = jest.fn(); + render( + + Content + + ); + + act(() => { + jest.runAllTimers(); + }); + + const mask = document.querySelector('.rc-dialog-wrap'); + if (!mask) throw new Error('Mask not found'); + + fireEvent.mouseDown(mask); + fireEvent.mouseUp(mask); + fireEvent.click(mask); + expect(onClose).toHaveBeenCalled(); + }); + + it('should not close when dragging from content to mask', () => { + const onClose = jest.fn(); + const { getByText } = render( + + Content + + ); + + act(() => { + jest.runAllTimers(); + }); + + const content = getByText('Content'); + const mask = document.querySelector('.rc-dialog-wrap'); + if (!mask) throw new Error('Mask not found'); + + // Simulate mouse down on content + fireEvent.mouseDown(content); + // Simulate mouse up on mask + fireEvent.mouseUp(mask); + // Simulate click on mask (since click follows mouseup) + fireEvent.click(mask); + + expect(onClose).not.toHaveBeenCalled(); + }); +});