graph-modal
Version:
Simple JS Library for modal windows
188 lines (160 loc) • 5.39 kB
JavaScript
export default class GraphModal {
constructor(options) {
let defaultOptions = {
isOpen: () => {},
isClose: () => {},
}
this.options = Object.assign(defaultOptions, options);
this.modal = document.querySelector('.graph-modal');
this.speed = 300;
this.animation = 'fade';
this._reOpen = false;
this._nextContainer = false;
this.modalContainer = false;
this.isOpen = false;
this.previousActiveElement = false;
this._focusElements = [
'a[href]',
'input',
'select',
'textarea',
'button',
'iframe',
'[contenteditable]',
'[tabindex]:not([tabindex^="-"])'
];
this._fixBlocks = document.querySelectorAll('.fix-block');
this.events();
}
events() {
if (this.modal) {
document.addEventListener('click', function (e) {
const clickedElement = e.target.closest(`[data-graph-path]`);
if (clickedElement) {
let target = clickedElement.dataset.graphPath;
let animation = clickedElement.dataset.graphAnimation;
let speed = clickedElement.dataset.graphSpeed;
this.animation = animation ? animation : 'fade';
this.speed = speed ? parseInt(speed) : 300;
this._nextContainer = document.querySelector(`[data-graph-target="${target}"]`);
this.open();
return;
}
if (e.target.closest('.js-modal-close')) {
this.close();
return;
}
}.bind(this));
window.addEventListener('keydown', function (e) {
if (e.keyCode == 27 && this.isOpen) {
this.close();
}
if (e.which == 9 && this.isOpen) {
this.focusCatch(e);
return;
}
}.bind(this));
document.addEventListener('click', function (e) {
if (e.target.classList.contains('graph-modal') && e.target.classList.contains("is-open")) {
this.close();
}
}.bind(this));
}
}
open(selector) {
this.previousActiveElement = document.activeElement;
if (this.isOpen) {
this.reOpen = true;
this.close();
return;
}
this.modalContainer = this._nextContainer;
if (selector) {
this.modalContainer = document.querySelector(`[data-graph-target="${selector}"]`);
}
this.modalContainer.scrollTo(0, 0)
this.modal.style.setProperty('--transition-time', `${this.speed / 1000}s`);
this.modal.classList.add('is-open');
document.body.style.scrollBehavior = 'auto';
document.documentElement.style.scrollBehavior = 'auto';
this.disableScroll();
this.modalContainer.classList.add('graph-modal-open');
this.modalContainer.classList.add(this.animation);
setTimeout(() => {
this.options.isOpen(this);
this.modalContainer.classList.add('animate-open');
this.isOpen = true;
this.focusTrap();
}, this.speed);
}
close() {
if (this.modalContainer) {
this.modalContainer.classList.remove('animate-open');
this.modalContainer.classList.remove(this.animation);
this.modal.classList.remove('is-open');
this.modalContainer.classList.remove('graph-modal-open');
this.enableScroll();
document.body.style.scrollBehavior = 'auto';
document.documentElement.style.scrollBehavior = 'auto';
this.options.isClose(this);
this.isOpen = false;
this.focusTrap();
if (this.reOpen) {
this.reOpen = false;
this.open();
}
}
}
focusCatch(e) {
const nodes = this.modalContainer.querySelectorAll(this._focusElements);
const nodesArray = Array.prototype.slice.call(nodes);
const focusedItemIndex = nodesArray.indexOf(document.activeElement)
if (e.shiftKey && focusedItemIndex === 0) {
nodesArray[nodesArray.length - 1].focus();
e.preventDefault();
}
if (!e.shiftKey && focusedItemIndex === nodesArray.length - 1) {
nodesArray[0].focus();
e.preventDefault();
}
}
focusTrap() {
const nodes = this.modalContainer.querySelectorAll(this._focusElements);
if (this.isOpen) {
if (nodes.length) nodes[0].focus();
} else {
this.previousActiveElement.focus();
}
}
disableScroll() {
let pagePosition = window.scrollY;
this.lockPadding();
document.body.classList.add('disable-scroll');
document.body.dataset.position = pagePosition;
document.body.style.top = -pagePosition + 'px';
}
enableScroll() {
let pagePosition = parseInt(document.body.dataset.position, 10);
this.unlockPadding();
document.body.style.top = 'auto';
document.body.classList.remove('disable-scroll');
window.scrollTo({
top: pagePosition,
left: 0
});
document.body.removeAttribute('data-position');
}
lockPadding() {
let paddingOffset = window.innerWidth - document.body.offsetWidth + 'px';
this._fixBlocks.forEach((el) => {
el.style.paddingRight = paddingOffset;
});
document.body.style.paddingRight = paddingOffset;
}
unlockPadding() {
this._fixBlocks.forEach((el) => {
el.style.paddingRight = '0px';
});
document.body.style.paddingRight = '0px';
}
}