easy-js-modal
Version:
EasyJsModal - A lightweight, easy-to-use, and customizable modal plugin for JavaScript applications
215 lines (214 loc) • 7.23 kB
JavaScript
;(function (a) {
typeof define == 'function' && define.amd ? define(a) : a()
})(function () {
'use strict'
var h = Object.defineProperty
var u = (a, l, s) =>
l in a
? h(a, l, { enumerable: !0, configurable: !0, writable: !0, value: s })
: (a[l] = s)
var i = (a, l, s) => (u(a, typeof l != 'symbol' ? l + '' : l, s), s)
class a {
constructor() {
i(this, 'activeModal', null)
i(this, 'focusableElementBeforeModal', null)
}
setActiveModal(e) {
this.activeModal && this.activeModal.close(), (this.activeModal = e)
}
removeActiveModal(e) {
this.activeModal === e && (this.activeModal = null)
}
get isActiveModal() {
return this.activeModal !== null
}
}
const l = new a(),
s = () => {
;(document.body.style.paddingRight = ''),
(document.body.style.overflow = '')
},
r = () => {
const n = document.createElement('div')
;(n.style.visibility = 'hidden'),
(n.style.width = '100px'),
(n.style.overflow = 'scroll'),
document.body.appendChild(n)
const e = document.createElement('div')
;(e.style.width = '100%'), n.appendChild(e)
const o = n.offsetWidth - e.offsetWidth
return n.remove(), o
},
c = (n) => {
if (typeof n != 'string') throw new Error('The input should be a string.')
return `--ejm-${n.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()}`
}
class m {
constructor(e, o, t) {
i(this, 'modalElement')
i(this, 'modalWindow')
i(this, 'scrollbarWidth')
i(this, 'modalBlockClass')
i(this, 'closeButtonClass')
i(this, 'animationDuration')
i(this, 'modalWindowClass')
i(this, 'onOpen')
i(this, 'onClose')
i(this, 'modalStyles')
i(this, 'openTimeout', null)
i(this, 'closeTimeout', null)
i(this, 'handleKeyDown', (e) => {
e.key === 'Escape' && this.close(), this.trapFocus(e)
})
;(this.onOpen = o == null ? void 0 : o.onOpen),
(this.onClose = o == null ? void 0 : o.onClose),
(this.modalBlockClass =
(o == null ? void 0 : o.modalBlockClass) || 'modal'),
(this.animationDuration =
(o == null ? void 0 : o.animationDuration) || 300),
(this.closeButtonClass = `${this.modalBlockClass}__close`),
(this.modalWindowClass = `${this.modalBlockClass}__window`),
(this.modalStyles = {
layoutBackgroundColor:
(t == null ? void 0 : t.layoutBackgroundColor) ||
'rgba(0, 0, 0, 0.5)',
animationDuration: `${this.animationDuration}ms`,
windowBackgroundColor:
(t == null ? void 0 : t.windowBackgroundColor) || '#fff',
windowWidth: (t == null ? void 0 : t.windowWidth) || '90%',
windowMaxWidth: (t == null ? void 0 : t.windowMaxWidth) || '500px',
windowPadding: (t == null ? void 0 : t.windowPadding) || '2rem',
windowBorderRadius:
(t == null ? void 0 : t.windowBorderRadius) || '0.5rem',
closeFontSize: (t == null ? void 0 : t.closeFontSize) || '1.25rem'
}),
(this.modalWindow = this.createModalWindow(e)),
(this.modalElement = this.createModalElement()),
(this.scrollbarWidth = r()),
this.init()
}
init() {
if (l.isActiveModal) {
console.warn(
'Another modal is already open. Please close it before opening a new one.'
)
return
}
document.body.appendChild(this.modalElement)
}
createModalElement() {
const e = document.createElement('div')
return (
e.classList.add(this.modalBlockClass),
e.appendChild(this.modalWindow),
e
)
}
createModalWindow(e) {
const o = document.createElement('div')
o.classList.add(this.modalWindowClass)
const t = `<button class="${this.closeButtonClass}">×</button>`
return (o.innerHTML = (typeof e == 'string' ? e : e.outerHTML) + t), o
}
getStylesForModal() {
return Object.entries(this.modalStyles).map(([e, o]) => `${c(e)}: ${o};`)
}
setContent(e) {
const o = this.modalWindow.querySelector(`.${this.closeButtonClass}`)
;(this.modalWindow.innerHTML = ''),
typeof e == 'string'
? (this.modalWindow.innerHTML = e)
: this.modalWindow.appendChild(e),
o && this.modalWindow.appendChild(o)
}
open() {
if (l.isActiveModal) {
console.warn(
'Another modal is already open. Please close it before opening a new one.'
)
return
}
;(this.modalElement.style.cssText = this.getStylesForModal().join(' ')),
(this.modalElement.style.display = 'flex'),
(this.openTimeout = setTimeout(() => {
this.modalElement.classList.add(`${this.modalBlockClass}--visible`),
this.modalWindow.classList.add(`${this.modalWindowClass}--visible`),
this.onOpen && this.onOpen(),
this.openTimeout !== null &&
(clearTimeout(this.openTimeout), (this.openTimeout = null))
}, this.animationDuration + 50)),
this.disableScroll(),
this.addEventListeners(),
(l.focusableElementBeforeModal = document.activeElement)
const e = this.modalWindow.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
e && e.focus(), l.setActiveModal(this)
}
close() {
var e
this.modalElement.classList.remove(`${this.modalBlockClass}--visible`),
this.modalWindow.classList.remove(`${this.modalWindowClass}--visible`),
s(),
(this.closeTimeout = setTimeout(() => {
;(this.modalElement.style.display = ''),
this.removeEventListeners(),
this.destroy(),
this.onClose && this.onClose(),
this.closeTimeout !== null &&
(clearTimeout(this.closeTimeout), (this.closeTimeout = null))
}, this.animationDuration + 50)),
(e = l.focusableElementBeforeModal) == null || e.focus(),
l.removeActiveModal(this)
}
destroy() {
this.modalElement.remove()
}
addEventListeners() {
this.modalElement
.querySelector(`.${this.closeButtonClass}`)
.addEventListener('click', () => this.close()),
this.modalElement.addEventListener('click', (e) => {
e.target === this.modalElement && this.close()
}),
document.addEventListener('keydown', this.handleKeyDown)
}
removeEventListeners() {
this.modalElement
.querySelector(`.${this.closeButtonClass}`)
.removeEventListener('click', () => this.close()),
this.modalElement.removeEventListener('click', (e) => {
e.target === this.modalElement && this.close()
}),
document.removeEventListener('keydown', this.handleKeyDown)
}
disableScroll() {
;(document.body.style.paddingRight = `${this.scrollbarWidth}px`),
(document.body.style.overflow = 'hidden')
}
trapFocus(e) {
if (e.key !== 'Tab') return
const o = this.getFocusableElements(),
t = o[0],
d = o[o.length - 1]
e.shiftKey
? document.activeElement === t && (d.focus(), e.preventDefault())
: document.activeElement === d && (t.focus(), e.preventDefault())
}
getFocusableElements() {
const e = `
a[href],
button:not([disabled]),
textarea:not([disabled]),
input[type="text"]:not([disabled]),
input[type="radio"]:not([disabled]),
input[type="checkbox"]:not([disabled]),
select:not([disabled])
`
return Array.from(this.modalElement.querySelectorAll(e)).filter(
(t) => !t.hasAttribute('tabindex') || t.tabIndex >= 0
)
}
}
typeof window < 'u' && (window.EasyJsModal = m)
})