Skip to content

Commit 550b271

Browse files
zkt2002zkt
andauthored
fix(Dialog): prevent mask close when dragging from content to mask (#543)
* fix(Dialog): prevent mask close when dragging from content to mask - Use event capture to track `mousedown` and `mouseup` on the wrapper - Ensure close is only triggered when the click action fully occurs on the mask - Fix issue where dragging from content to mask (e.g. slider or text selection) triggers unintended close Closes ant-design/ant-design#56348 * refactor: optimize logic and reset refs based on review * style: optimize code formatting and naming convention * fix: prevent modal close when dragging from content to mask - Use bubbling `onMouseDown` to track if the click originated from the mask. - Only trigger close in `onClick` if both mousedown and click targets are the wrapper. - Reset tracking refs when dialog becomes visible to prevent state pollution. - Remove capture listeners to avoid potential event conflicts. * fix(Dialog): 移除 maskClosable 条件下不必要的 onClose 存在性检查,确保拖拽行为不会意外触发对话框关闭。同时将相关测试从独立文件合并到主测试文件中以简化结构。 --------- Co-authored-by: zkt <Zhengkangtao@leadigital.cn>
1 parent 9339853 commit 550b271

File tree

2 files changed

+36
-28
lines changed

2 files changed

+36
-28
lines changed

src/Dialog/index.tsx

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -110,37 +110,27 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {
110110
}
111111

112112
// >>> Content
113-
const contentClickRef = useRef(false);
114-
const contentTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
115-
116-
// We need record content click in case content popup out of dialog
117-
const onContentMouseDown: React.MouseEventHandler = () => {
118-
clearTimeout(contentTimeoutRef.current);
119-
contentClickRef.current = true;
120-
};
121-
122-
const onContentMouseUp: React.MouseEventHandler = () => {
123-
contentTimeoutRef.current = setTimeout(() => {
124-
contentClickRef.current = false;
125-
});
126-
};
113+
const mouseDownOnMaskRef = useRef(false);
127114

128115
// >>> Wrapper
129116
// Close only when element not on dialog
130117
let onWrapperClick: (e: React.SyntheticEvent) => void = null;
131118
if (maskClosable) {
132119
onWrapperClick = (e) => {
133-
if (contentClickRef.current) {
134-
contentClickRef.current = false;
135-
} else if (wrapperRef.current === e.target) {
120+
if (wrapperRef.current === e.target && mouseDownOnMaskRef.current) {
136121
onInternalClose(e);
137122
}
138123
};
139124
}
140125

126+
function onWrapperMouseDown(e: React.MouseEvent) {
127+
mouseDownOnMaskRef.current = e.target === wrapperRef.current;
128+
}
129+
141130
// ========================= Effect =========================
142131
useEffect(() => {
143132
if (visible) {
133+
mouseDownOnMaskRef.current = false;
144134
setAnimatedVisible(true);
145135
saveLastOutSideActiveElementRef();
146136

@@ -158,14 +148,6 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {
158148
}
159149
}, [visible]);
160150

161-
// Remove direct should also check the scroll bar update
162-
useEffect(
163-
() => () => {
164-
clearTimeout(contentTimeoutRef.current);
165-
},
166-
[],
167-
);
168-
169151
const mergedStyle: React.CSSProperties = {
170152
zIndex,
171153
...wrapStyle,
@@ -192,14 +174,13 @@ const Dialog: React.FC<IDialogPropTypes> = (props) => {
192174
className={clsx(`${prefixCls}-wrap`, wrapClassName, modalClassNames?.wrapper)}
193175
ref={wrapperRef}
194176
onClick={onWrapperClick}
177+
onMouseDown={onWrapperMouseDown}
195178
style={mergedStyle}
196179
{...wrapProps}
197180
>
198181
<Content
199182
{...props}
200183
isFixedPos={isFixedPos}
201-
onMouseDown={onContentMouseDown}
202-
onMouseUp={onContentMouseUp}
203184
ref={contentRef}
204185
closable={closable}
205186
ariaId={ariaId}

tests/index.spec.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ describe('dialog', () => {
173173
const { rerender } = render(<Dialog onClose={onClose} visible />);
174174

175175
// Mask close
176-
fireEvent.click(document.querySelector('.rc-dialog-wrap'));
176+
const mask = document.querySelector('.rc-dialog-wrap');
177+
fireEvent.mouseDown(mask);
178+
fireEvent.mouseUp(mask);
179+
fireEvent.click(mask);
177180
jest.runAllTimers();
178181
expect(onClose).toHaveBeenCalled();
179182
onClose.mockReset();
@@ -185,6 +188,30 @@ describe('dialog', () => {
185188
expect(onClose).not.toHaveBeenCalled();
186189
});
187190

191+
it('should not close when dragging from content to mask', () => {
192+
const onClose = jest.fn();
193+
const { getByText } = render(
194+
<Dialog visible maskClosable onClose={onClose}>
195+
Content
196+
</Dialog>
197+
);
198+
199+
jest.runAllTimers();
200+
201+
const content = getByText('Content');
202+
const mask = document.querySelector('.rc-dialog-wrap');
203+
if (!mask) throw new Error('Mask not found');
204+
205+
// Simulate mouse down on content
206+
fireEvent.mouseDown(content);
207+
// Simulate mouse up on mask
208+
fireEvent.mouseUp(mask);
209+
// Simulate click on mask (since click follows mouseup)
210+
fireEvent.click(mask);
211+
212+
expect(onClose).not.toHaveBeenCalled();
213+
});
214+
188215
it('renderToBody', () => {
189216
const container = document.createElement('div');
190217
document.body.appendChild(container);

0 commit comments

Comments
 (0)