icynet-auth-server/src/fe/ts/modal/modal.ts

125 lines
3.2 KiB
TypeScript
Raw Normal View History

2022-03-20 14:50:12 +00:00
const isVisible = (elem: HTMLElement) =>
!!elem &&
!!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
export class Modal {
public triggers?: NodeListOf<HTMLElement>;
public modal?: HTMLElement;
2022-03-20 14:50:12 +00:00
public modalContent?: 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();
2022-03-20 14:50:12 +00:00
this.removeClickListener();
}
public open(): void {
if (!this.modal) {
return;
}
this.modal.style.display = 'block';
this.createFocusLock();
2022-03-20 14:50:12 +00:00
setTimeout(() =>
document.addEventListener('click', this.outsideClickListener),
);
}
public initialize(): void {
this.triggers = document.querySelectorAll(
`[data-modal-trigger="${this.name}"]`,
) as NodeListOf<HTMLElement>;
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');
2022-03-20 14:50:12 +00:00
this.modalContent = this.modal.querySelector('.modal__content');
this.modal.setAttribute('aria-modal', 'true');
2022-03-20 14:50:12 +00:00
this.modal.setAttribute('role', 'dialog');
this.modal.setAttribute('aria-labelledby', attrLabel);
label.setAttribute('id', attrLabel);
}
}
2022-03-20 14:50:12 +00:00
private outsideClickListener = (event: Event) => {
if (
!this.modalContent.contains(event.target as HTMLElement) &&
isVisible(this.modalContent)
) {
this.reset();
this.removeClickListener();
}
};
private removeClickListener = () => {
document.removeEventListener('click', this.outsideClickListener);
};
private getFocusable(): HTMLElement[] {
const focusable = Array.from(
this.modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
) as NodeListOf<HTMLElement>,
).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();
}
}