95 lines
2.2 KiB
JavaScript
95 lines
2.2 KiB
JavaScript
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);
|
|
}
|