let activeDialog = null; function ensureDialogRoot() { let root = document.getElementById('project-dialog-root'); if (!root) { root = document.createElement('div'); root.id = 'project-dialog-root'; document.body.appendChild(root); } return root; } export function showDialog({ title, message, confirmLabel = 'Подтвердить', cancelLabel = 'Отмена', danger = false, input = null }) { activeDialog?.close(false); const root = ensureDialogRoot(); const previousFocus = document.activeElement; root.innerHTML = `
`; const backdrop = root.firstElementChild; const dialog = backdrop.querySelector('.project-dialog'); const inputElement = dialog.querySelector('[data-dialog-input]'); return new Promise(resolve => { const close = result => { document.removeEventListener('keydown', handleKeydown); root.innerHTML = ''; activeDialog = null; previousFocus?.focus?.(); resolve(result); }; activeDialog = { close }; const cancel = () => close(input ? null : false); const confirm = () => { if (input?.required && !inputElement?.value.trim()) { dialog.querySelector('[data-dialog-error]').textContent = input.requiredMessage || 'Заполните поле'; inputElement?.focus(); return; } close(input ? inputElement.value.trim() : true); }; const handleKeydown = event => { if (event.key === 'Escape') cancel(); if (event.key === 'Tab') trapFocus(event, dialog); }; document.addEventListener('keydown', handleKeydown); dialog.querySelectorAll('[data-dialog-cancel]').forEach(button => button.addEventListener('click', cancel)); dialog.querySelector('[data-dialog-confirm]').addEventListener('click', confirm); backdrop.addEventListener('click', event => { if (event.target === backdrop) cancel(); }); window.setTimeout(() => (inputElement || dialog.querySelector('[data-dialog-confirm]')).focus(), 0); }); } export function confirmAction(options) { return showDialog(options); } export function promptAction(options) { return showDialog({ ...options, input: options.input || { label: 'Комментарий' } }); } export function alertAction(options) { return showDialog({ ...options, cancelLabel: null, confirmLabel: options.confirmLabel || 'Понятно' }); } function trapFocus(event, root) { const focusable = [...root.querySelectorAll('button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])')]; if (!focusable.length) return; const first = focusable[0]; const last = focusable.at(-1); if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } function escapeText(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); }