export function initModalControls() { document.addEventListener('click', e => { const closeBtn = e.target.closest('.modal-close'); if (!closeBtn) return; const modal = closeBtn.closest('.modal-overlay'); if (!modal) return; closeModal(modal); }); document.addEventListener('keydown', e => { const modal = getActiveModal(); if (!modal) return; if (e.code === 'Escape') { const closeBtn = modal.querySelector('.modal-close'); if (!closeBtn) return; e.preventDefault(); closeBtn.click(); return; } if (e.code === 'Enter') { const actionBtn = modal.querySelector('.modal-action'); if (!actionBtn) return; e.preventDefault(); actionBtn.click(); return; } if (e.code === 'Tab') { trapFocus(e, modal); } }); } export function openModal(modal) { modal.style.display = 'flex'; const primaryBtn = modal.querySelector('.modal-action'); const closeBtn = modal.querySelector('.modal-close'); (primaryBtn || closeBtn)?.focus(); } export function closeModal(modal) { modal.style.display = 'none'; modal.dispatchEvent(new CustomEvent('modal:closed')); } function getActiveModal() { const modals = [...document.querySelectorAll('.modal-overlay')]; return modals.find(modal => modal.style.display === 'flex') || null; } function trapFocus(e, modal) { const focusable = getFocusableElements(modal); if (!focusable.length) { e.preventDefault(); modal.focus(); return; } const first = focusable[0]; const last = focusable[focusable.length - 1]; const active = document.activeElement; if (!modal.contains(active)) { e.preventDefault(); first.focus(); return; } if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); } } function getFocusableElements(container) { const selector = [ 'button:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'a[href]', '[tabindex]:not([tabindex="-1"])', ].join(','); return [...container.querySelectorAll(selector)] .filter(el => !el.hidden && el.offsetParent !== null); }