Prevent accidental modal close on backdrop
This commit is contained in:
@@ -87,3 +87,11 @@ The frontend is a **single-page application** with no build step — plain HTML,
|
||||
| Tabulator | latest stable | CDN (`tabulator.info`) |
|
||||
|
||||
No bundler, no transpiler, no node_modules.
|
||||
|
||||
---
|
||||
|
||||
## Modal UX Rules
|
||||
|
||||
- Destructive or data-entry modals must not close on backdrop click.
|
||||
- Close such modals only via explicit controls: `Отмена`, `Закрыть`, or a top-right `×` button.
|
||||
- If a modal can be closed, provide a visible close button in the header (top-right `×`) in addition to the footer action when applicable.
|
||||
|
||||
@@ -244,7 +244,13 @@ function showImportErrorDialog(result, headers) {
|
||||
});
|
||||
|
||||
dialog.innerHTML = `
|
||||
<h3 style="margin-top: 0; color: #d32f2f;">📊 Результат импорта</h3>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 0; color: #d32f2f;">📊 Результат импорта</h3>
|
||||
<button id="importErrorX" type="button" aria-label="Закрыть"
|
||||
style="padding: 0; width: 32px; height: 32px; border: none; border-radius: 4px; background: transparent; cursor: pointer; font-size: 22px; line-height: 1; color: #666;">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
|
||||
<div style="flex: 1; padding: 15px; background: #e8f5e9; border-radius: 8px; text-align: center;">
|
||||
@@ -277,7 +283,7 @@ function showImportErrorDialog(result, headers) {
|
||||
modal.appendChild(dialog);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
|
||||
dialog.querySelector('#importErrorX').onclick = () => modal.remove();
|
||||
dialog.querySelector('#importErrorClose').onclick = () => modal.remove();
|
||||
|
||||
dialog.querySelector('#importErrorDownload').onclick = () => {
|
||||
@@ -371,7 +377,13 @@ async function showExportDialog() {
|
||||
}
|
||||
|
||||
dialog.innerHTML = `
|
||||
<h3 style="margin-top: 0;">📤 Экспорт данных</h3>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px;">
|
||||
<h3 style="margin-top: 0; margin-bottom: 0;">📤 Экспорт данных</h3>
|
||||
<button id="exportX" type="button" aria-label="Закрыть"
|
||||
style="padding: 0; width: 32px; height: 32px; border: none; border-radius: 4px; background: transparent; cursor: pointer; font-size: 22px; line-height: 1; color: #666;">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; border-bottom: 2px solid #ddd; margin-bottom: 15px;">
|
||||
<button id="tabCSV" class="export-tab" style="padding: 10px 20px; border: none; background: #4CAF50; color: white; cursor: pointer; border-radius: 4px 4px 0 0; margin-right: 5px;">
|
||||
@@ -461,7 +473,7 @@ async function showExportDialog() {
|
||||
panelBackup.style.display = 'block'; panelCSV.style.display = 'none';
|
||||
};
|
||||
|
||||
modal.onclick = (e) => { if (e.target === modal) modal.remove(); };
|
||||
dialog.querySelector('#exportX').onclick = () => modal.remove();
|
||||
dialog.querySelector('#exportClose').onclick = () => modal.remove();
|
||||
|
||||
if (hasTable) {
|
||||
|
||||
@@ -443,7 +443,13 @@ async function promptForRequiredFields(requiredFields, optionalFields) {
|
||||
`;
|
||||
|
||||
let html = `
|
||||
<h3 style="margin-top: 0; margin-bottom: 15px;">➕ Вставить запись в ${currentSchema}.${currentTable}</h3>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 15px;">
|
||||
<h3 style="margin: 0;">➕ Вставить запись в ${currentSchema}.${currentTable}</h3>
|
||||
<button id="insertClose" type="button" aria-label="Закрыть"
|
||||
style="padding: 0; width: 32px; height: 32px; border: none; border-radius: 4px; background: transparent; cursor: pointer; font-size: 22px; line-height: 1; color: #666;">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Обязательные поля
|
||||
@@ -478,21 +484,15 @@ async function promptForRequiredFields(requiredFields, optionalFields) {
|
||||
document.body.appendChild(modal);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
document.getElementById('insertCancel').addEventListener('click', () => {
|
||||
const closeInsertModal = () => {
|
||||
if (document.body.contains(modal)) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
};
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
if (document.body.contains(modal)) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
document.getElementById('insertCancel').addEventListener('click', closeInsertModal);
|
||||
document.getElementById('insertClose').addEventListener('click', closeInsertModal);
|
||||
|
||||
document.getElementById('insertSubmit').addEventListener('click', () => {
|
||||
const values = {};
|
||||
@@ -626,7 +626,13 @@ async function showEditModal(selectedRows) {
|
||||
`;
|
||||
|
||||
let html = `
|
||||
<h3 style="margin-top: 0; margin-bottom: 10px;">✏️ Редактирование ${isSingleRow ? 'записи' : `${count} записей`}</h3>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px;">
|
||||
<h3 style="margin: 0;">✏️ Редактирование ${isSingleRow ? 'записи' : `${count} записей`}</h3>
|
||||
<button id="editClose" type="button" aria-label="Закрыть"
|
||||
style="padding: 0; width: 32px; height: 32px; border: none; border-radius: 4px; background: transparent; cursor: pointer; font-size: 22px; line-height: 1; color: #666;">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (!isSingleRow) {
|
||||
@@ -738,19 +744,14 @@ async function showEditModal(selectedRows) {
|
||||
modal.appendChild(dialog);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
document.getElementById('editCancel').addEventListener('click', () => {
|
||||
const closeEditModal = () => {
|
||||
if (document.body.contains(modal)) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
if (document.body.contains(modal)) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
}
|
||||
});
|
||||
document.getElementById('editCancel').addEventListener('click', closeEditModal);
|
||||
document.getElementById('editClose').addEventListener('click', closeEditModal);
|
||||
|
||||
document.getElementById('editSubmit').addEventListener('click', async () => {
|
||||
const changes = {};
|
||||
|
||||
Reference in New Issue
Block a user