Unify modals

This commit is contained in:
2026-04-22 18:26:31 +02:00
parent 7c0d856081
commit 093b5c2ef0
6 changed files with 337 additions and 49 deletions
+146
View File
@@ -0,0 +1,146 @@
(function () {
var backdrop = null;
var modal = null;
var titleEl = null;
var bodyEl = null;
var cancelBtn = null;
var confirmBtn = null;
var footerEl = null;
var prevFocus = null;
var currentOpts = null;
var onKeydown = null;
function build() {
backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop';
modal = document.createElement('div');
modal.className = 'modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
var header = document.createElement('div');
header.className = 'modal-header';
titleEl = document.createElement('span');
header.appendChild(titleEl);
bodyEl = document.createElement('div');
bodyEl.className = 'modal-body';
footerEl = document.createElement('div');
footerEl.className = 'modal-footer';
cancelBtn = document.createElement('button');
cancelBtn.type = 'button';
confirmBtn = document.createElement('button');
confirmBtn.type = 'button';
modal.appendChild(header);
modal.appendChild(bodyEl);
modal.appendChild(footerEl);
backdrop.appendChild(modal);
backdrop.addEventListener('mousedown', function (e) {
if (e.target === backdrop) close();
});
cancelBtn.addEventListener('click', close);
confirmBtn.addEventListener('click', function () {
if (confirmBtn.disabled) return;
if (currentOpts && currentOpts.confirm && currentOpts.confirm.onConfirm) {
currentOpts.confirm.onConfirm();
}
});
}
function open(opts) {
if (backdrop && backdrop.parentNode) close();
if (!backdrop) build();
currentOpts = opts;
prevFocus = document.activeElement;
titleEl.textContent = opts.title || '';
bodyEl.textContent = '';
if (opts.body instanceof Node) {
bodyEl.appendChild(opts.body);
} else if (typeof opts.body === 'string') {
bodyEl.textContent = opts.body;
}
var confirmOpts = opts.confirm || {};
var cancelOpts = opts.cancel || {};
confirmBtn.textContent = confirmOpts.label || 'OK';
confirmBtn.className = 'btn' + (confirmOpts.danger ? ' danger' : '');
confirmBtn.disabled = !!confirmOpts.initiallyDisabled;
cancelBtn.textContent = cancelOpts.label || 'CANCEL';
cancelBtn.className = 'btn';
footerEl.textContent = '';
if (opts.swapButtons) {
footerEl.appendChild(confirmBtn);
footerEl.appendChild(cancelBtn);
} else {
footerEl.appendChild(cancelBtn);
footerEl.appendChild(confirmBtn);
}
document.body.appendChild(backdrop);
setTimeout(function () {
if (cancelOpts.autofocus) {
cancelBtn.focus();
return;
}
var firstInput = bodyEl.querySelector('input, textarea, select');
if (firstInput) {
firstInput.focus();
if (firstInput.select) firstInput.select();
} else {
confirmBtn.focus();
}
}, 0);
var enterConfirms = confirmOpts.enterConfirms !== false;
onKeydown = function (e) {
if (e.key === 'Escape') {
e.preventDefault();
close();
return;
}
if (e.key === 'Enter' && enterConfirms) {
var tag = (e.target && e.target.tagName) || '';
if (tag === 'TEXTAREA') return;
e.preventDefault();
if (!confirmBtn.disabled) confirmBtn.click();
}
};
document.addEventListener('keydown', onKeydown);
return {
close: close,
setConfirmDisabled: function (d) { confirmBtn.disabled = !!d; },
confirmButton: confirmBtn
};
}
function close() {
if (!backdrop || !backdrop.parentNode) return;
if (onKeydown) {
document.removeEventListener('keydown', onKeydown);
onKeydown = null;
}
backdrop.parentNode.removeChild(backdrop);
currentOpts = null;
var toRestore = prevFocus;
prevFocus = null;
if (toRestore && toRestore.focus) {
try { toRestore.focus(); } catch (e) {}
}
}
window.openModal = open;
window.closeModal = close;
})();