@digital-blueprint/dispatch-app
Version:
[GitHub Repository](https://github.com/digital-blueprint/dispatch-app) | [npmjs package](https://www.npmjs.com/package/@digital-blueprint/dispatch-app) | [Unpkg CDN](https://unpkg.com/browse/@digital-blueprint/dispatch-app/) | [Dispatch Bundle](https://gi
522 lines (463 loc) • 20.6 kB
JavaScript
// see https://github.com/ghosh/Micromodal/pull/351
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _toConsumableArray(arr) {
return (
_arrayWithoutHoles(arr) ||
_iterableToArray(arr) ||
_unsupportedIterableToArray(arr) ||
_nonIterableSpread()
);
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== 'undefined' && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(n);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError(
'Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
var MicroModal = (function () {
var FOCUSABLE_ELEMENTS = [
'a[href]',
'area[href]',
'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',
'select:not([disabled]):not([aria-hidden])',
'textarea:not([disabled]):not([aria-hidden])',
'button:not([disabled]):not([aria-hidden])',
'iframe',
'object',
'embed',
'[contenteditable]',
'[tabindex]:not([tabindex^="-"])',
];
var Modal = /*#__PURE__*/ (function () {
function Modal(_ref) {
var targetModal = _ref.targetModal,
_ref$triggers = _ref.triggers,
triggers = _ref$triggers === void 0 ? [] : _ref$triggers,
_ref$onShow = _ref.onShow,
onShow = _ref$onShow === void 0 ? function () {} : _ref$onShow,
_ref$onClose = _ref.onClose,
onClose = _ref$onClose === void 0 ? function () {} : _ref$onClose,
_ref$openTrigger = _ref.openTrigger,
openTrigger =
_ref$openTrigger === void 0 ? 'data-micromodal-trigger' : _ref$openTrigger,
_ref$closeTrigger = _ref.closeTrigger,
closeTrigger =
_ref$closeTrigger === void 0 ? 'data-micromodal-close' : _ref$closeTrigger,
_ref$openClass = _ref.openClass,
openClass = _ref$openClass === void 0 ? 'is-open' : _ref$openClass,
_ref$disableScroll = _ref.disableScroll,
disableScroll = _ref$disableScroll === void 0 ? false : _ref$disableScroll,
_ref$disableFocus = _ref.disableFocus,
disableFocus = _ref$disableFocus === void 0 ? false : _ref$disableFocus,
_ref$awaitCloseAnimat = _ref.awaitCloseAnimation,
awaitCloseAnimation =
_ref$awaitCloseAnimat === void 0 ? false : _ref$awaitCloseAnimat,
_ref$awaitOpenAnimati = _ref.awaitOpenAnimation,
awaitOpenAnimation =
_ref$awaitOpenAnimati === void 0 ? false : _ref$awaitOpenAnimati,
_ref$debugMode = _ref.debugMode,
debugMode = _ref$debugMode === void 0 ? false : _ref$debugMode;
_classCallCheck(this, Modal);
// Save a reference of the modal
this.modal = this.modal =
typeof targetModal === 'string'
? document.getElementById(targetModal)
: targetModal; // Save a reference to the passed config
this.config = {
debugMode: debugMode,
disableScroll: disableScroll,
openTrigger: openTrigger,
closeTrigger: closeTrigger,
openClass: openClass,
onShow: onShow,
onClose: onClose,
awaitCloseAnimation: awaitCloseAnimation,
awaitOpenAnimation: awaitOpenAnimation,
disableFocus: disableFocus,
}; // Register click events only if pre binding eventListeners
if (triggers.length > 0)
this.registerTriggers.apply(this, _toConsumableArray(triggers)); // pre bind functions for event listeners
this.onClick = this.onClick.bind(this);
this.onKeydown = this.onKeydown.bind(this);
}
/**
* Loops through all openTriggers and binds click event
* @param {Array} triggers [Array of node elements]
* @returns {void}
*/
_createClass(Modal, [
{
key: 'registerTriggers',
value: function registerTriggers() {
var _this = this;
for (
var _len = arguments.length, triggers = new Array(_len), _key = 0;
_key < _len;
_key++
) {
triggers[_key] = arguments[_key];
}
triggers.filter(Boolean).forEach(function (trigger) {
trigger.addEventListener('click', function (event) {
return _this.showModal(event);
});
});
},
},
{
key: 'showModal',
value: function showModal() {
var _this2 = this;
var event =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
this.activeElement = document.activeElement;
this.modal.setAttribute('aria-hidden', 'false');
this.modal.classList.add(this.config.openClass);
this.scrollBehaviour('disable');
this.addEventListeners();
if (this.config.awaitOpenAnimation) {
var handler = function handler() {
_this2.modal.removeEventListener('animationend', handler, false);
_this2.setFocusToFirstNode();
};
this.modal.addEventListener('animationend', handler, false);
} else {
this.setFocusToFirstNode();
}
this.config.onShow(this.modal, this.activeElement, event);
},
},
{
key: 'closeModal',
value: function closeModal() {
var event =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var modal = this.modal;
this.modal.setAttribute('aria-hidden', 'true');
this.removeEventListeners();
this.scrollBehaviour('enable');
if (this.activeElement && this.activeElement.focus) {
this.activeElement.focus();
}
this.config.onClose(this.modal, this.activeElement, event);
if (this.config.awaitCloseAnimation) {
var openClass = this.config.openClass; // <- old school ftw
this.modal.addEventListener(
'animationend',
function handler() {
modal.classList.remove(openClass);
modal.removeEventListener('animationend', handler, false);
},
false,
);
} else {
modal.classList.remove(this.config.openClass);
}
},
},
{
key: 'closeModalById',
value: function closeModalById(targetModal) {
// added support to pass on an element or an id -> for webcomponents
if (targetModal instanceof HTMLElement) {
this.modal = targetModal;
} else {
this.model = document.getElementById(targetModal);
}
if (this.modal) this.closeModal();
},
},
{
key: 'scrollBehaviour',
value: function scrollBehaviour(toggle) {
if (!this.config.disableScroll) return;
var body = document.querySelector('body');
switch (toggle) {
case 'enable':
Object.assign(body.style, {
overflow: '',
});
break;
case 'disable':
Object.assign(body.style, {
overflow: 'hidden',
});
break;
}
},
},
{
key: 'addEventListeners',
value: function addEventListeners() {
this.modal.addEventListener('touchstart', this.onClick);
this.modal.addEventListener('mousedown', this.onClick);
document.addEventListener('keydown', this.onKeydown);
},
},
{
key: 'removeEventListeners',
value: function removeEventListeners() {
this.modal.removeEventListener('touchstart', this.onClick);
this.modal.removeEventListener('mousedown', this.onClick);
document.removeEventListener('keydown', this.onKeydown);
},
},
{
key: 'onClick',
value: function onClick(event) {
if (event.target.hasAttribute(this.config.closeTrigger)) {
this.closeModal(event);
}
},
},
{
key: 'onKeydown',
value: function onKeydown(event) {
if (event.keyCode === 27) this.closeModal(event); // esc
if (event.keyCode === 9) this.retainFocus(event); // tab
},
},
{
key: 'getFocusableNodes',
value: function getFocusableNodes() {
var nodes = this.modal.querySelectorAll(FOCUSABLE_ELEMENTS);
return Array.apply(void 0, _toConsumableArray(nodes));
},
/**
* Tries to set focus on a node which is not a close trigger
* if no other nodes exist then focuses on first close trigger
*/
},
{
key: 'setFocusToFirstNode',
value: function setFocusToFirstNode() {
var _this3 = this;
if (this.config.disableFocus) return;
var focusableNodes = this.getFocusableNodes(); // no focusable nodes
if (focusableNodes.length === 0) return; // remove nodes on whose click, the modal closes
// could not think of a better name :(
var nodesWhichAreNotCloseTargets = focusableNodes.filter(function (node) {
return !node.hasAttribute(_this3.config.closeTrigger);
});
if (nodesWhichAreNotCloseTargets.length > 0)
nodesWhichAreNotCloseTargets[0].focus();
if (nodesWhichAreNotCloseTargets.length === 0) focusableNodes[0].focus();
},
},
{
key: 'retainFocus',
value: function retainFocus(event) {
// Focus retention doesn't work in web-components because document.activeElement can't look into them!
// By disabling below code, we can still use the modal in web-components with tabs,
// but the focus will jump out of the modal after the last element was reached.
// See https://gitlab.tugraz.at/dbp/dispatch/dispatch/-/issues/3#note_152717
return;
// var focusableNodes = this.getFocusableNodes(); // no focusable nodes
//
// if (focusableNodes.length === 0) return;
// /**
// * Filters nodes which are hidden to prevent
// * focus leak outside modal
// */
//
// focusableNodes = focusableNodes.filter(function (node) {
// return node.offsetParent !== null;
// }); // if disableFocus is true
//
// if (!this.modal.contains(document.activeElement)) {
// focusableNodes[0].focus();
// } else {
// var focusedItemIndex = focusableNodes.indexOf(document.activeElement);
//
// if (event.shiftKey && focusedItemIndex === 0) {
// focusableNodes[focusableNodes.length - 1].focus();
// event.preventDefault();
// }
//
// if (
// !event.shiftKey &&
// focusableNodes.length > 0 &&
// focusedItemIndex === focusableNodes.length - 1
// ) {
// focusableNodes[0].focus();
// event.preventDefault();
// }
// }
},
},
]);
return Modal;
})();
/**
* Modal prototype ends.
* Here on code is responsible for detecting and
* auto binding event handlers on modal triggers
*/
// Keep a reference to the opened modal
var activeModal = null;
/**
* Generates an associative array of modals and it's
* respective triggers
* @param {Array} triggers An array of all triggers
* @param {string} triggerAttr The data-attribute which triggers the module
* @returns {Array}
*/
var generateTriggerMap = function generateTriggerMap(triggers, triggerAttr) {
var triggerMap = [];
triggers.forEach(function (trigger) {
var targetModal = trigger.attributes[triggerAttr].value;
if (triggerMap[targetModal] === undefined) triggerMap[targetModal] = [];
triggerMap[targetModal].push(trigger);
});
return triggerMap;
};
/**
* Validates whether a modal of the given id exists
* in the DOM
* @param {number} id The id of the modal
* @returns {boolean}
*/
var validateModalPresence = function validateModalPresence(id) {
if (!document.getElementById(id)) {
console.warn(
"MicroModal: \u2757Seems like you have missed %c'".concat(id, "'"),
'background-color: #f8f9fa;color: #50596c;font-weight: bold;',
'ID somewhere in your code. Refer example below to resolve it.',
);
console.warn(
'%cExample:',
'background-color: #f8f9fa;color: #50596c;font-weight: bold;',
'<div class="modal" id="'.concat(id, '"></div>'),
);
return false;
}
};
/**
* Validates if there are modal triggers present
* in the DOM
* @param {Array} triggers An array of data-triggers
* @returns {boolean}
*/
var validateTriggerPresence = function validateTriggerPresence(triggers) {
if (triggers.length <= 0) {
console.warn(
"MicroModal: \u2757Please specify at least one %c'micromodal-trigger'",
'background-color: #f8f9fa;color: #50596c;font-weight: bold;',
'data attribute.',
);
console.warn(
'%cExample:',
'background-color: #f8f9fa;color: #50596c;font-weight: bold;',
'<a href="#" data-micromodal-trigger="my-modal"></a>',
);
return false;
}
};
/**
* Checks if triggers and their corresponding modals
* are present in the DOM
* @param {Array} triggers Array of DOM nodes which have data-triggers
* @param {Array} triggerMap Associative array of modals and their triggers
* @returns {boolean}
*/
var validateArgs = function validateArgs(triggers, triggerMap) {
validateTriggerPresence(triggers);
if (!triggerMap) return true;
for (var id in triggerMap) {
validateModalPresence(id);
}
return true;
};
/**
* Binds click handlers to all modal triggers
* @param {object} config [description]
* @returns void
*/
var init = function init(config) {
// Create an config object with default openTrigger
var options = Object.assign(
{},
{
openTrigger: 'data-micromodal-trigger',
},
config,
); // Collects all the nodes with the trigger
var triggers = _toConsumableArray(
document.querySelectorAll('['.concat(options.openTrigger, ']')),
); // Makes a mappings of modals with their trigger nodes
var triggerMap = generateTriggerMap(triggers, options.openTrigger); // Checks if modals and triggers exist in dom
if (options.debugMode === true && validateArgs(triggers, triggerMap) === false) return; // For every target modal creates a new instance
for (var key in triggerMap) {
var value = triggerMap[key];
options.targetModal = key;
options.triggers = _toConsumableArray(value);
activeModal = new Modal(options); // eslint-disable-line no-new
}
};
/**
* Shows a particular modal
* @param {string} targetModal [The id of the modal to display]
* @param {object} config [The configuration object to pass]
* @returns {void}
*/
var show = function show(targetModal, config) {
var options = config || {};
options.targetModal = targetModal; // Checks if modals and triggers exist in dom
if (options.debugMode === true && validateModalPresence(targetModal) === false) return; // clear events in case previous modal wasn't close
if (activeModal) activeModal.removeEventListeners(); // stores reference to active modal
activeModal = new Modal(options); // eslint-disable-line no-new
activeModal.showModal();
};
/**
* Closes the active modal
* @param {string} targetModal [The id of the modal to close]
* @returns {void}
*/
var close = function close(targetModal) {
targetModal ? activeModal.closeModalById(targetModal) : activeModal.closeModal();
};
return {
init: init,
show: show,
close: close,
};
})();
window.MicroModal = MicroModal;
export default MicroModal;