export class Modal { public triggers?: NodeListOf; public modal?: HTMLElement; protected focusLock: HTMLElement[] = []; protected trigger?: HTMLElement; constructor(public name: string) {} public reset(): void { if (!this.modal) { return; } this.modal.style.display = 'none'; this.removeFocusLock(); } public open(): void { if (!this.modal) { return; } this.modal.style.display = 'block'; this.createFocusLock(); } public initialize(): void { this.triggers = document.querySelectorAll( `[data-modal-trigger="${this.name}"]`, ) as NodeListOf; this.modal = document.querySelector( `[data-modal="${this.name}"]`, ) as HTMLElement; this.triggers.forEach((item) => item.addEventListener('click', (evt) => { evt.preventDefault(); this.trigger = item; this.open(); }), ); if (this.modal) { const attrLabel = `modal_${this.name}_label`; const label = this.modal.querySelector('.modal__title'); this.modal.setAttribute('aria-modal', 'true'); this.modal.setAttribute('aria-labelledby', attrLabel); label.setAttribute('id', attrLabel); } } private getFocusable(): HTMLElement[] { const focusable = Array.from( this.modal.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ) as NodeListOf, ).filter( (item) => item.offsetParent !== null && !this.focusLock.includes(item), ); const firstFocusable = focusable[0]; const lastFocusable = focusable[focusable.length - 1]; return [firstFocusable, lastFocusable]; } private createFocusLock(): void { const startFocus = document.createElement('div'); startFocus.setAttribute('tabindex', '0'); const stopFocus = document.createElement('div'); stopFocus.setAttribute('tabindex', '0'); this.modal.prepend(startFocus); this.modal.appendChild(stopFocus); this.focusLock = [startFocus, stopFocus]; stopFocus.addEventListener('focus', (event) => { event.preventDefault(); this.getFocusable()[0].focus(); }); startFocus.addEventListener('focus', (event) => { event.preventDefault(); this.getFocusable()[1].focus(); }); this.getFocusable()[0].focus(); } private removeFocusLock(): void { this.focusLock.forEach((item) => { item.parentElement.removeChild(item); }); this.trigger?.focus(); } }