Skip to content

Commit 3b6ed02

Browse files
authored
#12570 - Fix: Dialog closes unexpectedly when selecting text (#12575)
1 parent 33ff2f0 commit 3b6ed02

2 files changed

Lines changed: 142 additions & 2 deletions

File tree

web/client/components/misc/Dialog.jsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class Dialog extends React.Component {
4848
bounds: 'parent'
4949
};
5050

51+
constructor(props) {
52+
super(props);
53+
this.mouseDownInside = false;
54+
}
55+
5156
renderLoading = () => {
5257
if (this.props.maskLoading) {
5358
return (<div style={{
@@ -92,7 +97,7 @@ class Dialog extends React.Component {
9297
</Draggable>) : body;
9398
let containerStyle = Object.assign({}, this.props.style.display ? {display: this.props.style.display} : {}, this.props.backgroundStyle);
9499
return this.props.modal ?
95-
<div ref={(mask) => { this.mask = mask; }} onClick={this.onClickOut} style={containerStyle} className={"fade in modal " + this.props.containerClassName} role="dialog">
100+
<div ref={(mask) => { this.mask = mask; }} onMouseDown={this.onMouseDown} onClick={this.onClickOut} style={containerStyle} className={"fade in modal " + this.props.containerClassName} role="dialog">
96101
{dialog}
97102
</div> :
98103
dialog;
@@ -101,8 +106,13 @@ class Dialog extends React.Component {
101106
hasRole = (role) => {
102107
return React.Children.toArray(this.props.children).filter((child) => child.props.role === role).length > 0;
103108
};
109+
110+
onMouseDown = (e) => {
111+
this.mouseDownInside = this.mask && this.mask.contains(e.target) && this.mask !== e.target;
112+
};
113+
104114
onClickOut = (e) => {
105-
if (this.props.onClickOut && this.mask === e.target) {
115+
if (this.props.onClickOut && this.mask === e.target && !this.mouseDownInside) {
106116
this.props.onClickOut(e);
107117
}
108118
};
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2026, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import React from 'react';
10+
import ReactDOM from 'react-dom';
11+
import ReactTestUtils from 'react-dom/test-utils';
12+
import expect from 'expect';
13+
14+
import Dialog from '../Dialog';
15+
16+
describe('Dialog component', () => {
17+
beforeEach((done) => {
18+
document.body.innerHTML = '<div id="container"></div>';
19+
setTimeout(done);
20+
});
21+
22+
afterEach((done) => {
23+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
24+
document.body.innerHTML = '';
25+
setTimeout(done);
26+
});
27+
28+
it('renders with defaults', () => {
29+
ReactDOM.render(<Dialog id="dialog-test" />, document.getElementById('container'));
30+
const dialog = document.getElementById('dialog-test');
31+
expect(dialog).toExist();
32+
33+
const container = document.querySelector('.modal-dialog-container');
34+
expect(container).toExist();
35+
expect(container.className).toInclude('modal-dialog modal-content');
36+
expect(container.className).toInclude('modal-dialog-draggable');
37+
});
38+
39+
it('renders with modal', () => {
40+
ReactDOM.render(<Dialog id="dialog-test" modal />, document.getElementById('container'));
41+
const modal = document.querySelector('.modal');
42+
expect(modal).toExist();
43+
expect(modal.className).toInclude('fade in modal');
44+
45+
const dialog = document.getElementById('dialog-test');
46+
expect(dialog).toExist();
47+
});
48+
49+
it('renders roles (header, body, footer)', () => {
50+
ReactDOM.render(
51+
<Dialog id="dialog-test">
52+
<div role="header"><span className="test-header">Header</span></div>
53+
<div role="body"><span className="test-body">Body</span></div>
54+
<div role="footer"><span className="test-footer">Footer</span></div>
55+
</Dialog>,
56+
document.getElementById('container')
57+
);
58+
59+
const header = document.querySelector('.modal-header .test-header');
60+
expect(header).toExist();
61+
62+
const body = document.querySelector('.modal-body .test-body');
63+
expect(body).toExist();
64+
65+
const footer = document.querySelector('.modal-footer .test-footer');
66+
expect(footer).toExist();
67+
});
68+
69+
it('renders without draggable', () => {
70+
ReactDOM.render(<Dialog id="dialog-test" draggable={false} />, document.getElementById('container'));
71+
const dialog = document.getElementById('dialog-test');
72+
expect(dialog).toExist();
73+
expect(dialog.className.indexOf('modal-dialog-draggable')).toBe(-1);
74+
});
75+
76+
it('renders with maskLoading', () => {
77+
ReactDOM.render(
78+
<Dialog id="dialog-test" maskLoading>
79+
<div role="body">Body</div>
80+
</Dialog>,
81+
document.getElementById('container')
82+
);
83+
84+
const spinner = document.querySelector('.spinner');
85+
expect(spinner).toExist();
86+
});
87+
88+
it('triggers onClickOut when clicking on the mask', () => {
89+
const actions = {
90+
onClickOut: () => {}
91+
};
92+
const spy = expect.spyOn(actions, 'onClickOut');
93+
94+
ReactDOM.render(
95+
<Dialog id="dialog-test" modal onClickOut={actions.onClickOut} />,
96+
document.getElementById('container')
97+
);
98+
99+
const modal = document.querySelector('.modal');
100+
expect(modal).toExist();
101+
102+
ReactTestUtils.Simulate.click(modal);
103+
104+
expect(spy).toHaveBeenCalled();
105+
});
106+
107+
it('does not trigger onClickOut when clicking inside the dialog', () => {
108+
const actions = {
109+
onClickOut: () => {}
110+
};
111+
const spy = expect.spyOn(actions, 'onClickOut');
112+
113+
ReactDOM.render(
114+
<Dialog id="dialog-test" modal onClickOut={actions.onClickOut}>
115+
<div role="body"><button id="inner-button">Click</button></div>
116+
</Dialog>,
117+
document.getElementById('container')
118+
);
119+
120+
const modal = document.querySelector('.modal');
121+
expect(modal).toExist();
122+
123+
const innerButton = document.getElementById('inner-button');
124+
expect(innerButton).toExist();
125+
126+
ReactTestUtils.Simulate.click(innerButton);
127+
128+
expect(spy).toNotHaveBeenCalled();
129+
});
130+
});

0 commit comments

Comments
 (0)