) : body;
let containerStyle = Object.assign({}, this.props.style.display ? {display: this.props.style.display} : {}, this.props.backgroundStyle);
return this.props.modal ?
-
{ this.mask = mask; }} onClick={this.onClickOut} style={containerStyle} className={"fade in modal " + this.props.containerClassName} role="dialog">
+
{ this.mask = mask; }} onMouseDown={this.onMouseDown} onClick={this.onClickOut} style={containerStyle} className={"fade in modal " + this.props.containerClassName} role="dialog">
{dialog}
:
dialog;
@@ -101,8 +106,13 @@ class Dialog extends React.Component {
hasRole = (role) => {
return React.Children.toArray(this.props.children).filter((child) => child.props.role === role).length > 0;
};
+
+ onMouseDown = (e) => {
+ this.mouseDownInside = this.mask && this.mask.contains(e.target) && this.mask !== e.target;
+ };
+
onClickOut = (e) => {
- if (this.props.onClickOut && this.mask === e.target) {
+ if (this.props.onClickOut && this.mask === e.target && !this.mouseDownInside) {
this.props.onClickOut(e);
}
};
diff --git a/web/client/components/misc/__tests__/Dialog-test.jsx b/web/client/components/misc/__tests__/Dialog-test.jsx
new file mode 100644
index 00000000000..6b7fab6b0ce
--- /dev/null
+++ b/web/client/components/misc/__tests__/Dialog-test.jsx
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2026, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import ReactTestUtils from 'react-dom/test-utils';
+import expect from 'expect';
+
+import Dialog from '../Dialog';
+
+describe('Dialog component', () => {
+ beforeEach((done) => {
+ document.body.innerHTML = '
';
+ setTimeout(done);
+ });
+
+ afterEach((done) => {
+ ReactDOM.unmountComponentAtNode(document.getElementById("container"));
+ document.body.innerHTML = '';
+ setTimeout(done);
+ });
+
+ it('renders with defaults', () => {
+ ReactDOM.render(
, document.getElementById('container'));
+ const dialog = document.getElementById('dialog-test');
+ expect(dialog).toExist();
+
+ const container = document.querySelector('.modal-dialog-container');
+ expect(container).toExist();
+ expect(container.className).toInclude('modal-dialog modal-content');
+ expect(container.className).toInclude('modal-dialog-draggable');
+ });
+
+ it('renders with modal', () => {
+ ReactDOM.render(
, document.getElementById('container'));
+ const modal = document.querySelector('.modal');
+ expect(modal).toExist();
+ expect(modal.className).toInclude('fade in modal');
+
+ const dialog = document.getElementById('dialog-test');
+ expect(dialog).toExist();
+ });
+
+ it('renders roles (header, body, footer)', () => {
+ ReactDOM.render(
+
,
+ document.getElementById('container')
+ );
+
+ const header = document.querySelector('.modal-header .test-header');
+ expect(header).toExist();
+
+ const body = document.querySelector('.modal-body .test-body');
+ expect(body).toExist();
+
+ const footer = document.querySelector('.modal-footer .test-footer');
+ expect(footer).toExist();
+ });
+
+ it('renders without draggable', () => {
+ ReactDOM.render(
, document.getElementById('container'));
+ const dialog = document.getElementById('dialog-test');
+ expect(dialog).toExist();
+ expect(dialog.className.indexOf('modal-dialog-draggable')).toBe(-1);
+ });
+
+ it('renders with maskLoading', () => {
+ ReactDOM.render(
+
,
+ document.getElementById('container')
+ );
+
+ const spinner = document.querySelector('.spinner');
+ expect(spinner).toExist();
+ });
+
+ it('triggers onClickOut when clicking on the mask', () => {
+ const actions = {
+ onClickOut: () => {}
+ };
+ const spy = expect.spyOn(actions, 'onClickOut');
+
+ ReactDOM.render(
+
,
+ document.getElementById('container')
+ );
+
+ const modal = document.querySelector('.modal');
+ expect(modal).toExist();
+
+ ReactTestUtils.Simulate.click(modal);
+
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it('does not trigger onClickOut when clicking inside the dialog', () => {
+ const actions = {
+ onClickOut: () => {}
+ };
+ const spy = expect.spyOn(actions, 'onClickOut');
+
+ ReactDOM.render(
+
,
+ document.getElementById('container')
+ );
+
+ const modal = document.querySelector('.modal');
+ expect(modal).toExist();
+
+ const innerButton = document.getElementById('inner-button');
+ expect(innerButton).toExist();
+
+ ReactTestUtils.Simulate.click(innerButton);
+
+ expect(spy).toNotHaveBeenCalled();
+ });
+});