UNPKG

storm-modal

Version:
132 lines (117 loc) 4.27 kB
/** * @name storm-modal: Accessible modal dialogue * @version 1.2.0: Thu, 02 Aug 2018 12:23:05 GMT * @author stormid * @license MIT */ (function(root, factory) { var mod = { exports: {} }; if (typeof exports !== 'undefined'){ mod.exports = exports factory(mod.exports) module.exports = mod.exports.default } else { factory(mod.exports); root.StormModal = mod.exports.default } }(this, function(exports) { 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var defaults = { onClassName: 'active', mainSelector: 'main', modalSelector: 'js-modal', callback: false }; var TRIGGER_EVENTS = window.PointerEvent ? ['pointerdown', 'keydown'] : ['ontouchstart' in window ? 'touchstart' : 'click', 'keydown']; var TRIGGER_KEYCODES = [13, 32]; var FOCUSABLE_ELEMENTS = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex="-1"])']; var componentPrototype = { init: function init() { this.isOpen = false; this.dialog = this.node.querySelector('[role=dialog]'); if (!this.dialog) throw new Error('Modal cannot be initialised, a modal must contain a dialog (role="dialog")'); this.togglers = this.node.getAttribute('data-modal-toggler') && [].slice.call(document.querySelectorAll('.' + this.node.getAttribute('data-modal-toggler'))); if (!this.togglers.length) throw new Error('Modal cannot be initialised, no modal toggler elements found'); this.boundKeyListener = this.keyListener.bind(this); this.initTriggers(); this.focusableChildren = this.getFocusableChildren(); this.dialog.setAttribute('aria-hidden', true); return this; }, initTriggers: function initTriggers() { var _this = this; this.togglers.forEach(function (toggler) { TRIGGER_EVENTS.forEach(function (ev) { toggler.addEventListener(ev, function (e) { if (!!e.keyCode && !~TRIGGER_KEYCODES.indexOf(e.keyCode) || e.which && e.which === 3) return; e.preventDefault(); _this.change(_this); }); }); }); }, getFocusableChildren: function getFocusableChildren() { return [].slice.call(this.node.querySelectorAll(FOCUSABLE_ELEMENTS.join(','))); }, trapTab: function trapTab(e) { var focusedIndex = this.focusableChildren.indexOf(document.activeElement); if (e.shiftKey && focusedIndex === 0) { e.preventDefault(); this.focusableChildren[this.focusableChildren.length - 1].focus(); } else { if (!e.shiftKey && focusedIndex === this.focusableChildren.length - 1) { e.preventDefault(); this.focusableChildren[0].focus(); } } }, keyListener: function keyListener(e) { if (this.isOpen && e.keyCode === 27) { e.preventDefault(); this.toggle(); } if (this.isOpen && e.keyCode === 9) this.trapTab(e); }, open: function open() { var _this2 = this; document.addEventListener('keydown', this.boundKeyListener); this.lastFocused = document.activeElement; this.focusableChildren.length && window.setTimeout(function () { _this2.focusableChildren[0].focus(); }, 0); this.toggle(); }, close: function close() { document.removeEventListener('keydown', this.boundKeyListener); this.lastFocused.focus(); this.toggle(); }, toggle: function toggle() { this.isOpen = !this.isOpen; this.dialog.setAttribute('aria-hidden', !this.isOpen); this.node.classList.toggle(this.settings.onClassName); // document.querySelector(this.settings.mainSelector) && document.querySelector(this.settings.mainSelector).setAttribute('aria-hidden', this.isOpen); }, change: function change() { if (!this.isOpen) this.open();else this.close(); typeof this.settings.callback === 'function' && this.settings.callback.call(this); } }; var init = function init(sel, opts) { var els = [].slice.call(document.querySelectorAll(sel)); if (els.length === 0) throw new Error('Modal cannot be initialised, no trigger elements found'); return els.map(function (el) { return Object.assign(Object.create(componentPrototype), { node: el, settings: Object.assign({}, defaults, opts) }).init(); }); }; var index = { init: init }; exports.default = index;; }));