UNPKG

formstone

Version:

Library of modular front end components.

268 lines (198 loc) 5.34 kB
// import MediaQuery from './mediaquery.js'; import { extend, // select, siblings, iterate, // on, off, once, trigger, // addClass, removeClass, hasClass, // getAttr, setAttr, updateAttr, restoreAttr, } from './utils.js'; // Accessibility based on https://plousia.com/blog/how-create-accessible-mobile-menu // Class class Modal { static #_guid = 1; static #_defaults = { customClass: '', templates: { container: ` <div class="fs-modal" role="dialog" aria-modal="true"> <div class="fs-modal-overlay"></div> <div class="fs-modal-container"> <div class="fs-modal-wrap"> <button type="button" class="fs-modal-close" aria-label="Close">[close]</button> <div class="fs-modal-frame"></div> </div> </div> </div>`, close: `<span class="fs-modal-sr">Close</span><svg viewBox="-6 -6 24 24" fill="currentColor"><path d="M7.314 5.9l3.535-3.536A1 1 0 1 0 9.435.95L5.899 4.485 2.364.95A1 1 0 1 0 .95 2.364l3.535 3.535L.95 9.435a1 1 0 1 0 1.414 1.414l3.535-3.535 3.536 3.535a1 1 0 1 0 1.414-1.414L7.314 5.899z"></path></svg>`, }, }; static defaults(options) { this.#_defaults = extend(true, this.#_defaults, options); } static construct(selector, options) { let targets = select(selector); iterate(targets, (el) => { if (!el.Modal) { new Modal(el, options); } }); return targets; } // constructor(el, options) { if (el.Modal) { console.warn('Modal: Instance already exists', el); return; } // Parse JSON Options let optionsData = {}; let dataset = el.dataset; try { optionsData = JSON.parse(dataset.modalOptions || '{}'); } catch (e) { console.warn('Modal: Error parsing options JSON', el); } // Internal Data Object.assign(this, extend(true, this.constructor.#_defaults, options || {}, optionsData)); this.el = el; this.guid = this.constructor.#_guid++; this.guidClass = `fs-modal-element-${this.guid}`; this.isOpen = false; // addClass(this.el, this.guidClass); on(this.el, 'click', this.#onClick); el.Modal = this; } // destroy() { this.close(); removeClass(this.el, this.guidClass); off(this.el, 'click', this.#onClick); this.el.Modal = null; delete this.el.Modal; } // open() { if (this.isOpen) { return; } this.hash = this.el.hash; this.targetEl = select(this.hash)[0]; if (!this.targetEl) { return; } this.listeners = { 'close': this.#onClose(), 'container': this.#onContainerClick(), 'keydown': this.#onKeyDown(), }; this.#draw(); this.#hideSiblings(); setTimeout(() => { addClass(this.modalEl, 'fs-modal-open'); this.isOpen = true; // this.frameEl.childNodes[0].focus(); this.closeEl.focus(); trigger(window, 'modal:open', { el: this.el }); }, 10); } close() { if (!this.isOpen) { return; } removeClass(this.modalEl, 'fs-modal-open'); this.#showSiblings(); off(window, 'keydown', this.listeners.keydown); off(window, 'modal:close', this.listeners.close); let cb = (e) => { if (!hasClass(e.target, 'fs-modal')) { return; } this.targetEl.append(...this.frameEl.childNodes); off(this.modalEl, 'transitionend', cb); this.modalEl.remove(); this.isOpen = false; this.el.focus(); trigger(window, 'modal:close', { el: this.el }); }; on(this.modalEl, 'transitionend', cb); } // #draw() { let html = this.templates.container .replace('[close]', this.templates.close); document.body.insertAdjacentHTML('beforeend', html); this.modalEl = select('.fs-modal')[0]; this.overlayEl = select('.fs-modal-overlay', this.modalEl)[0]; this.closeEl = select('.fs-modal-close', this.modalEl)[0]; this.containerEl = select('.fs-modal-container', this.modalEl)[0]; this.wrapEl = select('.fs-modal-wrap', this.modalEl)[0]; this.frameEl = select('.fs-modal-frame', this.modalEl)[0]; this.frameEl.append(...this.targetEl.childNodes); this.wrapEl.append(this.frameEl); addClass(this.modalEl, this.customClass); once(this.closeEl, 'click', this.listeners.close); on(this.containerEl, 'click', this.listeners.container); on(window, 'keydown', this.listeners.keydown); on(window, 'lightbox:close', this.listeners.close); } // #hideSiblings() { updateAttr(siblings(this.modalEl), 'aria-hidden', 'true', 'modal'); } #showSiblings() { restoreAttr(siblings(this.modalEl), 'aria-hidden', 'modal'); } // #onClick(e) { e.preventDefault(); e.stopPropagation(); this.Modal.open(); } #onContainerClick() { return (e) => { if (e.target !== this.frameEl && !this.frameEl.contains(e.target)) { this.close(); } else { this.#checkClick(e); } }; } #checkClick(e) { if (hasClass(e.target, 'fs-modal-trigger-close')) { this.close(); } } #onClose() { return (e) => { this.close(); }; } #onKeyDown() { return (e) => { if (e.key === 'Escape') { this.close(); } }; } }; // Export export default Modal;