From 8adbb1fef0aca3939a811d32628c344f02c7151d Mon Sep 17 00:00:00 2001
From: Patrizio Bekerle <patrizio@bekerle.com>
Date: Fri, 10 Jul 2020 13:37:28 +0200
Subject: [PATCH] Finish FileSource_modal_dialog_with_source_selection

---
 packages/file-handling/src/file-source.js     | 250 +++++++++-
 .../src/i18n/de/translation.json              |   5 +
 .../src/i18n/en/translation.json              |   5 +
 packages/file-handling/src/micromodal.es.js   | 443 ++++++++++++++++++
 4 files changed, 677 insertions(+), 26 deletions(-)
 create mode 100644 packages/file-handling/src/micromodal.es.js

diff --git a/packages/file-handling/src/file-source.js b/packages/file-handling/src/file-source.js
index 390ce82e..c3c397a8 100644
--- a/packages/file-handling/src/file-source.js
+++ b/packages/file-handling/src/file-source.js
@@ -7,7 +7,7 @@ import {Icon, MiniSpinner} from 'vpu-common';
 import * as commonStyles from 'vpu-common/styles';
 import {NextcloudFilePicker} from "./vpu-nextcloud-file-picker";
 import {classMap} from 'lit-html/directives/class-map.js';
-
+import MicroModal from './micromodal.es'
 
 function mimeTypesToAccept(mimeTypes) {
     // Some operating systems can't handle mime types and
@@ -43,6 +43,8 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
         this.disabled = false;
         this.decompressZip = false;
         this._queueKey = 0;
+        this.activeSource = 'local';
+        this.isDialogOpen = false;
     }
 
     static get scopedElements() {
@@ -66,6 +68,8 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
             buttonLabel: { type: String, attribute: 'button-label' },
             disabled: { type: Boolean },
             decompressZip: { type: Boolean, attribute: 'decompress-zip' },
+            activeSource: { type: Boolean, attribute: false },
+            isDialogOpen: { type: Boolean, attribute: 'dialog-open' },
         };
     }
 
@@ -75,6 +79,14 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
             switch (propName) {
                 case "lang":
                     i18n.changeLanguage(this.lang);
+                    break;
+                case "isDialogOpen":
+                    if (this.isDialogOpen) {
+                        this.openDialog();
+                    } else {
+                        this.removeAttribute("dialog-open");
+                    }
+
                     break;
             }
         });
@@ -278,6 +290,14 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
         this.dispatchEvent(event);
     }
 
+    openDialog() {
+        console.log("openDialog");
+        console.log(this._('#modal-picker'));
+        MicroModal.show(this._('#modal-picker'), {
+            onClose: modal => { this.isDialogOpen = false; }
+        });
+    }
+
     static get styles() {
         // language=css
         return css`
@@ -287,9 +307,19 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
             #dropArea {
                 border: var(--FUBorderWidth, 2px) var(--FUBorderStyle, dashed) var(--FUBBorderColor, black);
                 border-radius: var(--FUBorderRadius, 0);
-                width: var(--FUWidth, auto);
+                width: auto;
                 margin: var(--FUMargin, 0px);
                 padding: var(--FUPadding, 20px);
+                /*flex-grow: 1;*/
+                height: 100%;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+                align-items: center;
+            }
+
+            #nextcloud-file-picker {
+                /*width: 100%;*/
             }
 
             #dropArea.highlight {
@@ -316,6 +346,145 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
             .block {
                 margin-bottom: 10px;
             }
+
+            /**************************\\
+              Modal Styles
+            \\**************************/
+
+            .modal-overlay {
+                position: fixed;
+                top: 0;
+                left: 0;
+                right: 0;
+                bottom: 0;
+                background: rgba(0,0,0,0.6);
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                z-index: 10000;
+            }
+            
+            .modal-container {
+                background-color: #fff;
+                max-width: 600px;
+                max-height: 100vh;
+                min-width: 60%;
+                min-height: 50%;
+                overflow-y: auto;
+                box-sizing: border-box;
+                display: grid;
+                grid-template-columns: 50px auto;
+                grid-template-rows: auto;
+                grid-template-areas: "sidebar main";
+            }
+            
+            .modal-nav {
+                cursor: pointer;
+                overflow: hidden;
+                background-color: #eee;
+            }
+            
+            .modal-nav > div {
+                padding: 5px;
+                text-align: center;
+            }
+
+            .modal-nav .nav-icon {
+                width: 35px;
+                height: 35px;
+            }
+            
+            .modal-nav .active {
+                background-color: #777;
+                color: white;
+            }
+
+            .modal-close {
+                position: absolute;
+                background: transparent;
+                border: none;
+                float: right;
+                top: 10px;
+                right: 10px;
+            }
+            
+            .modal-close:hover {
+                font-weight: bold;
+            }
+            
+            button.modal-close:focus {
+                outline: none;
+            }
+            
+            .modal-close:before { content: "\\2715"; }
+            
+            .modal-content {
+                padding: 10px;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                /*height: 50vh;*/
+            }
+
+            .modal-content .source-main {
+                /*flex-grow: 1;*/
+                /*justify-content: center;*/
+                /*align-items: center;*/
+                height: 100%;
+                width: 100%;
+            }
+
+            /**************************\\
+              Modal Animation Style
+            \\**************************/
+            @keyframes mmfadeIn {
+                from { opacity: 0; }
+                  to { opacity: 1; }
+            }
+            
+            @keyframes mmfadeOut {
+                from { opacity: 1; }
+                  to { opacity: 0; }
+            }
+            
+            @keyframes mmslideIn {
+              from { transform: translateY(15%); }
+                to { transform: translateY(0); }
+            }
+            
+            @keyframes mmslideOut {
+                from { transform: translateY(0); }
+                to { transform: translateY(-10%); }
+            }
+            
+            .micromodal-slide {
+                display: none;
+            }
+            
+            .micromodal-slide.is-open {
+                display: block;
+            }
+            
+            .micromodal-slide[aria-hidden="false"] .modal-overlay {
+                animation: mmfadeIn .3s cubic-bezier(0.0, 0.0, 0.2, 1);
+            }
+            
+            .micromodal-slide[aria-hidden="false"] .modal-container {
+                animation: mmslideIn .3s cubic-bezier(0, 0, .2, 1);
+            }
+            
+            .micromodal-slide[aria-hidden="true"] .modal-overlay {
+              animation: mmfadeOut .3s cubic-bezier(0.0, 0.0, 0.2, 1);
+            }
+            
+            .micromodal-slide[aria-hidden="true"] .modal-container {
+                animation: mmslideOut .3s cubic-bezier(0, 0, .2, 1);
+            }
+            
+            .micromodal-slide .modal-container,
+            .micromodal-slide .modal-overlay {
+                will-change: transform;
+            }
         `;
     }
 
@@ -327,32 +496,61 @@ export class FileSource extends ScopedElementsMixin(VPULitElement) {
         }
 
         return html`
-            <div id="dropArea">
-                <div>
-                    <div class="block">
-                        ${this.text || i18n.t('intro')}
+<!--
+            <button class="button"
+                ?disabled="${this.disabled}"
+                @click="${() => { this.openDialog(); }}">${i18n.t('file-source.open-menu')}</button>
+-->
+            <div class="modal micromodal-slide" id="modal-picker" aria-hidden="true">
+                <div class="modal-overlay" tabindex="-1" data-micromodal-close>
+                    <div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-picker-title">
+                        <nav class="modal-nav">
+                            <div title="${i18n.t('file-source.nav-local')}"
+                                 @click="${() => { this.activeSource = "local"; }}"
+                                 class="${classMap({"active": this.activeSource === "local"})}">
+                                <vpu-icon class="nav-icon" name="laptop"></vpu-icon>
+                            </div>
+                            <div title="Nextcloud"
+                                 @click="${() => { this.activeSource = "nextcloud"; }}"
+                                 class="${classMap({"active": this.activeSource === "nextcloud", hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}">
+                                <vpu-icon class="nav-icon" name="cloud"></vpu-icon>
+                            </div>
+                        </nav>
+                        <main class="modal-content" id="modal-picker-content">
+                            <button title="${i18n.t('file-source.modal-close')}" class="modal-close" aria-label="Close modal" data-micromodal-close></button>
+                            <div class="source-main ${classMap({"hidden": this.activeSource !== "local"})}">
+                                <div id="dropArea">
+                                    <div class="block">
+                                        ${this.text || i18n.t('intro')}
+                                    </div>
+                                    <input ?disabled="${this.disabled}"
+                                           type="file"
+                                           id="fileElem"
+                                           multiple
+                                           accept="${mimeTypesToAccept(allowedMimeTypes)}"
+                                           name='file'>
+                                    <label class="button is-primary" for="fileElem" ?disabled="${this.disabled}">
+                                        ${this.buttonLabel || i18n.t('upload-label')}
+                                    </label>
+                                </div>
+                            </div>
+                            <div class="source-main ${classMap({"hidden": this.activeSource !== "nextcloud"})}">
+                                <vpu-nextcloud-file-picker id="nextcloud-file-picker"
+                                       class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"
+                                       ?disabled="${this.disabled}"
+                                       lang="${this.lang}"
+                                       auth-url="${this.nextcloudAuthUrl}"
+                                       web-dav-url="${this.nextcloudWebDavUrl}"
+                                       allowed-mime-types="${this.allowedMimeTypes}"
+                                       @vpu-nextcloud-file-picker-file-downloaded="${(event) => {
+                                    this.sendFileEvent(event.detail.file);
+                                }}"></vpu-nextcloud-file-picker>
+                            </div>
+                        </main>
                     </div>
-                    <input ?disabled="${this.disabled}"
-                           type="file"
-                           id="fileElem"
-                           multiple
-                           accept="${mimeTypesToAccept(allowedMimeTypes)}"
-                           name='file'>
-                    <label class="button is-primary" for="fileElem" ?disabled="${this.disabled}">
-                        ${this.buttonLabel || i18n.t('upload-label')}
-                    </label>
-                    <vpu-nextcloud-file-picker id="nextcloud-file-picker"
-                                               class="${classMap({hidden: this.nextcloudWebDavUrl === "" || this.nextcloudAuthUrl === ""})}"
-                                               ?disabled="${this.disabled}"
-                                               lang="${this.lang}"
-                                               auth-url="${this.nextcloudAuthUrl}"
-                                               web-dav-url="${this.nextcloudWebDavUrl}"
-                                               allowed-mime-types="${this.allowedMimeTypes}"                                        
-                                               @vpu-nextcloud-file-picker-file-downloaded="${(event) => {
-                                                   this.sendFileEvent(event.detail.file);
-                                               }}"></vpu-nextcloud-file-picker>
                 </div>
             </div>
-        `;
+
+          `;
     }
 }
\ No newline at end of file
diff --git a/packages/file-handling/src/i18n/de/translation.json b/packages/file-handling/src/i18n/de/translation.json
index 87f5ebea..64e5b3ac 100644
--- a/packages/file-handling/src/i18n/de/translation.json
+++ b/packages/file-handling/src/i18n/de/translation.json
@@ -10,6 +10,11 @@
   "intro": "Laden Sie mehrere Dateien mit dem Auswahldialog oder durch Ziehen und Fallenlassen in diesem Bereich hoch",
   "upload-label": "Dateiauswahl",
   "upload-disabled-title": "Die Dateiauswahl ist während dem Hochladvorgang gesperrt!",
+  "file-source": {
+    "modal-select-files": "Dateien auswählen",
+    "modal-close": "Dialog schließen",
+    "nav-local": "Lokaler Computer"
+  },
   "nextcloud-file-picker": {
     "open": "Nextcloud",
     "open-nextcloud-file-picker": "Dateien von Ihrer Nextcloud auswählen",
diff --git a/packages/file-handling/src/i18n/en/translation.json b/packages/file-handling/src/i18n/en/translation.json
index 98db57d4..248ddca4 100644
--- a/packages/file-handling/src/i18n/en/translation.json
+++ b/packages/file-handling/src/i18n/en/translation.json
@@ -10,6 +10,11 @@
   "intro": "Upload multiple files with the file dialog or by dragging and dropping images onto the dashed region",
   "upload-label": "Select some files",
   "upload-disabled-title": "The file selection is disabled while uploading!",
+  "file-source": {
+    "modal-select-files": "Select files",
+    "modal-close": "Close dialog",
+    "nav-local": "My device"
+  },
   "nextcloud-file-picker": {
     "open": "Nextcloud",
     "open-nextcloud-file-picker": "Select files from your Nextcloud",
diff --git a/packages/file-handling/src/micromodal.es.js b/packages/file-handling/src/micromodal.es.js
new file mode 100644
index 00000000..0118dd1d
--- /dev/null
+++ b/packages/file-handling/src/micromodal.es.js
@@ -0,0 +1,443 @@
+// 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]
+     * @return {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) {
+        this.modal = 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('click', this.onClick);
+        document.addEventListener('keydown', this.onKeydown);
+      }
+    }, {
+      key: "removeEventListeners",
+      value: function removeEventListeners() {
+        this.modal.removeEventListener('touchstart', this.onClick);
+        this.modal.removeEventListener('click', 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) {
+        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
+   * @return {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
+   * @return {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
+   * @return {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
+   * @return {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]
+   * @return 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]
+   * @return {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]
+   * @return {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;
-- 
GitLab