From b723dff93ae5fd959f4931ac226c7ab97c635f30 Mon Sep 17 00:00:00 2001 From: Karel-Jan Van Haute Date: Fri, 14 Jun 2024 16:45:43 +0200 Subject: [PATCH] Add option to give extra class to modal dialog Fixes #350 --- tailoff/js/components/modal.component.ts | 255 ++++++++++------------- 1 file changed, 107 insertions(+), 148 deletions(-) diff --git a/tailoff/js/components/modal.component.ts b/tailoff/js/components/modal.component.ts index 68c63d5b..a7ee6f28 100644 --- a/tailoff/js/components/modal.component.ts +++ b/tailoff/js/components/modal.component.ts @@ -1,10 +1,7 @@ -import { SiteLang } from "../utils/site-lang"; -import { A11yUtils } from "../utils/a11y"; -import "wicg-inert"; -import { - ModalPlugin, - ModalPluginConstructor, -} from "../plugins/modal/plugin.interface"; +import { SiteLang } from '../utils/site-lang'; +import { A11yUtils } from '../utils/a11y'; +import 'wicg-inert'; +import { ModalPlugin, ModalPluginConstructor } from '../plugins/modal/plugin.interface'; export class ModalComponent { private siteLang = SiteLang.getLang(); @@ -46,30 +43,19 @@ export class ModalComponent { this.getLang(); this.options = { ...this.options, ...options }; - this.mainContentBlock = document.getElementById("mainContentBlock"); - this.bodyElement = document.getElementsByTagName( - "BODY" - )[0] as HTMLBodyElement; - - this.options.plugins.forEach( - ( - plugin: - | ModalPluginConstructor - | { plugin: ModalPluginConstructor; options: {} } - ) => { - const p = - typeof plugin == "function" - ? new plugin(this) - : new plugin.plugin(this); - if (this.options.initTriggers) { - p.initElement(); - } - this.plugins.push(p); + this.mainContentBlock = document.getElementById('mainContentBlock'); + this.bodyElement = document.getElementsByTagName('BODY')[0] as HTMLBodyElement; + + this.options.plugins.forEach((plugin: ModalPluginConstructor | { plugin: ModalPluginConstructor; options: {} }) => { + const p = typeof plugin == 'function' ? new plugin(this) : new plugin.plugin(this); + if (this.options.initTriggers) { + p.initElement(); } - ); + this.plugins.push(p); + }); if (this.options.initTriggers) { - const triggers = document.querySelectorAll(".js-modal"); + const triggers = document.querySelectorAll('.js-modal'); Array.from(triggers).forEach((trigger) => { this.initTrigger(trigger); }); @@ -87,8 +73,8 @@ export class ModalComponent { } private initTrigger(trigger: Element) { - trigger.setAttribute("role", "button"); - trigger.addEventListener("click", (e) => { + trigger.setAttribute('role', 'button'); + trigger.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); @@ -108,26 +94,22 @@ export class ModalComponent { private openModalClick(trigger: HTMLElement) { this.trigger = trigger; - if (trigger.classList.contains("js-modal")) { - if (trigger.tagName === "A") { - this.openInlineModal( - (trigger as HTMLAnchorElement).getAttribute("href") - ); + if (trigger.classList.contains('js-modal')) { + if (trigger.tagName === 'A') { + this.openInlineModal((trigger as HTMLAnchorElement).getAttribute('href')); } else { - const id = trigger.getAttribute("data-modal-id"); - id - ? this.openInlineModal(id) - : console.log("No modal id is provided on the trigger"); + const id = trigger.getAttribute('data-modal-id'); + id ? this.openInlineModal(id) : console.log('No modal id is provided on the trigger'); } } - document.body.classList.add("has-open-modal"); + document.body.classList.add('has-open-modal'); } public getTriggerSrc(trigger: Element) { - if (trigger.tagName === "A") { - return (trigger as HTMLAnchorElement).getAttribute("href"); + if (trigger.tagName === 'A') { + return (trigger as HTMLAnchorElement).getAttribute('href'); } else { - const src = trigger.getAttribute("data-modal-src"); + const src = trigger.getAttribute('data-modal-src'); return src ? src : null; } } @@ -139,15 +121,15 @@ export class ModalComponent { this.inlineContentWrapper = document.querySelector(id); if (this.inlineContentWrapper) { // this.modalContent.insertAdjacentHTML("afterbegin", content.innerHTML); + if (this.inlineContentWrapper.hasAttribute('data-dialog-class')) { + this.modal.classList.add(this.inlineContentWrapper.getAttribute('data-dialog-class')); + } Array.from(this.inlineContentWrapper.children).forEach((element) => { - this.modalContent.insertAdjacentElement("beforeend", element); + this.modalContent.insertAdjacentElement('beforeend', element); }); this.linkAccesebilityToDialog(); } else { - this.modalContent.insertAdjacentHTML( - "afterbegin", - `

Error

${this.lang.contentError}

` - ); + this.modalContent.insertAdjacentHTML('afterbegin', `

Error

${this.lang.contentError}

`); } this.trapTab(); @@ -163,48 +145,45 @@ export class ModalComponent { } public createOverlay() { - this.modalOverlay = document.createElement("div"); - this.modalOverlay.classList.add("modal__overlay"); + this.modalOverlay = document.createElement('div'); + this.modalOverlay.classList.add('modal__overlay'); if (this.options.allowClose) { - this.modalOverlay.addEventListener("click", () => { + this.modalOverlay.addEventListener('click', () => { this.closeModal(); }); } - this.bodyElement.insertAdjacentElement("beforeend", this.modalOverlay); + this.bodyElement.insertAdjacentElement('beforeend', this.modalOverlay); } - public createModal(dialogClass = "", modalContentClass = "modal__content") { - this.modal = document.createElement("div"); - this.modal.classList.add("modal__dialog"); - dialogClass != "" && this.modal.classList.add(dialogClass); - this.modal.setAttribute("role", "dialog"); + public createModal(dialogClass = '', modalContentClass = 'modal__content') { + this.modal = document.createElement('div'); + this.modal.classList.add('modal__dialog'); + dialogClass != '' && this.modal.classList.add(dialogClass); + this.modal.setAttribute('role', 'dialog'); // this.modal.setAttribute("aria-selected", "true"); - this.modal.setAttribute("aria-label", this.lang.dialogLabel); + this.modal.setAttribute('aria-label', this.lang.dialogLabel); if (this.options.allowClose) { - this.modalClose = document.createElement("button"); - this.modalClose.classList.add("modal__close"); - this.modalClose.setAttribute("type", "button"); + this.modalClose = document.createElement('button'); + this.modalClose.classList.add('modal__close'); + this.modalClose.setAttribute('type', 'button'); // this.modalClose.setAttribute("aria-label", this.lang.closeLabel); - this.modalClose.insertAdjacentHTML( - "beforeend", - `${this.lang.closeLabel}` - ); - this.modalClose.insertAdjacentHTML("beforeend", this.options.closeHTML); - this.modalClose.addEventListener("click", () => { + this.modalClose.insertAdjacentHTML('beforeend', `${this.lang.closeLabel}`); + this.modalClose.insertAdjacentHTML('beforeend', this.options.closeHTML); + this.modalClose.addEventListener('click', () => { this.closeModal(); }); - this.modal.insertAdjacentElement("afterbegin", this.modalClose); + this.modal.insertAdjacentElement('afterbegin', this.modalClose); } - this.modalContent = document.createElement("div"); + this.modalContent = document.createElement('div'); this.modalContent.classList.add(modalContentClass); - this.modalContent.setAttribute("tabindex", "-1"); - this.modal.insertAdjacentElement("beforeend", this.modalContent); + this.modalContent.setAttribute('tabindex', '-1'); + this.modal.insertAdjacentElement('beforeend', this.modalContent); if (this.options.allowClose) { this.closeListener = this.escKeyAction.bind(this); - document.addEventListener("keydown", this.closeListener); + document.addEventListener('keydown', this.closeListener); } this.plugins.forEach((p) => { @@ -216,65 +195,57 @@ export class ModalComponent { } }); - this.bodyElement.insertAdjacentElement("beforeend", this.modal); + this.bodyElement.insertAdjacentElement('beforeend', this.modal); this.setMainContentInert(); } private linkAccesebilityToDialog() { - let label = this.modalContent.querySelector(".js-modal-label"); - label = label - ? label - : this.modalContent.querySelector("h1,h2,h3,h4,h5,h6"); + let label = this.modalContent.querySelector('.js-modal-label'); + label = label ? label : this.modalContent.querySelector('h1,h2,h3,h4,h5,h6'); if (label) { - label.setAttribute("id", "js-modal-label"); - this.modal.setAttribute("aria-labelledby", "js-modal-label"); + label.setAttribute('id', 'js-modal-label'); + this.modal.setAttribute('aria-labelledby', 'js-modal-label'); } } public addNavigation() { - this.nextButton = document.createElement("button"); - this.nextButton.classList.add("modal__navigation"); - this.nextButton.classList.add("modal__next-button"); - this.nextButton.setAttribute("type", "button"); - this.nextButton.setAttribute("aria-label", this.lang.nextLabel); - this.nextButton.insertAdjacentHTML( - "beforeend", - `${this.lang.nextText}` - ); - this.nextButton.insertAdjacentHTML("beforeend", this.options.nextHTML); - this.nextButton.addEventListener("click", this.gotoNextItem.bind(this)); + this.nextButton = document.createElement('button'); + this.nextButton.classList.add('modal__navigation'); + this.nextButton.classList.add('modal__next-button'); + this.nextButton.setAttribute('type', 'button'); + this.nextButton.setAttribute('aria-label', this.lang.nextLabel); + this.nextButton.insertAdjacentHTML('beforeend', `${this.lang.nextText}`); + this.nextButton.insertAdjacentHTML('beforeend', this.options.nextHTML); + this.nextButton.addEventListener('click', this.gotoNextItem.bind(this)); if (this.currentGroupIndex === this.galleryGroup.length - 1) { - this.nextButton.classList.add("hidden"); - this.nextButton.setAttribute("disabled", ""); + this.nextButton.classList.add('hidden'); + this.nextButton.setAttribute('disabled', ''); } - this.modalContent.insertAdjacentElement("beforeend", this.nextButton); - - this.prevButton = document.createElement("button"); - this.prevButton.classList.add("modal__navigation"); - this.prevButton.classList.add("modal__prev-button"); - this.prevButton.setAttribute("type", "button"); - this.prevButton.setAttribute("aria-label", this.lang.prevLabel); - this.prevButton.insertAdjacentHTML( - "beforeend", - `${this.lang.prevText}` - ); - this.prevButton.insertAdjacentHTML("beforeend", this.options.prevHTML); - this.prevButton.addEventListener("click", this.gotoPrevItem.bind(this)); + this.modalContent.insertAdjacentElement('beforeend', this.nextButton); + + this.prevButton = document.createElement('button'); + this.prevButton.classList.add('modal__navigation'); + this.prevButton.classList.add('modal__prev-button'); + this.prevButton.setAttribute('type', 'button'); + this.prevButton.setAttribute('aria-label', this.lang.prevLabel); + this.prevButton.insertAdjacentHTML('beforeend', `${this.lang.prevText}`); + this.prevButton.insertAdjacentHTML('beforeend', this.options.prevHTML); + this.prevButton.addEventListener('click', this.gotoPrevItem.bind(this)); if (this.currentGroupIndex === 0) { - this.prevButton.classList.add("hidden"); - this.prevButton.setAttribute("disabled", ""); + this.prevButton.classList.add('hidden'); + this.prevButton.setAttribute('disabled', ''); } - this.modalContent.insertAdjacentElement("beforeend", this.prevButton); + this.modalContent.insertAdjacentElement('beforeend', this.prevButton); this.navListener = this.keyBoardNavigation.bind(this); - document.addEventListener("keydown", this.navListener); + document.addEventListener('keydown', this.navListener); - this.modalContent.addEventListener("touchstart", (e) => { + this.modalContent.addEventListener('touchstart', (e) => { this.startTouchX = e.changedTouches[0].pageX; this.startTouchY = e.changedTouches[0].pageY; }); - this.modalContent.addEventListener("touchend", (e) => { + this.modalContent.addEventListener('touchend', (e) => { const swipeThreshold = 10; let moved; if (this.startTouchX - e.changedTouches[0].pageX > swipeThreshold) { @@ -285,24 +256,18 @@ export class ModalComponent { this.gotoPrevItem(); moved = true; } - if ( - this.startTouchY - e.changedTouches[0].pageY > swipeThreshold && - !moved - ) { + if (this.startTouchY - e.changedTouches[0].pageY > swipeThreshold && !moved) { this.gotoNextItem(); } - if ( - this.startTouchY - e.changedTouches[0].pageY < swipeThreshold && - !moved - ) { + if (this.startTouchY - e.changedTouches[0].pageY < swipeThreshold && !moved) { this.gotoPrevItem(); } }); } public gotoNextItem() { - this.prevButton.classList.remove("hidden"); - this.prevButton.removeAttribute("disabled"); + this.prevButton.classList.remove('hidden'); + this.prevButton.removeAttribute('disabled'); if (this.currentGroupIndex < this.galleryGroup.length - 1) { this.currentGroupIndex++; this.plugins.forEach((p) => { @@ -312,16 +277,16 @@ export class ModalComponent { }); } if (this.currentGroupIndex === this.galleryGroup.length - 1) { - this.nextButton.classList.add("hidden"); - this.nextButton.setAttribute("disabled", ""); + this.nextButton.classList.add('hidden'); + this.nextButton.setAttribute('disabled', ''); this.prevButton.focus(); } this.updateGalleryTabIndexes(); } public gotoPrevItem() { - this.nextButton.classList.remove("hidden"); - this.nextButton.removeAttribute("disabled"); + this.nextButton.classList.remove('hidden'); + this.nextButton.removeAttribute('disabled'); if (this.currentGroupIndex > 0) { this.currentGroupIndex--; this.plugins.forEach((p) => { @@ -331,8 +296,8 @@ export class ModalComponent { }); } if (this.currentGroupIndex === 0) { - this.prevButton.classList.add("hidden"); - this.prevButton.setAttribute("disabled", ""); + this.prevButton.classList.add('hidden'); + this.prevButton.setAttribute('disabled', ''); this.nextButton.focus(); } this.updateGalleryTabIndexes(); @@ -341,16 +306,15 @@ export class ModalComponent { private keyBoardNavigation(event) { if ( event.keyCode === 39 || - event.key === "ArrowRight" || - (event.code === "ArrowRight" && - this.currentGroupIndex < this.galleryGroup.length - 1) + event.key === 'ArrowRight' || + (event.code === 'ArrowRight' && this.currentGroupIndex < this.galleryGroup.length - 1) ) { this.gotoNextItem(); } if ( event.keyCode === 37 || - event.key === "ArrowLeft" || - (event.code === "ArrowLeft" && this.currentGroupIndex > 0) + event.key === 'ArrowLeft' || + (event.code === 'ArrowLeft' && this.currentGroupIndex > 0) ) { this.gotoPrevItem(); } @@ -362,11 +326,7 @@ export class ModalComponent { } private escKeyAction(event) { - if ( - event.keyCode === 27 || - event.key === "Escape" || - event.code === "Escape" - ) { + if (event.keyCode === 27 || event.key === 'Escape' || event.code === 'Escape') { this.closeModal(); } } @@ -380,21 +340,20 @@ export class ModalComponent { const allTabbableElements = this.modal.querySelectorAll(tabbableElements); this.firstTabbableElement = allTabbableElements[0]; - this.lastTabbableElement = - allTabbableElements[allTabbableElements.length - 1]; + this.lastTabbableElement = allTabbableElements[allTabbableElements.length - 1]; } public closeModal() { - document.body.classList.remove("has-open-modal"); + document.body.classList.remove('has-open-modal'); if (this.inlineContentWrapper) { Array.from(this.modalContent.children).forEach((element) => { - this.inlineContentWrapper.insertAdjacentElement("beforeend", element); + this.inlineContentWrapper.insertAdjacentElement('beforeend', element); }); } this.bodyElement.removeChild(this.modalOverlay); this.bodyElement.removeChild(this.modal); - document.removeEventListener("keydown", this.closeListener); - document.removeEventListener("keydown", this.navListener); + document.removeEventListener('keydown', this.closeListener); + document.removeEventListener('keydown', this.navListener); this.plugins.forEach((p) => { if ( (this.trigger && this.trigger.matches(`.${p.getTriggerClass()}`)) || @@ -411,19 +370,19 @@ export class ModalComponent { }, 0); } - if (this.options.onClose && typeof this.options.onClose == "function") { + if (this.options.onClose && typeof this.options.onClose == 'function') { this.options.onClose(); } } private setMainContentInert(set = true) { if (this.mainContentBlock && set) { - this.mainContentBlock.setAttribute("inert", ""); - document.documentElement.classList.add("overflow-hidden"); + this.mainContentBlock.setAttribute('inert', ''); + document.documentElement.classList.add('overflow-hidden'); } if (this.mainContentBlock && !set) { - this.mainContentBlock.removeAttribute("inert"); - document.documentElement.classList.remove("overflow-hidden"); + this.mainContentBlock.removeAttribute('inert'); + document.documentElement.classList.remove('overflow-hidden'); } } }