Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/node_modules/dialog-polyfill/LICENSE b/node_modules/dialog-polyfill/LICENSE
new file mode 100644
index 0000000..3d0f7d3
--- /dev/null
+++ b/node_modules/dialog-polyfill/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/node_modules/dialog-polyfill/README.md b/node_modules/dialog-polyfill/README.md
new file mode 100644
index 0000000..fa907ec
--- /dev/null
+++ b/node_modules/dialog-polyfill/README.md
@@ -0,0 +1,123 @@
+dialog-polyfill.js is a polyfill for `<dialog>` and `<form method="dialog">`.
+Check out [some demos](http://demo.agektmr.com/dialog/)!
+
+`<dialog>` is an element for a popup box in a web page, including a modal option which will make the rest of the page inert during use.
+This could be useful to block a user's interaction until they give you a response, or to confirm an action.
+See the [HTML spec](https://html.spec.whatwg.org/multipage/forms.html#the-dialog-element).
+
+## Usage
+
+### Installation
+
+You may optionally install via NPM -
+
+    $ npm install dialog-polyfill
+
+
+There are several ways that to include the dialog polyfill:
+
+* include `dialog-polyfill.js` script directly in your HTML, which exposes a global `dialogPolyfill` function.
+* `import` (es modules)
+* `require` (commonjs/node)
+
+
+```javascript
+// direct import (script module, deno)
+import dialogPolyfill from './node_modules/dialog-polyfill/index.js';
+
+// *OR*
+
+// modern es modules with rollup/webpack bundlers, and node via esm module
+import dialogPolyfill from 'dialog-polyfill'
+
+// *OR*
+
+// traditional commonjs/node and browserify bundler
+const dialogPolyfill = require('dialog-polyfill')
+```
+
+
+### Supports
+
+This polyfill works on modern versions of all major browsers. It also supports IE9 and above.
+
+### Steps
+
+1. Include the CSS in the `<head>` of your document, and the Javascript anywhere before referencing `dialogPolyfill`.
+2. Create your dialog elements within the document. See [limitations](#limitations) for more details.
+3. Register the elements using `dialogPolyfill.registerDialog()`, passing it one node at a time. This polyfill won't replace native support.
+4. Use your `<dialog>` elements!
+
+## Script Global Example
+
+```html
+<head>
+  <link rel="stylesheet" type="text/css" href="dist/dialog-polyfill.css" />
+</head>
+<body>
+  <dialog>
+    I'm a dialog!
+    <form method="dialog">
+      <input type="submit" value="Close" />
+    </form>
+  </dialog>
+  <script src="dist/dialog-polyfill.js"></script>
+  <script>
+    var dialog = document.querySelector('dialog');
+    dialogPolyfill.registerDialog(dialog);
+    // Now dialog acts like a native <dialog>.
+    dialog.showModal();
+  </script>
+</body>
+```
+
+### ::backdrop
+
+In native `<dialog>`, the backdrop is a pseudo-element.
+When using the polyfill, the backdrop will be an adjacent element:
+
+```css
+dialog::backdrop { /* native */
+  background-color: green;
+}
+dialog + .backdrop { /* polyfill */
+  background-color: green;
+}
+```
+
+## Limitations
+
+In the polyfill, modal dialogs have limitations-
+
+- They should not be contained by parents that create a stacking context, see below
+- The browser's chrome may not always be accessible via the tab key
+- Changes to the CSS top/bottom values while open aren't retained
+
+### Stacking Context
+
+The major limitation of the polyfill is that dialogs should not have parents that create [a stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context).
+The easiest way to solve this is to move your `<dialog>` element to be a child of `<body>`.
+
+If this isn't possible you may still be able to use the dialog.
+However, you may want to resolve it for two major reasons-
+
+1. The polyfill can't guarantee that the dialog will be the top-most element of your page
+2. The dialog may be positioned incorrectly as they are positioned as part of the page layout _where they are opened_ (defined by spec), and not at a fixed position in the user's browser.
+
+To position a dialog in the center (regardless of user scroll position or stacking context), you can specify the following CSS-
+
+```css
+dialog {
+  position: fixed;
+  top: 50%;
+  transform: translate(0, -50%);
+}
+```
+
+## Extensions
+
+### Focus
+
+The WAI-ARIA doc suggests returning focus to the previously focused element after a modal dialog is closed.
+However, this is not part of the dialog spec itself.
+See [this snippet](https://gist.github.com/samthor/babe9fad4a65625b301ba482dad284d1) to add this, even to the native `dialog`.
diff --git a/node_modules/dialog-polyfill/dialog-polyfill.css b/node_modules/dialog-polyfill/dialog-polyfill.css
new file mode 100644
index 0000000..6b38bf0
--- /dev/null
+++ b/node_modules/dialog-polyfill/dialog-polyfill.css
@@ -0,0 +1,37 @@
+dialog {
+  position: absolute;
+  left: 0; right: 0;
+  width: -moz-fit-content;
+  width: -webkit-fit-content;
+  width: fit-content;
+  height: -moz-fit-content;
+  height: -webkit-fit-content;
+  height: fit-content;
+  margin: auto;
+  border: solid;
+  padding: 1em;
+  background: white;
+  color: black;
+  display: block;
+}
+
+dialog:not([open]) {
+  display: none;
+}
+
+dialog + .backdrop {
+  position: fixed;
+  top: 0; right: 0; bottom: 0; left: 0;
+  background: rgba(0,0,0,0.1);
+}
+
+._dialog_overlay {
+  position: fixed;
+  top: 0; right: 0; bottom: 0; left: 0;
+}
+
+dialog.fixed {
+  position: fixed;
+  top: 50%;
+  transform: translate(0, -50%);
+}
\ No newline at end of file
diff --git a/node_modules/dialog-polyfill/dist/dialog-polyfill.css b/node_modules/dialog-polyfill/dist/dialog-polyfill.css
new file mode 100644
index 0000000..6b38bf0
--- /dev/null
+++ b/node_modules/dialog-polyfill/dist/dialog-polyfill.css
@@ -0,0 +1,37 @@
+dialog {
+  position: absolute;
+  left: 0; right: 0;
+  width: -moz-fit-content;
+  width: -webkit-fit-content;
+  width: fit-content;
+  height: -moz-fit-content;
+  height: -webkit-fit-content;
+  height: fit-content;
+  margin: auto;
+  border: solid;
+  padding: 1em;
+  background: white;
+  color: black;
+  display: block;
+}
+
+dialog:not([open]) {
+  display: none;
+}
+
+dialog + .backdrop {
+  position: fixed;
+  top: 0; right: 0; bottom: 0; left: 0;
+  background: rgba(0,0,0,0.1);
+}
+
+._dialog_overlay {
+  position: fixed;
+  top: 0; right: 0; bottom: 0; left: 0;
+}
+
+dialog.fixed {
+  position: fixed;
+  top: 50%;
+  transform: translate(0, -50%);
+}
\ No newline at end of file
diff --git a/node_modules/dialog-polyfill/dist/dialog-polyfill.esm.js b/node_modules/dialog-polyfill/dist/dialog-polyfill.esm.js
new file mode 100644
index 0000000..d866b94
--- /dev/null
+++ b/node_modules/dialog-polyfill/dist/dialog-polyfill.esm.js
@@ -0,0 +1,728 @@
+// nb. This is for IE10 and lower _only_.
+var supportCustomEvent = window.CustomEvent;
+if (!supportCustomEvent || typeof supportCustomEvent === 'object') {
+  supportCustomEvent = function CustomEvent(event, x) {
+    x = x || {};
+    var ev = document.createEvent('CustomEvent');
+    ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null);
+    return ev;
+  };
+  supportCustomEvent.prototype = window.Event.prototype;
+}
+
+/**
+ * @param {Element} el to check for stacking context
+ * @return {boolean} whether this el or its parents creates a stacking context
+ */
+function createsStackingContext(el) {
+  while (el && el !== document.body) {
+    var s = window.getComputedStyle(el);
+    var invalid = function(k, ok) {
+      return !(s[k] === undefined || s[k] === ok);
+    };
+    
+    if (s.opacity < 1 ||
+        invalid('zIndex', 'auto') ||
+        invalid('transform', 'none') ||
+        invalid('mixBlendMode', 'normal') ||
+        invalid('filter', 'none') ||
+        invalid('perspective', 'none') ||
+        s['isolation'] === 'isolate' ||
+        s.position === 'fixed' ||
+        s.webkitOverflowScrolling === 'touch') {
+      return true;
+    }
+    el = el.parentElement;
+  }
+  return false;
+}
+
+/**
+ * Finds the nearest <dialog> from the passed element.
+ *
+ * @param {Element} el to search from
+ * @return {HTMLDialogElement} dialog found
+ */
+function findNearestDialog(el) {
+  while (el) {
+    if (el.localName === 'dialog') {
+      return /** @type {HTMLDialogElement} */ (el);
+    }
+    el = el.parentElement;
+  }
+  return null;
+}
+
+/**
+ * Blur the specified element, as long as it's not the HTML body element.
+ * This works around an IE9/10 bug - blurring the body causes Windows to
+ * blur the whole application.
+ *
+ * @param {Element} el to blur
+ */
+function safeBlur(el) {
+  if (el && el.blur && el !== document.body) {
+    el.blur();
+  }
+}
+
+/**
+ * @param {!NodeList} nodeList to search
+ * @param {Node} node to find
+ * @return {boolean} whether node is inside nodeList
+ */
+function inNodeList(nodeList, node) {
+  for (var i = 0; i < nodeList.length; ++i) {
+    if (nodeList[i] === node) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * @param {HTMLFormElement} el to check
+ * @return {boolean} whether this form has method="dialog"
+ */
+function isFormMethodDialog(el) {
+  if (!el || !el.hasAttribute('method')) {
+    return false;
+  }
+  return el.getAttribute('method').toLowerCase() === 'dialog';
+}
+
+/**
+ * @param {!HTMLDialogElement} dialog to upgrade
+ * @constructor
+ */
+function dialogPolyfillInfo(dialog) {
+  this.dialog_ = dialog;
+  this.replacedStyleTop_ = false;
+  this.openAsModal_ = false;
+
+  // Set a11y role. Browsers that support dialog implicitly know this already.
+  if (!dialog.hasAttribute('role')) {
+    dialog.setAttribute('role', 'dialog');
+  }
+
+  dialog.show = this.show.bind(this);
+  dialog.showModal = this.showModal.bind(this);
+  dialog.close = this.close.bind(this);
+
+  if (!('returnValue' in dialog)) {
+    dialog.returnValue = '';
+  }
+
+  if ('MutationObserver' in window) {
+    var mo = new MutationObserver(this.maybeHideModal.bind(this));
+    mo.observe(dialog, {attributes: true, attributeFilter: ['open']});
+  } else {
+    // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
+    // seem to fire even if the element was removed as part of a parent removal. Use the removed
+    // events to force downgrade (useful if removed/immediately added).
+    var removed = false;
+    var cb = function() {
+      removed ? this.downgradeModal() : this.maybeHideModal();
+      removed = false;
+    }.bind(this);
+    var timeout;
+    var delayModel = function(ev) {
+      if (ev.target !== dialog) { return; }  // not for a child element
+      var cand = 'DOMNodeRemoved';
+      removed |= (ev.type.substr(0, cand.length) === cand);
+      window.clearTimeout(timeout);
+      timeout = window.setTimeout(cb, 0);
+    };
+    ['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) {
+      dialog.addEventListener(name, delayModel);
+    });
+  }
+  // Note that the DOM is observed inside DialogManager while any dialog
+  // is being displayed as a modal, to catch modal removal from the DOM.
+
+  Object.defineProperty(dialog, 'open', {
+    set: this.setOpen.bind(this),
+    get: dialog.hasAttribute.bind(dialog, 'open')
+  });
+
+  this.backdrop_ = document.createElement('div');
+  this.backdrop_.className = 'backdrop';
+  this.backdrop_.addEventListener('click', this.backdropClick_.bind(this));
+}
+
+dialogPolyfillInfo.prototype = {
+
+  get dialog() {
+    return this.dialog_;
+  },
+
+  /**
+   * Maybe remove this dialog from the modal top layer. This is called when
+   * a modal dialog may no longer be tenable, e.g., when the dialog is no
+   * longer open or is no longer part of the DOM.
+   */
+  maybeHideModal: function() {
+    if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; }
+    this.downgradeModal();
+  },
+
+  /**
+   * Remove this dialog from the modal top layer, leaving it as a non-modal.
+   */
+  downgradeModal: function() {
+    if (!this.openAsModal_) { return; }
+    this.openAsModal_ = false;
+    this.dialog_.style.zIndex = '';
+
+    // This won't match the native <dialog> exactly because if the user set top on a centered
+    // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
+    // possible to polyfill this perfectly.
+    if (this.replacedStyleTop_) {
+      this.dialog_.style.top = '';
+      this.replacedStyleTop_ = false;
+    }
+
+    // Clear the backdrop and remove from the manager.
+    this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_);
+    dialogPolyfill.dm.removeDialog(this);
+  },
+
+  /**
+   * @param {boolean} value whether to open or close this dialog
+   */
+  setOpen: function(value) {
+    if (value) {
+      this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', '');
+    } else {
+      this.dialog_.removeAttribute('open');
+      this.maybeHideModal();  // nb. redundant with MutationObserver
+    }
+  },
+
+  /**
+   * Handles clicks on the fake .backdrop element, redirecting them as if
+   * they were on the dialog itself.
+   *
+   * @param {!Event} e to redirect
+   */
+  backdropClick_: function(e) {
+    if (!this.dialog_.hasAttribute('tabindex')) {
+      // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
+      // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
+      // would not be needed - clicks would move the implicit cursor there.
+      var fake = document.createElement('div');
+      this.dialog_.insertBefore(fake, this.dialog_.firstChild);
+      fake.tabIndex = -1;
+      fake.focus();
+      this.dialog_.removeChild(fake);
+    } else {
+      this.dialog_.focus();
+    }
+
+    var redirectedEvent = document.createEvent('MouseEvents');
+    redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window,
+        e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey,
+        e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
+    this.dialog_.dispatchEvent(redirectedEvent);
+    e.stopPropagation();
+  },
+
+  /**
+   * Focuses on the first focusable element within the dialog. This will always blur the current
+   * focus, even if nothing within the dialog is found.
+   */
+  focus_: function() {
+    // Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
+    var target = this.dialog_.querySelector('[autofocus]:not([disabled])');
+    if (!target && this.dialog_.tabIndex >= 0) {
+      target = this.dialog_;
+    }
+    if (!target) {
+      // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
+      // alternative involves stepping through and trying to focus everything.
+      var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
+      var query = opts.map(function(el) {
+        return el + ':not([disabled])';
+      });
+      // TODO(samthor): tabindex values that are not numeric are not focusable.
+      query.push('[tabindex]:not([disabled]):not([tabindex=""])');  // tabindex != "", not disabled
+      target = this.dialog_.querySelector(query.join(', '));
+    }
+    safeBlur(document.activeElement);
+    target && target.focus();
+  },
+
+  /**
+   * Sets the zIndex for the backdrop and dialog.
+   *
+   * @param {number} dialogZ
+   * @param {number} backdropZ
+   */
+  updateZIndex: function(dialogZ, backdropZ) {
+    if (dialogZ < backdropZ) {
+      throw new Error('dialogZ should never be < backdropZ');
+    }
+    this.dialog_.style.zIndex = dialogZ;
+    this.backdrop_.style.zIndex = backdropZ;
+  },
+
+  /**
+   * Shows the dialog. If the dialog is already open, this does nothing.
+   */
+  show: function() {
+    if (!this.dialog_.open) {
+      this.setOpen(true);
+      this.focus_();
+    }
+  },
+
+  /**
+   * Show this dialog modally.
+   */
+  showModal: function() {
+    if (this.dialog_.hasAttribute('open')) {
+      throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
+    }
+    if (!document.body.contains(this.dialog_)) {
+      throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
+    }
+    if (!dialogPolyfill.dm.pushDialog(this)) {
+      throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.');
+    }
+
+    if (createsStackingContext(this.dialog_.parentElement)) {
+      console.warn('A dialog is being shown inside a stacking context. ' +
+          'This may cause it to be unusable. For more information, see this link: ' +
+          'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context');
+    }
+
+    this.setOpen(true);
+    this.openAsModal_ = true;
+
+    // Optionally center vertically, relative to the current viewport.
+    if (dialogPolyfill.needsCentering(this.dialog_)) {
+      dialogPolyfill.reposition(this.dialog_);
+      this.replacedStyleTop_ = true;
+    } else {
+      this.replacedStyleTop_ = false;
+    }
+
+    // Insert backdrop.
+    this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling);
+
+    // Focus on whatever inside the dialog.
+    this.focus_();
+  },
+
+  /**
+   * Closes this HTMLDialogElement. This is optional vs clearing the open
+   * attribute, however this fires a 'close' event.
+   *
+   * @param {string=} opt_returnValue to use as the returnValue
+   */
+  close: function(opt_returnValue) {
+    if (!this.dialog_.hasAttribute('open')) {
+      throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.');
+    }
+    this.setOpen(false);
+
+    // Leave returnValue untouched in case it was set directly on the element
+    if (opt_returnValue !== undefined) {
+      this.dialog_.returnValue = opt_returnValue;
+    }
+
+    // Triggering "close" event for any attached listeners on the <dialog>.
+    var closeEvent = new supportCustomEvent('close', {
+      bubbles: false,
+      cancelable: false
+    });
+    this.dialog_.dispatchEvent(closeEvent);
+  }
+
+};
+
+var dialogPolyfill = {};
+
+dialogPolyfill.reposition = function(element) {
+  var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+  var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
+  element.style.top = Math.max(scrollTop, topValue) + 'px';
+};
+
+dialogPolyfill.isInlinePositionSetByStylesheet = function(element) {
+  for (var i = 0; i < document.styleSheets.length; ++i) {
+    var styleSheet = document.styleSheets[i];
+    var cssRules = null;
+    // Some browsers throw on cssRules.
+    try {
+      cssRules = styleSheet.cssRules;
+    } catch (e) {}
+    if (!cssRules) { continue; }
+    for (var j = 0; j < cssRules.length; ++j) {
+      var rule = cssRules[j];
+      var selectedNodes = null;
+      // Ignore errors on invalid selector texts.
+      try {
+        selectedNodes = document.querySelectorAll(rule.selectorText);
+      } catch(e) {}
+      if (!selectedNodes || !inNodeList(selectedNodes, element)) {
+        continue;
+      }
+      var cssTop = rule.style.getPropertyValue('top');
+      var cssBottom = rule.style.getPropertyValue('bottom');
+      if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
+
+dialogPolyfill.needsCentering = function(dialog) {
+  var computedStyle = window.getComputedStyle(dialog);
+  if (computedStyle.position !== 'absolute') {
+    return false;
+  }
+
+  // We must determine whether the top/bottom specified value is non-auto.  In
+  // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
+  // Firefox returns the used value. So we do this crazy thing instead: check
+  // the inline style and then go through CSS rules.
+  if ((dialog.style.top !== 'auto' && dialog.style.top !== '') ||
+      (dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) {
+    return false;
+  }
+  return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
+};
+
+/**
+ * @param {!Element} element to force upgrade
+ */
+dialogPolyfill.forceRegisterDialog = function(element) {
+  if (window.HTMLDialogElement || element.showModal) {
+    console.warn('This browser already supports <dialog>, the polyfill ' +
+        'may not work correctly', element);
+  }
+  if (element.localName !== 'dialog') {
+    throw new Error('Failed to register dialog: The element is not a dialog.');
+  }
+  new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element));
+};
+
+/**
+ * @param {!Element} element to upgrade, if necessary
+ */
+dialogPolyfill.registerDialog = function(element) {
+  if (!element.showModal) {
+    dialogPolyfill.forceRegisterDialog(element);
+  }
+};
+
+/**
+ * @constructor
+ */
+dialogPolyfill.DialogManager = function() {
+  /** @type {!Array<!dialogPolyfillInfo>} */
+  this.pendingDialogStack = [];
+
+  var checkDOM = this.checkDOM_.bind(this);
+
+  // The overlay is used to simulate how a modal dialog blocks the document.
+  // The blocking dialog is positioned on top of the overlay, and the rest of
+  // the dialogs on the pending dialog stack are positioned below it. In the
+  // actual implementation, the modal dialog stacking is controlled by the
+  // top layer, where z-index has no effect.
+  this.overlay = document.createElement('div');
+  this.overlay.className = '_dialog_overlay';
+  this.overlay.addEventListener('click', function(e) {
+    this.forwardTab_ = undefined;
+    e.stopPropagation();
+    checkDOM([]);  // sanity-check DOM
+  }.bind(this));
+
+  this.handleKey_ = this.handleKey_.bind(this);
+  this.handleFocus_ = this.handleFocus_.bind(this);
+
+  this.zIndexLow_ = 100000;
+  this.zIndexHigh_ = 100000 + 150;
+
+  this.forwardTab_ = undefined;
+
+  if ('MutationObserver' in window) {
+    this.mo_ = new MutationObserver(function(records) {
+      var removed = [];
+      records.forEach(function(rec) {
+        for (var i = 0, c; c = rec.removedNodes[i]; ++i) {
+          if (!(c instanceof Element)) {
+            continue;
+          } else if (c.localName === 'dialog') {
+            removed.push(c);
+          }
+          removed = removed.concat(c.querySelectorAll('dialog'));
+        }
+      });
+      removed.length && checkDOM(removed);
+    });
+  }
+};
+
+/**
+ * Called on the first modal dialog being shown. Adds the overlay and related
+ * handlers.
+ */
+dialogPolyfill.DialogManager.prototype.blockDocument = function() {
+  document.documentElement.addEventListener('focus', this.handleFocus_, true);
+  document.addEventListener('keydown', this.handleKey_);
+  this.mo_ && this.mo_.observe(document, {childList: true, subtree: true});
+};
+
+/**
+ * Called on the first modal dialog being removed, i.e., when no more modal
+ * dialogs are visible.
+ */
+dialogPolyfill.DialogManager.prototype.unblockDocument = function() {
+  document.documentElement.removeEventListener('focus', this.handleFocus_, true);
+  document.removeEventListener('keydown', this.handleKey_);
+  this.mo_ && this.mo_.disconnect();
+};
+
+/**
+ * Updates the stacking of all known dialogs.
+ */
+dialogPolyfill.DialogManager.prototype.updateStacking = function() {
+  var zIndex = this.zIndexHigh_;
+
+  for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
+    dpi.updateZIndex(--zIndex, --zIndex);
+    if (i === 0) {
+      this.overlay.style.zIndex = --zIndex;
+    }
+  }
+
+  // Make the overlay a sibling of the dialog itself.
+  var last = this.pendingDialogStack[0];
+  if (last) {
+    var p = last.dialog.parentNode || document.body;
+    p.appendChild(this.overlay);
+  } else if (this.overlay.parentNode) {
+    this.overlay.parentNode.removeChild(this.overlay);
+  }
+};
+
+/**
+ * @param {Element} candidate to check if contained or is the top-most modal dialog
+ * @return {boolean} whether candidate is contained in top dialog
+ */
+dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) {
+  while (candidate = findNearestDialog(candidate)) {
+    for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
+      if (dpi.dialog === candidate) {
+        return i === 0;  // only valid if top-most
+      }
+    }
+    candidate = candidate.parentElement;
+  }
+  return false;
+};
+
+dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
+  if (this.containedByTopDialog_(event.target)) { return; }
+
+  if (document.activeElement === document.documentElement) { return; }
+
+  event.preventDefault();
+  event.stopPropagation();
+  safeBlur(/** @type {Element} */ (event.target));
+
+  if (this.forwardTab_ === undefined) { return; }  // move focus only from a tab key
+
+  var dpi = this.pendingDialogStack[0];
+  var dialog = dpi.dialog;
+  var position = dialog.compareDocumentPosition(event.target);
+  if (position & Node.DOCUMENT_POSITION_PRECEDING) {
+    if (this.forwardTab_) {
+      // forward
+      dpi.focus_();
+    } else if (event.target !== document.documentElement) {
+      // backwards if we're not already focused on <html>
+      document.documentElement.focus();
+    }
+  }
+
+  return false;
+};
+
+dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) {
+  this.forwardTab_ = undefined;
+  if (event.keyCode === 27) {
+    event.preventDefault();
+    event.stopPropagation();
+    var cancelEvent = new supportCustomEvent('cancel', {
+      bubbles: false,
+      cancelable: true
+    });
+    var dpi = this.pendingDialogStack[0];
+    if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
+      dpi.dialog.close();
+    }
+  } else if (event.keyCode === 9) {
+    this.forwardTab_ = !event.shiftKey;
+  }
+};
+
+/**
+ * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
+ * removed and immediately readded don't stay modal, they become normal.
+ *
+ * @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
+ */
+dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) {
+  // This operates on a clone because it may cause it to change. Each change also calls
+  // updateStacking, which only actually needs to happen once. But who removes many modal dialogs
+  // at a time?!
+  var clone = this.pendingDialogStack.slice();
+  clone.forEach(function(dpi) {
+    if (removed.indexOf(dpi.dialog) !== -1) {
+      dpi.downgradeModal();
+    } else {
+      dpi.maybeHideModal();
+    }
+  });
+};
+
+/**
+ * @param {!dialogPolyfillInfo} dpi
+ * @return {boolean} whether the dialog was allowed
+ */
+dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) {
+  var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
+  if (this.pendingDialogStack.length >= allowed) {
+    return false;
+  }
+  if (this.pendingDialogStack.unshift(dpi) === 1) {
+    this.blockDocument();
+  }
+  this.updateStacking();
+  return true;
+};
+
+/**
+ * @param {!dialogPolyfillInfo} dpi
+ */
+dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) {
+  var index = this.pendingDialogStack.indexOf(dpi);
+  if (index === -1) { return; }
+
+  this.pendingDialogStack.splice(index, 1);
+  if (this.pendingDialogStack.length === 0) {
+    this.unblockDocument();
+  }
+  this.updateStacking();
+};
+
+dialogPolyfill.dm = new dialogPolyfill.DialogManager();
+dialogPolyfill.formSubmitter = null;
+dialogPolyfill.useValue = null;
+
+/**
+ * Installs global handlers, such as click listers and native method overrides. These are needed
+ * even if a no dialog is registered, as they deal with <form method="dialog">.
+ */
+if (window.HTMLDialogElement === undefined) {
+
+  /**
+   * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
+   * one that returns the correct value.
+   */
+  var testForm = document.createElement('form');
+  testForm.setAttribute('method', 'dialog');
+  if (testForm.method !== 'dialog') {
+    var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method');
+    if (methodDescriptor) {
+      // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
+      // and don't bother to update the element.
+      var realGet = methodDescriptor.get;
+      methodDescriptor.get = function() {
+        if (isFormMethodDialog(this)) {
+          return 'dialog';
+        }
+        return realGet.call(this);
+      };
+      var realSet = methodDescriptor.set;
+      methodDescriptor.set = function(v) {
+        if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
+          return this.setAttribute('method', v);
+        }
+        return realSet.call(this, v);
+      };
+      Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor);
+    }
+  }
+
+  /**
+   * Global 'click' handler, to capture the <input type="submit"> or <button> element which has
+   * submitted a <form method="dialog">. Needed as Safari and others don't report this inside
+   * document.activeElement.
+   */
+  document.addEventListener('click', function(ev) {
+    dialogPolyfill.formSubmitter = null;
+    dialogPolyfill.useValue = null;
+    if (ev.defaultPrevented) { return; }  // e.g. a submit which prevents default submission
+
+    var target = /** @type {Element} */ (ev.target);
+    if (!target || !isFormMethodDialog(target.form)) { return; }
+
+    var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
+    if (!valid) {
+      if (!(target.localName === 'input' && target.type === 'image')) { return; }
+      // this is a <input type="image">, which can submit forms
+      dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY;
+    }
+
+    var dialog = findNearestDialog(target);
+    if (!dialog) { return; }
+
+    dialogPolyfill.formSubmitter = target;
+
+  }, false);
+
+  /**
+   * Replace the native HTMLFormElement.submit() method, as it won't fire the
+   * submit event and give us a chance to respond.
+   */
+  var nativeFormSubmit = HTMLFormElement.prototype.submit;
+  var replacementFormSubmit = function () {
+    if (!isFormMethodDialog(this)) {
+      return nativeFormSubmit.call(this);
+    }
+    var dialog = findNearestDialog(this);
+    dialog && dialog.close();
+  };
+  HTMLFormElement.prototype.submit = replacementFormSubmit;
+
+  /**
+   * Global form 'dialog' method handler. Closes a dialog correctly on submit
+   * and possibly sets its return value.
+   */
+  document.addEventListener('submit', function(ev) {
+    var form = /** @type {HTMLFormElement} */ (ev.target);
+    if (!isFormMethodDialog(form)) { return; }
+    ev.preventDefault();
+
+    var dialog = findNearestDialog(form);
+    if (!dialog) { return; }
+
+    // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that
+    // the submitter is correct before using its value as .returnValue.
+    var s = dialogPolyfill.formSubmitter;
+    if (s && s.form === form) {
+      dialog.close(dialogPolyfill.useValue || s.value);
+    } else {
+      dialog.close();
+    }
+    dialogPolyfill.formSubmitter = null;
+
+  }, true);
+}
+
+export default dialogPolyfill;
diff --git a/node_modules/dialog-polyfill/dist/dialog-polyfill.js b/node_modules/dialog-polyfill/dist/dialog-polyfill.js
new file mode 100644
index 0000000..72a4ab9
--- /dev/null
+++ b/node_modules/dialog-polyfill/dist/dialog-polyfill.js
@@ -0,0 +1,736 @@
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define(factory) :
+  (global = global || self, global.dialogPolyfill = factory());
+}(this, function () { 'use strict';
+
+  // nb. This is for IE10 and lower _only_.
+  var supportCustomEvent = window.CustomEvent;
+  if (!supportCustomEvent || typeof supportCustomEvent === 'object') {
+    supportCustomEvent = function CustomEvent(event, x) {
+      x = x || {};
+      var ev = document.createEvent('CustomEvent');
+      ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null);
+      return ev;
+    };
+    supportCustomEvent.prototype = window.Event.prototype;
+  }
+
+  /**
+   * @param {Element} el to check for stacking context
+   * @return {boolean} whether this el or its parents creates a stacking context
+   */
+  function createsStackingContext(el) {
+    while (el && el !== document.body) {
+      var s = window.getComputedStyle(el);
+      var invalid = function(k, ok) {
+        return !(s[k] === undefined || s[k] === ok);
+      };
+      
+      if (s.opacity < 1 ||
+          invalid('zIndex', 'auto') ||
+          invalid('transform', 'none') ||
+          invalid('mixBlendMode', 'normal') ||
+          invalid('filter', 'none') ||
+          invalid('perspective', 'none') ||
+          s['isolation'] === 'isolate' ||
+          s.position === 'fixed' ||
+          s.webkitOverflowScrolling === 'touch') {
+        return true;
+      }
+      el = el.parentElement;
+    }
+    return false;
+  }
+
+  /**
+   * Finds the nearest <dialog> from the passed element.
+   *
+   * @param {Element} el to search from
+   * @return {HTMLDialogElement} dialog found
+   */
+  function findNearestDialog(el) {
+    while (el) {
+      if (el.localName === 'dialog') {
+        return /** @type {HTMLDialogElement} */ (el);
+      }
+      el = el.parentElement;
+    }
+    return null;
+  }
+
+  /**
+   * Blur the specified element, as long as it's not the HTML body element.
+   * This works around an IE9/10 bug - blurring the body causes Windows to
+   * blur the whole application.
+   *
+   * @param {Element} el to blur
+   */
+  function safeBlur(el) {
+    if (el && el.blur && el !== document.body) {
+      el.blur();
+    }
+  }
+
+  /**
+   * @param {!NodeList} nodeList to search
+   * @param {Node} node to find
+   * @return {boolean} whether node is inside nodeList
+   */
+  function inNodeList(nodeList, node) {
+    for (var i = 0; i < nodeList.length; ++i) {
+      if (nodeList[i] === node) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @param {HTMLFormElement} el to check
+   * @return {boolean} whether this form has method="dialog"
+   */
+  function isFormMethodDialog(el) {
+    if (!el || !el.hasAttribute('method')) {
+      return false;
+    }
+    return el.getAttribute('method').toLowerCase() === 'dialog';
+  }
+
+  /**
+   * @param {!HTMLDialogElement} dialog to upgrade
+   * @constructor
+   */
+  function dialogPolyfillInfo(dialog) {
+    this.dialog_ = dialog;
+    this.replacedStyleTop_ = false;
+    this.openAsModal_ = false;
+
+    // Set a11y role. Browsers that support dialog implicitly know this already.
+    if (!dialog.hasAttribute('role')) {
+      dialog.setAttribute('role', 'dialog');
+    }
+
+    dialog.show = this.show.bind(this);
+    dialog.showModal = this.showModal.bind(this);
+    dialog.close = this.close.bind(this);
+
+    if (!('returnValue' in dialog)) {
+      dialog.returnValue = '';
+    }
+
+    if ('MutationObserver' in window) {
+      var mo = new MutationObserver(this.maybeHideModal.bind(this));
+      mo.observe(dialog, {attributes: true, attributeFilter: ['open']});
+    } else {
+      // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
+      // seem to fire even if the element was removed as part of a parent removal. Use the removed
+      // events to force downgrade (useful if removed/immediately added).
+      var removed = false;
+      var cb = function() {
+        removed ? this.downgradeModal() : this.maybeHideModal();
+        removed = false;
+      }.bind(this);
+      var timeout;
+      var delayModel = function(ev) {
+        if (ev.target !== dialog) { return; }  // not for a child element
+        var cand = 'DOMNodeRemoved';
+        removed |= (ev.type.substr(0, cand.length) === cand);
+        window.clearTimeout(timeout);
+        timeout = window.setTimeout(cb, 0);
+      };
+      ['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) {
+        dialog.addEventListener(name, delayModel);
+      });
+    }
+    // Note that the DOM is observed inside DialogManager while any dialog
+    // is being displayed as a modal, to catch modal removal from the DOM.
+
+    Object.defineProperty(dialog, 'open', {
+      set: this.setOpen.bind(this),
+      get: dialog.hasAttribute.bind(dialog, 'open')
+    });
+
+    this.backdrop_ = document.createElement('div');
+    this.backdrop_.className = 'backdrop';
+    this.backdrop_.addEventListener('click', this.backdropClick_.bind(this));
+  }
+
+  dialogPolyfillInfo.prototype = {
+
+    get dialog() {
+      return this.dialog_;
+    },
+
+    /**
+     * Maybe remove this dialog from the modal top layer. This is called when
+     * a modal dialog may no longer be tenable, e.g., when the dialog is no
+     * longer open or is no longer part of the DOM.
+     */
+    maybeHideModal: function() {
+      if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; }
+      this.downgradeModal();
+    },
+
+    /**
+     * Remove this dialog from the modal top layer, leaving it as a non-modal.
+     */
+    downgradeModal: function() {
+      if (!this.openAsModal_) { return; }
+      this.openAsModal_ = false;
+      this.dialog_.style.zIndex = '';
+
+      // This won't match the native <dialog> exactly because if the user set top on a centered
+      // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
+      // possible to polyfill this perfectly.
+      if (this.replacedStyleTop_) {
+        this.dialog_.style.top = '';
+        this.replacedStyleTop_ = false;
+      }
+
+      // Clear the backdrop and remove from the manager.
+      this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_);
+      dialogPolyfill.dm.removeDialog(this);
+    },
+
+    /**
+     * @param {boolean} value whether to open or close this dialog
+     */
+    setOpen: function(value) {
+      if (value) {
+        this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', '');
+      } else {
+        this.dialog_.removeAttribute('open');
+        this.maybeHideModal();  // nb. redundant with MutationObserver
+      }
+    },
+
+    /**
+     * Handles clicks on the fake .backdrop element, redirecting them as if
+     * they were on the dialog itself.
+     *
+     * @param {!Event} e to redirect
+     */
+    backdropClick_: function(e) {
+      if (!this.dialog_.hasAttribute('tabindex')) {
+        // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
+        // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
+        // would not be needed - clicks would move the implicit cursor there.
+        var fake = document.createElement('div');
+        this.dialog_.insertBefore(fake, this.dialog_.firstChild);
+        fake.tabIndex = -1;
+        fake.focus();
+        this.dialog_.removeChild(fake);
+      } else {
+        this.dialog_.focus();
+      }
+
+      var redirectedEvent = document.createEvent('MouseEvents');
+      redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window,
+          e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey,
+          e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
+      this.dialog_.dispatchEvent(redirectedEvent);
+      e.stopPropagation();
+    },
+
+    /**
+     * Focuses on the first focusable element within the dialog. This will always blur the current
+     * focus, even if nothing within the dialog is found.
+     */
+    focus_: function() {
+      // Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
+      var target = this.dialog_.querySelector('[autofocus]:not([disabled])');
+      if (!target && this.dialog_.tabIndex >= 0) {
+        target = this.dialog_;
+      }
+      if (!target) {
+        // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
+        // alternative involves stepping through and trying to focus everything.
+        var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
+        var query = opts.map(function(el) {
+          return el + ':not([disabled])';
+        });
+        // TODO(samthor): tabindex values that are not numeric are not focusable.
+        query.push('[tabindex]:not([disabled]):not([tabindex=""])');  // tabindex != "", not disabled
+        target = this.dialog_.querySelector(query.join(', '));
+      }
+      safeBlur(document.activeElement);
+      target && target.focus();
+    },
+
+    /**
+     * Sets the zIndex for the backdrop and dialog.
+     *
+     * @param {number} dialogZ
+     * @param {number} backdropZ
+     */
+    updateZIndex: function(dialogZ, backdropZ) {
+      if (dialogZ < backdropZ) {
+        throw new Error('dialogZ should never be < backdropZ');
+      }
+      this.dialog_.style.zIndex = dialogZ;
+      this.backdrop_.style.zIndex = backdropZ;
+    },
+
+    /**
+     * Shows the dialog. If the dialog is already open, this does nothing.
+     */
+    show: function() {
+      if (!this.dialog_.open) {
+        this.setOpen(true);
+        this.focus_();
+      }
+    },
+
+    /**
+     * Show this dialog modally.
+     */
+    showModal: function() {
+      if (this.dialog_.hasAttribute('open')) {
+        throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
+      }
+      if (!document.body.contains(this.dialog_)) {
+        throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
+      }
+      if (!dialogPolyfill.dm.pushDialog(this)) {
+        throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.');
+      }
+
+      if (createsStackingContext(this.dialog_.parentElement)) {
+        console.warn('A dialog is being shown inside a stacking context. ' +
+            'This may cause it to be unusable. For more information, see this link: ' +
+            'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context');
+      }
+
+      this.setOpen(true);
+      this.openAsModal_ = true;
+
+      // Optionally center vertically, relative to the current viewport.
+      if (dialogPolyfill.needsCentering(this.dialog_)) {
+        dialogPolyfill.reposition(this.dialog_);
+        this.replacedStyleTop_ = true;
+      } else {
+        this.replacedStyleTop_ = false;
+      }
+
+      // Insert backdrop.
+      this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling);
+
+      // Focus on whatever inside the dialog.
+      this.focus_();
+    },
+
+    /**
+     * Closes this HTMLDialogElement. This is optional vs clearing the open
+     * attribute, however this fires a 'close' event.
+     *
+     * @param {string=} opt_returnValue to use as the returnValue
+     */
+    close: function(opt_returnValue) {
+      if (!this.dialog_.hasAttribute('open')) {
+        throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.');
+      }
+      this.setOpen(false);
+
+      // Leave returnValue untouched in case it was set directly on the element
+      if (opt_returnValue !== undefined) {
+        this.dialog_.returnValue = opt_returnValue;
+      }
+
+      // Triggering "close" event for any attached listeners on the <dialog>.
+      var closeEvent = new supportCustomEvent('close', {
+        bubbles: false,
+        cancelable: false
+      });
+      this.dialog_.dispatchEvent(closeEvent);
+    }
+
+  };
+
+  var dialogPolyfill = {};
+
+  dialogPolyfill.reposition = function(element) {
+    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+    var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
+    element.style.top = Math.max(scrollTop, topValue) + 'px';
+  };
+
+  dialogPolyfill.isInlinePositionSetByStylesheet = function(element) {
+    for (var i = 0; i < document.styleSheets.length; ++i) {
+      var styleSheet = document.styleSheets[i];
+      var cssRules = null;
+      // Some browsers throw on cssRules.
+      try {
+        cssRules = styleSheet.cssRules;
+      } catch (e) {}
+      if (!cssRules) { continue; }
+      for (var j = 0; j < cssRules.length; ++j) {
+        var rule = cssRules[j];
+        var selectedNodes = null;
+        // Ignore errors on invalid selector texts.
+        try {
+          selectedNodes = document.querySelectorAll(rule.selectorText);
+        } catch(e) {}
+        if (!selectedNodes || !inNodeList(selectedNodes, element)) {
+          continue;
+        }
+        var cssTop = rule.style.getPropertyValue('top');
+        var cssBottom = rule.style.getPropertyValue('bottom');
+        if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) {
+          return true;
+        }
+      }
+    }
+    return false;
+  };
+
+  dialogPolyfill.needsCentering = function(dialog) {
+    var computedStyle = window.getComputedStyle(dialog);
+    if (computedStyle.position !== 'absolute') {
+      return false;
+    }
+
+    // We must determine whether the top/bottom specified value is non-auto.  In
+    // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
+    // Firefox returns the used value. So we do this crazy thing instead: check
+    // the inline style and then go through CSS rules.
+    if ((dialog.style.top !== 'auto' && dialog.style.top !== '') ||
+        (dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) {
+      return false;
+    }
+    return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
+  };
+
+  /**
+   * @param {!Element} element to force upgrade
+   */
+  dialogPolyfill.forceRegisterDialog = function(element) {
+    if (window.HTMLDialogElement || element.showModal) {
+      console.warn('This browser already supports <dialog>, the polyfill ' +
+          'may not work correctly', element);
+    }
+    if (element.localName !== 'dialog') {
+      throw new Error('Failed to register dialog: The element is not a dialog.');
+    }
+    new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element));
+  };
+
+  /**
+   * @param {!Element} element to upgrade, if necessary
+   */
+  dialogPolyfill.registerDialog = function(element) {
+    if (!element.showModal) {
+      dialogPolyfill.forceRegisterDialog(element);
+    }
+  };
+
+  /**
+   * @constructor
+   */
+  dialogPolyfill.DialogManager = function() {
+    /** @type {!Array<!dialogPolyfillInfo>} */
+    this.pendingDialogStack = [];
+
+    var checkDOM = this.checkDOM_.bind(this);
+
+    // The overlay is used to simulate how a modal dialog blocks the document.
+    // The blocking dialog is positioned on top of the overlay, and the rest of
+    // the dialogs on the pending dialog stack are positioned below it. In the
+    // actual implementation, the modal dialog stacking is controlled by the
+    // top layer, where z-index has no effect.
+    this.overlay = document.createElement('div');
+    this.overlay.className = '_dialog_overlay';
+    this.overlay.addEventListener('click', function(e) {
+      this.forwardTab_ = undefined;
+      e.stopPropagation();
+      checkDOM([]);  // sanity-check DOM
+    }.bind(this));
+
+    this.handleKey_ = this.handleKey_.bind(this);
+    this.handleFocus_ = this.handleFocus_.bind(this);
+
+    this.zIndexLow_ = 100000;
+    this.zIndexHigh_ = 100000 + 150;
+
+    this.forwardTab_ = undefined;
+
+    if ('MutationObserver' in window) {
+      this.mo_ = new MutationObserver(function(records) {
+        var removed = [];
+        records.forEach(function(rec) {
+          for (var i = 0, c; c = rec.removedNodes[i]; ++i) {
+            if (!(c instanceof Element)) {
+              continue;
+            } else if (c.localName === 'dialog') {
+              removed.push(c);
+            }
+            removed = removed.concat(c.querySelectorAll('dialog'));
+          }
+        });
+        removed.length && checkDOM(removed);
+      });
+    }
+  };
+
+  /**
+   * Called on the first modal dialog being shown. Adds the overlay and related
+   * handlers.
+   */
+  dialogPolyfill.DialogManager.prototype.blockDocument = function() {
+    document.documentElement.addEventListener('focus', this.handleFocus_, true);
+    document.addEventListener('keydown', this.handleKey_);
+    this.mo_ && this.mo_.observe(document, {childList: true, subtree: true});
+  };
+
+  /**
+   * Called on the first modal dialog being removed, i.e., when no more modal
+   * dialogs are visible.
+   */
+  dialogPolyfill.DialogManager.prototype.unblockDocument = function() {
+    document.documentElement.removeEventListener('focus', this.handleFocus_, true);
+    document.removeEventListener('keydown', this.handleKey_);
+    this.mo_ && this.mo_.disconnect();
+  };
+
+  /**
+   * Updates the stacking of all known dialogs.
+   */
+  dialogPolyfill.DialogManager.prototype.updateStacking = function() {
+    var zIndex = this.zIndexHigh_;
+
+    for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
+      dpi.updateZIndex(--zIndex, --zIndex);
+      if (i === 0) {
+        this.overlay.style.zIndex = --zIndex;
+      }
+    }
+
+    // Make the overlay a sibling of the dialog itself.
+    var last = this.pendingDialogStack[0];
+    if (last) {
+      var p = last.dialog.parentNode || document.body;
+      p.appendChild(this.overlay);
+    } else if (this.overlay.parentNode) {
+      this.overlay.parentNode.removeChild(this.overlay);
+    }
+  };
+
+  /**
+   * @param {Element} candidate to check if contained or is the top-most modal dialog
+   * @return {boolean} whether candidate is contained in top dialog
+   */
+  dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) {
+    while (candidate = findNearestDialog(candidate)) {
+      for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
+        if (dpi.dialog === candidate) {
+          return i === 0;  // only valid if top-most
+        }
+      }
+      candidate = candidate.parentElement;
+    }
+    return false;
+  };
+
+  dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
+    if (this.containedByTopDialog_(event.target)) { return; }
+
+    if (document.activeElement === document.documentElement) { return; }
+
+    event.preventDefault();
+    event.stopPropagation();
+    safeBlur(/** @type {Element} */ (event.target));
+
+    if (this.forwardTab_ === undefined) { return; }  // move focus only from a tab key
+
+    var dpi = this.pendingDialogStack[0];
+    var dialog = dpi.dialog;
+    var position = dialog.compareDocumentPosition(event.target);
+    if (position & Node.DOCUMENT_POSITION_PRECEDING) {
+      if (this.forwardTab_) {
+        // forward
+        dpi.focus_();
+      } else if (event.target !== document.documentElement) {
+        // backwards if we're not already focused on <html>
+        document.documentElement.focus();
+      }
+    }
+
+    return false;
+  };
+
+  dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) {
+    this.forwardTab_ = undefined;
+    if (event.keyCode === 27) {
+      event.preventDefault();
+      event.stopPropagation();
+      var cancelEvent = new supportCustomEvent('cancel', {
+        bubbles: false,
+        cancelable: true
+      });
+      var dpi = this.pendingDialogStack[0];
+      if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
+        dpi.dialog.close();
+      }
+    } else if (event.keyCode === 9) {
+      this.forwardTab_ = !event.shiftKey;
+    }
+  };
+
+  /**
+   * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
+   * removed and immediately readded don't stay modal, they become normal.
+   *
+   * @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
+   */
+  dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) {
+    // This operates on a clone because it may cause it to change. Each change also calls
+    // updateStacking, which only actually needs to happen once. But who removes many modal dialogs
+    // at a time?!
+    var clone = this.pendingDialogStack.slice();
+    clone.forEach(function(dpi) {
+      if (removed.indexOf(dpi.dialog) !== -1) {
+        dpi.downgradeModal();
+      } else {
+        dpi.maybeHideModal();
+      }
+    });
+  };
+
+  /**
+   * @param {!dialogPolyfillInfo} dpi
+   * @return {boolean} whether the dialog was allowed
+   */
+  dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) {
+    var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
+    if (this.pendingDialogStack.length >= allowed) {
+      return false;
+    }
+    if (this.pendingDialogStack.unshift(dpi) === 1) {
+      this.blockDocument();
+    }
+    this.updateStacking();
+    return true;
+  };
+
+  /**
+   * @param {!dialogPolyfillInfo} dpi
+   */
+  dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) {
+    var index = this.pendingDialogStack.indexOf(dpi);
+    if (index === -1) { return; }
+
+    this.pendingDialogStack.splice(index, 1);
+    if (this.pendingDialogStack.length === 0) {
+      this.unblockDocument();
+    }
+    this.updateStacking();
+  };
+
+  dialogPolyfill.dm = new dialogPolyfill.DialogManager();
+  dialogPolyfill.formSubmitter = null;
+  dialogPolyfill.useValue = null;
+
+  /**
+   * Installs global handlers, such as click listers and native method overrides. These are needed
+   * even if a no dialog is registered, as they deal with <form method="dialog">.
+   */
+  if (window.HTMLDialogElement === undefined) {
+
+    /**
+     * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
+     * one that returns the correct value.
+     */
+    var testForm = document.createElement('form');
+    testForm.setAttribute('method', 'dialog');
+    if (testForm.method !== 'dialog') {
+      var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method');
+      if (methodDescriptor) {
+        // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
+        // and don't bother to update the element.
+        var realGet = methodDescriptor.get;
+        methodDescriptor.get = function() {
+          if (isFormMethodDialog(this)) {
+            return 'dialog';
+          }
+          return realGet.call(this);
+        };
+        var realSet = methodDescriptor.set;
+        methodDescriptor.set = function(v) {
+          if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
+            return this.setAttribute('method', v);
+          }
+          return realSet.call(this, v);
+        };
+        Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor);
+      }
+    }
+
+    /**
+     * Global 'click' handler, to capture the <input type="submit"> or <button> element which has
+     * submitted a <form method="dialog">. Needed as Safari and others don't report this inside
+     * document.activeElement.
+     */
+    document.addEventListener('click', function(ev) {
+      dialogPolyfill.formSubmitter = null;
+      dialogPolyfill.useValue = null;
+      if (ev.defaultPrevented) { return; }  // e.g. a submit which prevents default submission
+
+      var target = /** @type {Element} */ (ev.target);
+      if (!target || !isFormMethodDialog(target.form)) { return; }
+
+      var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
+      if (!valid) {
+        if (!(target.localName === 'input' && target.type === 'image')) { return; }
+        // this is a <input type="image">, which can submit forms
+        dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY;
+      }
+
+      var dialog = findNearestDialog(target);
+      if (!dialog) { return; }
+
+      dialogPolyfill.formSubmitter = target;
+
+    }, false);
+
+    /**
+     * Replace the native HTMLFormElement.submit() method, as it won't fire the
+     * submit event and give us a chance to respond.
+     */
+    var nativeFormSubmit = HTMLFormElement.prototype.submit;
+    var replacementFormSubmit = function () {
+      if (!isFormMethodDialog(this)) {
+        return nativeFormSubmit.call(this);
+      }
+      var dialog = findNearestDialog(this);
+      dialog && dialog.close();
+    };
+    HTMLFormElement.prototype.submit = replacementFormSubmit;
+
+    /**
+     * Global form 'dialog' method handler. Closes a dialog correctly on submit
+     * and possibly sets its return value.
+     */
+    document.addEventListener('submit', function(ev) {
+      var form = /** @type {HTMLFormElement} */ (ev.target);
+      if (!isFormMethodDialog(form)) { return; }
+      ev.preventDefault();
+
+      var dialog = findNearestDialog(form);
+      if (!dialog) { return; }
+
+      // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that
+      // the submitter is correct before using its value as .returnValue.
+      var s = dialogPolyfill.formSubmitter;
+      if (s && s.form === form) {
+        dialog.close(dialogPolyfill.useValue || s.value);
+      } else {
+        dialog.close();
+      }
+      dialogPolyfill.formSubmitter = null;
+
+    }, true);
+  }
+
+  return dialogPolyfill;
+
+}));
diff --git a/node_modules/dialog-polyfill/index.js b/node_modules/dialog-polyfill/index.js
new file mode 100644
index 0000000..e4db8da
--- /dev/null
+++ b/node_modules/dialog-polyfill/index.js
@@ -0,0 +1,732 @@
+
+// nb. This is for IE10 and lower _only_.
+var supportCustomEvent = window.CustomEvent;
+if (!supportCustomEvent || typeof supportCustomEvent === 'object') {
+  supportCustomEvent = function CustomEvent(event, x) {
+    x = x || {};
+    var ev = document.createEvent('CustomEvent');
+    ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null);
+    return ev;
+  };
+  supportCustomEvent.prototype = window.Event.prototype;
+}
+
+/**
+ * @param {Element} el to check for stacking context
+ * @return {boolean} whether this el or its parents creates a stacking context
+ */
+function createsStackingContext(el) {
+  while (el && el !== document.body) {
+    var s = window.getComputedStyle(el);
+    var invalid = function(k, ok) {
+      return !(s[k] === undefined || s[k] === ok);
+    };
+    
+    if (s.opacity < 1 ||
+        invalid('zIndex', 'auto') ||
+        invalid('transform', 'none') ||
+        invalid('mixBlendMode', 'normal') ||
+        invalid('filter', 'none') ||
+        invalid('perspective', 'none') ||
+        s['isolation'] === 'isolate' ||
+        s.position === 'fixed' ||
+        s.webkitOverflowScrolling === 'touch') {
+      return true;
+    }
+    el = el.parentElement;
+  }
+  return false;
+}
+
+/**
+ * Finds the nearest <dialog> from the passed element.
+ *
+ * @param {Element} el to search from
+ * @return {HTMLDialogElement} dialog found
+ */
+function findNearestDialog(el) {
+  while (el) {
+    if (el.localName === 'dialog') {
+      return /** @type {HTMLDialogElement} */ (el);
+    }
+    el = el.parentElement;
+  }
+  return null;
+}
+
+/**
+ * Blur the specified element, as long as it's not the HTML body element.
+ * This works around an IE9/10 bug - blurring the body causes Windows to
+ * blur the whole application.
+ *
+ * @param {Element} el to blur
+ */
+function safeBlur(el) {
+  if (el && el.blur && el !== document.body) {
+    el.blur();
+  }
+}
+
+/**
+ * @param {!NodeList} nodeList to search
+ * @param {Node} node to find
+ * @return {boolean} whether node is inside nodeList
+ */
+function inNodeList(nodeList, node) {
+  for (var i = 0; i < nodeList.length; ++i) {
+    if (nodeList[i] === node) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * @param {HTMLFormElement} el to check
+ * @return {boolean} whether this form has method="dialog"
+ */
+function isFormMethodDialog(el) {
+  if (!el || !el.hasAttribute('method')) {
+    return false;
+  }
+  return el.getAttribute('method').toLowerCase() === 'dialog';
+}
+
+/**
+ * @param {!HTMLDialogElement} dialog to upgrade
+ * @constructor
+ */
+function dialogPolyfillInfo(dialog) {
+  this.dialog_ = dialog;
+  this.replacedStyleTop_ = false;
+  this.openAsModal_ = false;
+
+  // Set a11y role. Browsers that support dialog implicitly know this already.
+  if (!dialog.hasAttribute('role')) {
+    dialog.setAttribute('role', 'dialog');
+  }
+
+  dialog.show = this.show.bind(this);
+  dialog.showModal = this.showModal.bind(this);
+  dialog.close = this.close.bind(this);
+
+  if (!('returnValue' in dialog)) {
+    dialog.returnValue = '';
+  }
+
+  if ('MutationObserver' in window) {
+    var mo = new MutationObserver(this.maybeHideModal.bind(this));
+    mo.observe(dialog, {attributes: true, attributeFilter: ['open']});
+  } else {
+    // IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
+    // seem to fire even if the element was removed as part of a parent removal. Use the removed
+    // events to force downgrade (useful if removed/immediately added).
+    var removed = false;
+    var cb = function() {
+      removed ? this.downgradeModal() : this.maybeHideModal();
+      removed = false;
+    }.bind(this);
+    var timeout;
+    var delayModel = function(ev) {
+      if (ev.target !== dialog) { return; }  // not for a child element
+      var cand = 'DOMNodeRemoved';
+      removed |= (ev.type.substr(0, cand.length) === cand);
+      window.clearTimeout(timeout);
+      timeout = window.setTimeout(cb, 0);
+    };
+    ['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) {
+      dialog.addEventListener(name, delayModel);
+    });
+  }
+  // Note that the DOM is observed inside DialogManager while any dialog
+  // is being displayed as a modal, to catch modal removal from the DOM.
+
+  Object.defineProperty(dialog, 'open', {
+    set: this.setOpen.bind(this),
+    get: dialog.hasAttribute.bind(dialog, 'open')
+  });
+
+  this.backdrop_ = document.createElement('div');
+  this.backdrop_.className = 'backdrop';
+  this.backdrop_.addEventListener('click', this.backdropClick_.bind(this));
+}
+
+dialogPolyfillInfo.prototype = {
+
+  get dialog() {
+    return this.dialog_;
+  },
+
+  /**
+   * Maybe remove this dialog from the modal top layer. This is called when
+   * a modal dialog may no longer be tenable, e.g., when the dialog is no
+   * longer open or is no longer part of the DOM.
+   */
+  maybeHideModal: function() {
+    if (this.dialog_.hasAttribute('open') && document.body.contains(this.dialog_)) { return; }
+    this.downgradeModal();
+  },
+
+  /**
+   * Remove this dialog from the modal top layer, leaving it as a non-modal.
+   */
+  downgradeModal: function() {
+    if (!this.openAsModal_) { return; }
+    this.openAsModal_ = false;
+    this.dialog_.style.zIndex = '';
+
+    // This won't match the native <dialog> exactly because if the user set top on a centered
+    // polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
+    // possible to polyfill this perfectly.
+    if (this.replacedStyleTop_) {
+      this.dialog_.style.top = '';
+      this.replacedStyleTop_ = false;
+    }
+
+    // Clear the backdrop and remove from the manager.
+    this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_);
+    dialogPolyfill.dm.removeDialog(this);
+  },
+
+  /**
+   * @param {boolean} value whether to open or close this dialog
+   */
+  setOpen: function(value) {
+    if (value) {
+      this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', '');
+    } else {
+      this.dialog_.removeAttribute('open');
+      this.maybeHideModal();  // nb. redundant with MutationObserver
+    }
+  },
+
+  /**
+   * Handles clicks on the fake .backdrop element, redirecting them as if
+   * they were on the dialog itself.
+   *
+   * @param {!Event} e to redirect
+   */
+  backdropClick_: function(e) {
+    if (!this.dialog_.hasAttribute('tabindex')) {
+      // Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
+      // focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
+      // would not be needed - clicks would move the implicit cursor there.
+      var fake = document.createElement('div');
+      this.dialog_.insertBefore(fake, this.dialog_.firstChild);
+      fake.tabIndex = -1;
+      fake.focus();
+      this.dialog_.removeChild(fake);
+    } else {
+      this.dialog_.focus();
+    }
+
+    var redirectedEvent = document.createEvent('MouseEvents');
+    redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window,
+        e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey,
+        e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
+    this.dialog_.dispatchEvent(redirectedEvent);
+    e.stopPropagation();
+  },
+
+  /**
+   * Focuses on the first focusable element within the dialog. This will always blur the current
+   * focus, even if nothing within the dialog is found.
+   */
+  focus_: function() {
+    // Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
+    var target = this.dialog_.querySelector('[autofocus]:not([disabled])');
+    if (!target && this.dialog_.tabIndex >= 0) {
+      target = this.dialog_;
+    }
+    if (!target) {
+      // Note that this is 'any focusable area'. This list is probably not exhaustive, but the
+      // alternative involves stepping through and trying to focus everything.
+      var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
+      var query = opts.map(function(el) {
+        return el + ':not([disabled])';
+      });
+      // TODO(samthor): tabindex values that are not numeric are not focusable.
+      query.push('[tabindex]:not([disabled]):not([tabindex=""])');  // tabindex != "", not disabled
+      target = this.dialog_.querySelector(query.join(', '));
+    }
+    safeBlur(document.activeElement);
+    target && target.focus();
+  },
+
+  /**
+   * Sets the zIndex for the backdrop and dialog.
+   *
+   * @param {number} dialogZ
+   * @param {number} backdropZ
+   */
+  updateZIndex: function(dialogZ, backdropZ) {
+    if (dialogZ < backdropZ) {
+      throw new Error('dialogZ should never be < backdropZ');
+    }
+    this.dialog_.style.zIndex = dialogZ;
+    this.backdrop_.style.zIndex = backdropZ;
+  },
+
+  /**
+   * Shows the dialog. If the dialog is already open, this does nothing.
+   */
+  show: function() {
+    if (!this.dialog_.open) {
+      this.setOpen(true);
+      this.focus_();
+    }
+  },
+
+  /**
+   * Show this dialog modally.
+   */
+  showModal: function() {
+    if (this.dialog_.hasAttribute('open')) {
+      throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
+    }
+    if (!document.body.contains(this.dialog_)) {
+      throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
+    }
+    if (!dialogPolyfill.dm.pushDialog(this)) {
+      throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.');
+    }
+
+    if (createsStackingContext(this.dialog_.parentElement)) {
+      console.warn('A dialog is being shown inside a stacking context. ' +
+          'This may cause it to be unusable. For more information, see this link: ' +
+          'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context');
+    }
+
+    this.setOpen(true);
+    this.openAsModal_ = true;
+
+    // Optionally center vertically, relative to the current viewport.
+    if (dialogPolyfill.needsCentering(this.dialog_)) {
+      dialogPolyfill.reposition(this.dialog_);
+      this.replacedStyleTop_ = true;
+    } else {
+      this.replacedStyleTop_ = false;
+    }
+
+    // Insert backdrop.
+    this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling);
+
+    // Focus on whatever inside the dialog.
+    this.focus_();
+  },
+
+  /**
+   * Closes this HTMLDialogElement. This is optional vs clearing the open
+   * attribute, however this fires a 'close' event.
+   *
+   * @param {string=} opt_returnValue to use as the returnValue
+   */
+  close: function(opt_returnValue) {
+    if (!this.dialog_.hasAttribute('open')) {
+      throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.');
+    }
+    this.setOpen(false);
+
+    // Leave returnValue untouched in case it was set directly on the element
+    if (opt_returnValue !== undefined) {
+      this.dialog_.returnValue = opt_returnValue;
+    }
+
+    // Triggering "close" event for any attached listeners on the <dialog>.
+    var closeEvent = new supportCustomEvent('close', {
+      bubbles: false,
+      cancelable: false
+    });
+    this.dialog_.dispatchEvent(closeEvent);
+  }
+
+};
+
+var dialogPolyfill = {};
+
+dialogPolyfill.reposition = function(element) {
+  var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+  var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
+  element.style.top = Math.max(scrollTop, topValue) + 'px';
+};
+
+dialogPolyfill.isInlinePositionSetByStylesheet = function(element) {
+  for (var i = 0; i < document.styleSheets.length; ++i) {
+    var styleSheet = document.styleSheets[i];
+    var cssRules = null;
+    // Some browsers throw on cssRules.
+    try {
+      cssRules = styleSheet.cssRules;
+    } catch (e) {}
+    if (!cssRules) { continue; }
+    for (var j = 0; j < cssRules.length; ++j) {
+      var rule = cssRules[j];
+      var selectedNodes = null;
+      // Ignore errors on invalid selector texts.
+      try {
+        selectedNodes = document.querySelectorAll(rule.selectorText);
+      } catch(e) {}
+      if (!selectedNodes || !inNodeList(selectedNodes, element)) {
+        continue;
+      }
+      var cssTop = rule.style.getPropertyValue('top');
+      var cssBottom = rule.style.getPropertyValue('bottom');
+      if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) {
+        return true;
+      }
+    }
+  }
+  return false;
+};
+
+dialogPolyfill.needsCentering = function(dialog) {
+  var computedStyle = window.getComputedStyle(dialog);
+  if (computedStyle.position !== 'absolute') {
+    return false;
+  }
+
+  // We must determine whether the top/bottom specified value is non-auto.  In
+  // WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
+  // Firefox returns the used value. So we do this crazy thing instead: check
+  // the inline style and then go through CSS rules.
+  if ((dialog.style.top !== 'auto' && dialog.style.top !== '') ||
+      (dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) {
+    return false;
+  }
+  return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
+};
+
+/**
+ * @param {!Element} element to force upgrade
+ */
+dialogPolyfill.forceRegisterDialog = function(element) {
+  if (window.HTMLDialogElement || element.showModal) {
+    console.warn('This browser already supports <dialog>, the polyfill ' +
+        'may not work correctly', element);
+  }
+  if (element.localName !== 'dialog') {
+    throw new Error('Failed to register dialog: The element is not a dialog.');
+  }
+  new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element));
+};
+
+/**
+ * @param {!Element} element to upgrade, if necessary
+ */
+dialogPolyfill.registerDialog = function(element) {
+  if (!element.showModal) {
+    dialogPolyfill.forceRegisterDialog(element);
+  }
+};
+
+/**
+ * @constructor
+ */
+dialogPolyfill.DialogManager = function() {
+  /** @type {!Array<!dialogPolyfillInfo>} */
+  this.pendingDialogStack = [];
+
+  var checkDOM = this.checkDOM_.bind(this);
+
+  // The overlay is used to simulate how a modal dialog blocks the document.
+  // The blocking dialog is positioned on top of the overlay, and the rest of
+  // the dialogs on the pending dialog stack are positioned below it. In the
+  // actual implementation, the modal dialog stacking is controlled by the
+  // top layer, where z-index has no effect.
+  this.overlay = document.createElement('div');
+  this.overlay.className = '_dialog_overlay';
+  this.overlay.addEventListener('click', function(e) {
+    this.forwardTab_ = undefined;
+    e.stopPropagation();
+    checkDOM([]);  // sanity-check DOM
+  }.bind(this));
+
+  this.handleKey_ = this.handleKey_.bind(this);
+  this.handleFocus_ = this.handleFocus_.bind(this);
+
+  this.zIndexLow_ = 100000;
+  this.zIndexHigh_ = 100000 + 150;
+
+  this.forwardTab_ = undefined;
+
+  if ('MutationObserver' in window) {
+    this.mo_ = new MutationObserver(function(records) {
+      var removed = [];
+      records.forEach(function(rec) {
+        for (var i = 0, c; c = rec.removedNodes[i]; ++i) {
+          if (!(c instanceof Element)) {
+            continue;
+          } else if (c.localName === 'dialog') {
+            removed.push(c);
+          }
+          removed = removed.concat(c.querySelectorAll('dialog'));
+        }
+      });
+      removed.length && checkDOM(removed);
+    });
+  }
+};
+
+/**
+ * Called on the first modal dialog being shown. Adds the overlay and related
+ * handlers.
+ */
+dialogPolyfill.DialogManager.prototype.blockDocument = function() {
+  document.documentElement.addEventListener('focus', this.handleFocus_, true);
+  document.addEventListener('keydown', this.handleKey_);
+  this.mo_ && this.mo_.observe(document, {childList: true, subtree: true});
+};
+
+/**
+ * Called on the first modal dialog being removed, i.e., when no more modal
+ * dialogs are visible.
+ */
+dialogPolyfill.DialogManager.prototype.unblockDocument = function() {
+  document.documentElement.removeEventListener('focus', this.handleFocus_, true);
+  document.removeEventListener('keydown', this.handleKey_);
+  this.mo_ && this.mo_.disconnect();
+};
+
+/**
+ * Updates the stacking of all known dialogs.
+ */
+dialogPolyfill.DialogManager.prototype.updateStacking = function() {
+  var zIndex = this.zIndexHigh_;
+
+  for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
+    dpi.updateZIndex(--zIndex, --zIndex);
+    if (i === 0) {
+      this.overlay.style.zIndex = --zIndex;
+    }
+  }
+
+  // Make the overlay a sibling of the dialog itself.
+  var last = this.pendingDialogStack[0];
+  if (last) {
+    var p = last.dialog.parentNode || document.body;
+    p.appendChild(this.overlay);
+  } else if (this.overlay.parentNode) {
+    this.overlay.parentNode.removeChild(this.overlay);
+  }
+};
+
+/**
+ * @param {Element} candidate to check if contained or is the top-most modal dialog
+ * @return {boolean} whether candidate is contained in top dialog
+ */
+dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) {
+  while (candidate = findNearestDialog(candidate)) {
+    for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
+      if (dpi.dialog === candidate) {
+        return i === 0;  // only valid if top-most
+      }
+    }
+    candidate = candidate.parentElement;
+  }
+  return false;
+};
+
+dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
+  if (this.containedByTopDialog_(event.target)) { return; }
+
+  if (document.activeElement === document.documentElement) { return; }
+
+  event.preventDefault();
+  event.stopPropagation();
+  safeBlur(/** @type {Element} */ (event.target));
+
+  if (this.forwardTab_ === undefined) { return; }  // move focus only from a tab key
+
+  var dpi = this.pendingDialogStack[0];
+  var dialog = dpi.dialog;
+  var position = dialog.compareDocumentPosition(event.target);
+  if (position & Node.DOCUMENT_POSITION_PRECEDING) {
+    if (this.forwardTab_) {
+      // forward
+      dpi.focus_();
+    } else if (event.target !== document.documentElement) {
+      // backwards if we're not already focused on <html>
+      document.documentElement.focus();
+    }
+  } else {
+    // TODO: Focus after the dialog, is ignored.
+  }
+
+  return false;
+};
+
+dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) {
+  this.forwardTab_ = undefined;
+  if (event.keyCode === 27) {
+    event.preventDefault();
+    event.stopPropagation();
+    var cancelEvent = new supportCustomEvent('cancel', {
+      bubbles: false,
+      cancelable: true
+    });
+    var dpi = this.pendingDialogStack[0];
+    if (dpi && dpi.dialog.dispatchEvent(cancelEvent)) {
+      dpi.dialog.close();
+    }
+  } else if (event.keyCode === 9) {
+    this.forwardTab_ = !event.shiftKey;
+  }
+};
+
+/**
+ * Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
+ * removed and immediately readded don't stay modal, they become normal.
+ *
+ * @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
+ */
+dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) {
+  // This operates on a clone because it may cause it to change. Each change also calls
+  // updateStacking, which only actually needs to happen once. But who removes many modal dialogs
+  // at a time?!
+  var clone = this.pendingDialogStack.slice();
+  clone.forEach(function(dpi) {
+    if (removed.indexOf(dpi.dialog) !== -1) {
+      dpi.downgradeModal();
+    } else {
+      dpi.maybeHideModal();
+    }
+  });
+};
+
+/**
+ * @param {!dialogPolyfillInfo} dpi
+ * @return {boolean} whether the dialog was allowed
+ */
+dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) {
+  var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
+  if (this.pendingDialogStack.length >= allowed) {
+    return false;
+  }
+  if (this.pendingDialogStack.unshift(dpi) === 1) {
+    this.blockDocument();
+  }
+  this.updateStacking();
+  return true;
+};
+
+/**
+ * @param {!dialogPolyfillInfo} dpi
+ */
+dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) {
+  var index = this.pendingDialogStack.indexOf(dpi);
+  if (index === -1) { return; }
+
+  this.pendingDialogStack.splice(index, 1);
+  if (this.pendingDialogStack.length === 0) {
+    this.unblockDocument();
+  }
+  this.updateStacking();
+};
+
+dialogPolyfill.dm = new dialogPolyfill.DialogManager();
+dialogPolyfill.formSubmitter = null;
+dialogPolyfill.useValue = null;
+
+/**
+ * Installs global handlers, such as click listers and native method overrides. These are needed
+ * even if a no dialog is registered, as they deal with <form method="dialog">.
+ */
+if (window.HTMLDialogElement === undefined) {
+
+  /**
+   * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
+   * one that returns the correct value.
+   */
+  var testForm = document.createElement('form');
+  testForm.setAttribute('method', 'dialog');
+  if (testForm.method !== 'dialog') {
+    var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method');
+    if (methodDescriptor) {
+      // nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
+      // and don't bother to update the element.
+      var realGet = methodDescriptor.get;
+      methodDescriptor.get = function() {
+        if (isFormMethodDialog(this)) {
+          return 'dialog';
+        }
+        return realGet.call(this);
+      };
+      var realSet = methodDescriptor.set;
+      methodDescriptor.set = function(v) {
+        if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
+          return this.setAttribute('method', v);
+        }
+        return realSet.call(this, v);
+      };
+      Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor);
+    }
+  }
+
+  /**
+   * Global 'click' handler, to capture the <input type="submit"> or <button> element which has
+   * submitted a <form method="dialog">. Needed as Safari and others don't report this inside
+   * document.activeElement.
+   */
+  document.addEventListener('click', function(ev) {
+    dialogPolyfill.formSubmitter = null;
+    dialogPolyfill.useValue = null;
+    if (ev.defaultPrevented) { return; }  // e.g. a submit which prevents default submission
+
+    var target = /** @type {Element} */ (ev.target);
+    if (!target || !isFormMethodDialog(target.form)) { return; }
+
+    var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
+    if (!valid) {
+      if (!(target.localName === 'input' && target.type === 'image')) { return; }
+      // this is a <input type="image">, which can submit forms
+      dialogPolyfill.useValue = ev.offsetX + ',' + ev.offsetY;
+    }
+
+    var dialog = findNearestDialog(target);
+    if (!dialog) { return; }
+
+    dialogPolyfill.formSubmitter = target;
+
+  }, false);
+
+  /**
+   * Replace the native HTMLFormElement.submit() method, as it won't fire the
+   * submit event and give us a chance to respond.
+   */
+  var nativeFormSubmit = HTMLFormElement.prototype.submit;
+  var replacementFormSubmit = function () {
+    if (!isFormMethodDialog(this)) {
+      return nativeFormSubmit.call(this);
+    }
+    var dialog = findNearestDialog(this);
+    dialog && dialog.close();
+  };
+  HTMLFormElement.prototype.submit = replacementFormSubmit;
+
+  /**
+   * Global form 'dialog' method handler. Closes a dialog correctly on submit
+   * and possibly sets its return value.
+   */
+  document.addEventListener('submit', function(ev) {
+    var form = /** @type {HTMLFormElement} */ (ev.target);
+    if (!isFormMethodDialog(form)) { return; }
+    ev.preventDefault();
+
+    var dialog = findNearestDialog(form);
+    if (!dialog) { return; }
+
+    // Forms can only be submitted via .submit() or a click (?), but anyway: sanity-check that
+    // the submitter is correct before using its value as .returnValue.
+    var s = dialogPolyfill.formSubmitter;
+    if (s && s.form === form) {
+      dialog.close(dialogPolyfill.useValue || s.value);
+    } else {
+      dialog.close();
+    }
+    dialogPolyfill.formSubmitter = null;
+
+  }, true);
+}
+
+
+export default dialogPolyfill;
diff --git a/node_modules/dialog-polyfill/package-lock.json b/node_modules/dialog-polyfill/package-lock.json
new file mode 100644
index 0000000..fcc690e
--- /dev/null
+++ b/node_modules/dialog-polyfill/package-lock.json
@@ -0,0 +1,697 @@
+{
+  "name": "dialog-polyfill",
+  "version": "0.4.10",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@types/estree": {
+      "version": "0.0.39",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+      "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "11.9.5",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.5.tgz",
+      "integrity": "sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q==",
+      "dev": true
+    },
+    "acorn": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz",
+      "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "array-filter": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
+      "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
+      "dev": true
+    },
+    "array-map": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
+      "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
+      "dev": true
+    },
+    "array-reduce": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
+      "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
+      "dev": true
+    },
+    "assertion-error": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "browser-stdout": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+      "dev": true
+    },
+    "chai": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
+      "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+      "dev": true,
+      "requires": {
+        "assertion-error": "^1.1.0",
+        "check-error": "^1.0.2",
+        "deep-eql": "^3.0.1",
+        "get-func-name": "^2.0.0",
+        "pathval": "^1.1.0",
+        "type-detect": "^4.0.5"
+      }
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "check-error": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+      "dev": true
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "commander": {
+      "version": "2.15.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+      "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "debug": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+      "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+      "dev": true,
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "deep-eql": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+      "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+      "dev": true,
+      "requires": {
+        "type-detect": "^4.0.0"
+      }
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "diff": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "dev": true
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
+      "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
+      "dev": true,
+      "requires": {
+        "es-to-primitive": "^1.2.0",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "is-callable": "^1.1.4",
+        "is-regex": "^1.0.4",
+        "object-keys": "^1.0.12"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+      "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "get-func-name": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+      "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.0.4",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.1.15",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
+      "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==",
+      "dev": true
+    },
+    "growl": {
+      "version": "1.10.5",
+      "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+      "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+      "dev": true
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "has-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+      "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+      "dev": true
+    },
+    "he": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+      "dev": true
+    },
+    "hosted-git-info": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
+      "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-callable": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+      "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+      "dev": true
+    },
+    "is-date-object": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+      "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+      "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.1"
+      }
+    },
+    "is-symbol": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+      "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.0"
+      }
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "jsonify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+      "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+      "dev": true
+    },
+    "load-json-file": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+      "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^4.0.0",
+        "pify": "^3.0.0",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+      "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+      "dev": true
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "dev": true,
+      "requires": {
+        "minimist": "0.0.8"
+      }
+    },
+    "mocha": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
+      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
+      "dev": true,
+      "requires": {
+        "browser-stdout": "1.3.1",
+        "commander": "2.15.1",
+        "debug": "3.1.0",
+        "diff": "3.5.0",
+        "escape-string-regexp": "1.0.5",
+        "glob": "7.1.2",
+        "growl": "1.10.5",
+        "he": "1.1.1",
+        "minimatch": "3.0.4",
+        "mkdirp": "0.5.1",
+        "supports-color": "5.4.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "dev": true
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      }
+    },
+    "npm-run-all": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
+      "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "chalk": "^2.4.1",
+        "cross-spawn": "^6.0.5",
+        "memorystream": "^0.3.1",
+        "minimatch": "^3.0.4",
+        "pidtree": "^0.3.0",
+        "read-pkg": "^3.0.0",
+        "shell-quote": "^1.6.1",
+        "string.prototype.padend": "^3.0.0"
+      }
+    },
+    "object-keys": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz",
+      "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==",
+      "dev": true
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "parse-json": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.3.1",
+        "json-parse-better-errors": "^1.0.1"
+      }
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-type": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+      "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+      "dev": true,
+      "requires": {
+        "pify": "^3.0.0"
+      }
+    },
+    "pathval": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+      "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+      "dev": true
+    },
+    "pidtree": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz",
+      "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==",
+      "dev": true
+    },
+    "pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+      "dev": true
+    },
+    "read-pkg": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+      "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^4.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^3.0.0"
+      }
+    },
+    "resolve": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+      "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "rollup": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.3.0.tgz",
+      "integrity": "sha512-QiT0wFmu0IzkGgT5LvzRK5hezHcJW8T9MQdvdC+FylrNpsprpz0gTOpcyY9iM6Sda1fD1SatwNutCxcQd3Y/Lg==",
+      "dev": true,
+      "requires": {
+        "@types/estree": "0.0.39",
+        "@types/node": "^11.9.5",
+        "acorn": "^6.1.0"
+      }
+    },
+    "semver": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+      "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+      "dev": true
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "shell-quote": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
+      "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
+      "dev": true,
+      "requires": {
+        "array-filter": "~0.0.0",
+        "array-map": "~0.0.0",
+        "array-reduce": "~0.0.0",
+        "jsonify": "~0.0.0"
+      }
+    },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz",
+      "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==",
+      "dev": true
+    },
+    "string.prototype.padend": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
+      "integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "es-abstract": "^1.4.3",
+        "function-bind": "^1.0.2"
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+      "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "type-detect": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+      "dev": true
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    }
+  }
+}
diff --git a/node_modules/dialog-polyfill/package.json b/node_modules/dialog-polyfill/package.json
new file mode 100644
index 0000000..6b3bcd0
--- /dev/null
+++ b/node_modules/dialog-polyfill/package.json
@@ -0,0 +1,59 @@
+{
+  "_from": "dialog-polyfill",
+  "_id": "dialog-polyfill@0.5.0",
+  "_inBundle": false,
+  "_integrity": "sha512-fOj68T8KB6UIsDFmK7zYbmORJMLYkRmtsLM1W6wbCVUWu4Hdcud5bqbvuueTxO84JXtK9HcpCHV9vNwlWUdCIw==",
+  "_location": "/dialog-polyfill",
+  "_phantomChildren": {},
+  "_requested": {
+    "type": "tag",
+    "registry": true,
+    "raw": "dialog-polyfill",
+    "name": "dialog-polyfill",
+    "escapedName": "dialog-polyfill",
+    "rawSpec": "",
+    "saveSpec": null,
+    "fetchSpec": "latest"
+  },
+  "_requiredBy": [
+    "#USER",
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.5.0.tgz",
+  "_shasum": "bf67bc67abaf538e44fff80f2f71ff132befadd7",
+  "_spec": "dialog-polyfill",
+  "_where": "/var/www/html/hores",
+  "author": {
+    "name": "The Chromium Authors"
+  },
+  "bugs": {
+    "url": "https://github.com/GoogleChrome/dialog-polyfill/issues"
+  },
+  "bundleDependencies": false,
+  "deprecated": false,
+  "description": "Polyfill for the dialog element",
+  "devDependencies": {
+    "chai": "^4.2.0",
+    "mocha": "^6.0.2",
+    "npm-run-all": "^4.1.5",
+    "rollup": "^1.3.0"
+  },
+  "homepage": "https://github.com/GoogleChrome/dialog-polyfill",
+  "license": "BSD",
+  "main": "dist/dialog-polyfill.js",
+  "module": "dist/dialog-polyfill.esm.js",
+  "name": "dialog-polyfill",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/GoogleChrome/dialog-polyfill.git"
+  },
+  "scripts": {
+    "build": "npm-run-all -p build:*",
+    "build:css": "cp dialog-polyfill.css dist/dialog-polyfill.css",
+    "build:esm": "rollup index.js --file dist/dialog-polyfill.esm.js --format esm",
+    "build:umd": "rollup index.js --file dist/dialog-polyfill.js --format umd --name dialogPolyfill",
+    "prepublishOnly": "npm run build",
+    "test": "open test.html"
+  },
+  "version": "0.5.0"
+}
diff --git a/node_modules/dialog-polyfill/suite.js b/node_modules/dialog-polyfill/suite.js
new file mode 100644
index 0000000..943f17d
--- /dev/null
+++ b/node_modules/dialog-polyfill/suite.js
@@ -0,0 +1,790 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+void function() {
+
+  /**
+   * Asserts that the displayed dialog is in the center of the screen.
+   *
+   * @param {HTMLDialogElement?} opt_dialog to check, or test default
+   */
+  function checkDialogCenter(opt_dialog) {
+    var d = opt_dialog || dialog;
+    var expectedTop = (window.innerHeight - d.offsetHeight) / 2;
+    var expectedLeft = (window.innerWidth - d.offsetWidth) / 2;
+    var rect = d.getBoundingClientRect();
+    assert.closeTo(rect.top, expectedTop, 1, 'top should be nearby');
+    assert.closeTo(rect.left, expectedLeft, 1, 'left should be nearby');
+  }
+
+  /**
+   * Creates a fake KeyboardEvent.
+   *
+   * @param {number} keyCode to press
+   * @param {string?} opt_type to use, default keydown
+   * @return {!Event} event
+   */
+  function createKeyboardEvent(keyCode, opt_type) {
+    var ev = document.createEvent('Events');
+    ev.initEvent(opt_type || 'keydown', true, true);
+    ev.keyCode = keyCode;
+    ev.which = keyCode;
+    return ev;
+  }
+
+  /**
+   * Cleans up any passed DOM elements.
+   *
+   * @param {!Element} el to clean up
+   * @return {!Element} the same element, for chaining
+   */
+  var cleanup = (function() {
+    var e = [];
+    teardown(function() {
+      e.forEach(function(el) {
+        try {
+          el.close();  // try to close dialogs
+        } catch (e) {}
+        el.parentElement && el.parentElement.removeChild(el);
+      });
+      e = [];
+    });
+
+    return function(el) {
+      e.push(el);
+      return el;
+    };
+  })();
+
+  /**
+   * Creates a dialog for testing that will be cleaned up later.
+   *
+   * @param {string?} opt_content to be used as innerHTML
+   */
+  function createDialog(opt_content) {
+    var dialog = document.createElement('dialog');
+    dialog.innerHTML = opt_content || 'Dialog #' + (cleanup.length);
+    document.body.appendChild(dialog);
+    if (window.location.search == '?force') {
+      dialogPolyfill.forceRegisterDialog(dialog);
+    } else {
+      dialogPolyfill.registerDialog(dialog);
+    }
+    return cleanup(dialog);
+  }
+
+  var dialog;  // global dialog for all tests
+  setup(function() {
+    dialog = createDialog('Default Dialog');
+  });
+
+  suite('basic', function() {
+    test('show and close', function() {
+      assert.isFalse(dialog.hasAttribute('open'));
+      dialog.show();
+      assert.isTrue(dialog.hasAttribute('open'));
+      assert.isTrue(dialog.open);
+
+      var returnValue = 1234;
+      dialog.close(returnValue);
+      assert.isFalse(dialog.hasAttribute('open'));
+      assert.equal(dialog.returnValue, returnValue);
+
+      dialog.show();
+      dialog.close();
+      assert.isFalse(dialog.open);
+      assert.equal(dialog.returnValue, returnValue);
+    });
+    test('open property', function() {
+      assert.isFalse(dialog.hasAttribute('open'));
+      dialog.show();
+      assert.isTrue(dialog.hasAttribute('open'));
+      assert.isTrue(dialog.open);
+
+      dialog.open = false;
+      assert.isFalse(dialog.open);
+      assert.isFalse(dialog.hasAttribute('open'),
+          'open property should clear attribute');
+      assert.throws(dialog.close);
+
+      var overlay = document.querySelector('._dialog_overlay');
+      assert.isNull(overlay);
+    });
+    test('show/showModal interaction', function() {
+      assert.isFalse(dialog.hasAttribute('open'));
+      dialog.show();
+
+      // If the native dialog is being tested, show/showModal are not already
+      // bound, so wrap them in helper methods for throws/doesNotThrow.
+      var show = function() { dialog.show(); };
+      var showModal = function() { dialog.showModal(); };
+
+      assert.doesNotThrow(show);
+      assert.throws(showModal);
+
+      dialog.open = false;
+      assert.doesNotThrow(showModal);
+      assert.doesNotThrow(show);  // show after showModal does nothing
+      assert.throws(showModal);
+      // TODO: check dialog is still modal
+
+      assert.isTrue(dialog.open);
+    });
+    test('setAttribute reflects property', function() {
+      dialog.setAttribute('open', '');
+      assert.isTrue(dialog.open, 'attribute opens dialog');
+    });
+    test('changing open to dummy value is ignored', function() {
+      dialog.showModal();
+
+      dialog.setAttribute('open', 'dummy, ignored');
+      assert.isTrue(dialog.open, 'dialog open with dummy open value');
+
+      var overlay = document.querySelector('._dialog_overlay');
+      assert(overlay, 'dialog is still modal');
+    });
+    test('show/showModal outside document', function() {
+      dialog.open = false;
+      dialog.parentNode.removeChild(dialog);
+
+      assert.throws(function() { dialog.showModal(); });
+
+      assert.doesNotThrow(function() { dialog.show(); });
+      assert.isTrue(dialog.open, 'can open non-modal outside document');
+      assert.isFalse(document.body.contains(dialog));
+    });
+    test('has a11y property', function() {
+      assert.equal(dialog.getAttribute('role'), 'dialog', 'role should be dialog');
+    });
+  });
+
+  suite('DOM', function() {
+    setup(function(done) {
+      // DOM tests wait for modal to settle, so MutationOberver doesn't coalesce attr changes
+      dialog.showModal();
+      window.setTimeout(done, 0);
+    });
+    test('DOM direct removal', function(done) {
+      assert.isTrue(dialog.open);
+      assert.isNotNull(document.querySelector('.backdrop'));
+
+      var parentNode = dialog.parentNode;
+      parentNode.removeChild(dialog);
+
+      // DOMNodeRemoved defers its task a frame (since it occurs before removal, not after). This
+      // doesn't effect MutationObserver, just delays the test a frame.
+      window.setTimeout(function() {
+        assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
+
+        assert.isTrue(dialog.open, 'removed dialog should still be open');
+        parentNode.appendChild(dialog);
+
+        assert.isTrue(dialog.open, 're-added dialog should still be open');
+        assert.isNull(document.querySelector('.backdrop'), 're-add dialog should not be modal');
+
+        done();
+      }, 0);
+    });
+    test('DOM removal inside other element', function(done) {
+      var div = cleanup(document.createElement('div'));
+      document.body.appendChild(div);
+      div.appendChild(dialog);
+
+      document.body.removeChild(div);
+
+      window.setTimeout(function() {
+        assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
+        assert.isTrue(dialog.open, 'removed dialog should still be open');
+        done();
+      }, 0);
+    });
+    test('DOM instant remove/add', function(done) {
+      var div = cleanup(document.createElement('div'));
+      document.body.appendChild(div);
+      dialog.parentNode.removeChild(dialog);
+      div.appendChild(dialog);
+
+      window.setTimeout(function() {
+        assert.isNull(document.querySelector('.backdrop'), 'backdrop should disappear');
+        assert.isTrue(dialog.open);
+        done();
+      }, 0);
+    });
+  });
+
+  suite('position', function() {
+    test('non-modal is not centered', function() {
+      var el = cleanup(document.createElement('div'));
+      dialog.parentNode.insertBefore(el, dialog);
+      var testRect = el.getBoundingClientRect();
+
+      dialog.show();
+      var rect = dialog.getBoundingClientRect();
+
+      assert.equal(rect.top, testRect.top, 'dialog should not be centered');
+    });
+    test('default modal centering', function() {
+      dialog.showModal();
+      checkDialogCenter();
+      assert.ok(dialog.style.top, 'expected top to be set');
+      dialog.close();
+      assert.notOk(dialog.style.top, 'expected top to be cleared');
+    });
+    test('modal respects static position', function() {
+      dialog.style.top = '10px';
+      dialog.showModal();
+
+      var rect = dialog.getBoundingClientRect();
+      assert.equal(rect.top, 10);
+    });
+    test('modal recentering', function() {
+      var pX = document.body.scrollLeft;
+      var pY = document.body.scrollTop;
+      var big = cleanup(document.createElement('div'));
+      big.style.height = '200vh';  // 2x view height
+      document.body.appendChild(big);
+
+      try {
+        var scrollValue = 200;  // don't use incredibly large values
+        dialog.showModal();
+        dialog.close();
+
+        window.scrollTo(0, scrollValue);
+        dialog.showModal();
+        checkDialogCenter();  // must be centered, even after scroll
+        var rectAtScroll = dialog.getBoundingClientRect();
+
+        // after scroll, we aren't recentered, check offset
+        window.scrollTo(0, 0);
+        var rect = dialog.getBoundingClientRect();
+        assert.closeTo(rectAtScroll.top + scrollValue, rect.top, 1);
+      } finally {
+        window.scrollTo(pX, pY);
+      }
+    });
+    test('clamped to top of page', function() {
+      var big = cleanup(document.createElement('div'));
+      big.style.height = '200vh';  // 2x view height
+      document.body.appendChild(big);
+      document.documentElement.scrollTop = document.documentElement.scrollHeight / 2;
+
+      dialog.style.height = document.documentElement.scrollHeight + 200 + 'px';
+      dialog.showModal();
+
+      var visibleRect = dialog.getBoundingClientRect();
+      assert.equal(visibleRect.top, 0, 'large dialog should be visible at top of page');
+
+      var style = window.getComputedStyle(dialog);
+      assert.equal(style.top, document.documentElement.scrollTop + 'px',
+          'large dialog should be absolutely positioned at scroll top');
+    });
+  });
+
+  suite('backdrop', function() {
+    test('backdrop div on modal', function() {
+      dialog.showModal();
+      var foundBackdrop = document.querySelector('.backdrop');
+      assert.isNotNull(foundBackdrop);
+
+      var sibling = dialog.nextElementSibling;
+      assert.strictEqual(foundBackdrop, sibling);
+    });
+    test('no backdrop on non-modal', function() {
+      dialog.show();
+      assert.isNull(document.querySelector('.backdrop'));
+      dialog.close();
+    });
+    test('backdrop click appears as dialog', function() {
+      dialog.showModal();
+      var backdrop = dialog.nextElementSibling;
+
+      var clickFired = 0;
+      var helper = function(ev) {
+        assert.equal(ev.target, dialog);
+        ++clickFired;
+      };
+
+      dialog.addEventListener('click', helper)
+      backdrop.click();
+      assert.equal(clickFired, 1);
+    });
+    test('backdrop click focuses dialog', function() {
+      dialog.showModal();
+      dialog.tabIndex = 0;
+
+      var input = document.createElement('input');
+      input.type = 'text';
+      dialog.appendChild(input);
+
+      // TODO: It would be nice to check `input` instead here, but there's no more reliable ways
+      // to emulate a browser tab event (Firefox, Chrome etc have made it a security violation).
+
+      var backdrop = dialog.nextElementSibling;
+      backdrop.click();
+      assert.equal(document.activeElement, dialog);
+    });
+  });
+
+  suite('form focus', function() {
+    test('non-modal inside modal is focusable', function() {
+      var sub = createDialog();
+      dialog.appendChild(sub);
+
+      var input = document.createElement('input');
+      input.type = 'text';
+      sub.appendChild(input);
+
+      dialog.showModal();
+      sub.show();
+
+      input.focus();
+      assert.equal(input, document.activeElement);
+    });
+    test('clear focus when nothing focusable in modal', function() {
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      document.body.appendChild(input);
+      input.focus();
+
+      var previous = document.activeElement;
+      dialog.showModal();
+      assert.notEqual(previous, document.activeElement);
+    });
+    test('default focus on modal', function() {
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      dialog.appendChild(input);
+
+      var anotherInput = cleanup(document.createElement('input'));
+      anotherInput.type = 'text';
+      dialog.appendChild(anotherInput);
+
+      dialog.showModal();
+      assert.equal(document.activeElement, input);
+    });
+    test('default focus on non-modal', function() {
+      var div = cleanup(document.createElement('div'));
+      div.tabIndex = 4;
+      dialog.appendChild(div);
+
+      dialog.show();
+      assert.equal(document.activeElement, div);
+    });
+    test('autofocus element chosen', function() {
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      dialog.appendChild(input);
+
+      var inputAF = cleanup(document.createElement('input'));
+      inputAF.type = 'text';
+      inputAF.autofocus = true;
+      dialog.appendChild(inputAF);
+
+      dialog.showModal();
+      assert.equal(document.activeElement, inputAF);
+    });
+    test('child modal dialog', function() {
+      dialog.showModal();
+
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      dialog.appendChild(input);
+      input.focus();
+      assert.equal(document.activeElement, input);
+
+      // NOTE: This is a single sub-test, but all the above tests could be run
+      // again in a sub-context (i.e., dialog within dialog).
+      var child = createDialog();
+      child.showModal();
+      assert.notEqual(document.activeElement, input,
+          'additional modal dialog should clear parent focus');
+
+      child.close();
+      assert.notEqual(document.activeElement, input,
+          'parent focus should not be restored');
+    });
+    test('don\'t scroll anything into focus', function() {
+      // https://github.com/GoogleChrome/dialog-polyfill/issues/119
+
+      var div = cleanup(document.createElement('div'));
+      document.body.appendChild(div);
+
+      var inner = document.createElement('div');
+      inner.style.height = '10000px';
+      div.appendChild(inner);
+
+      div.appendChild(dialog);
+
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      dialog.appendChild(input);
+
+      var prev = document.documentElement.scrollTop;
+      dialog.showModal();
+      assert.equal(document.documentElement.scrollTop, prev);
+    });
+  });
+
+  suite('top layer / inert', function() {
+    test('background focus allowed on non-modal', function() {
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      document.body.appendChild(input);
+      input.focus();
+
+      dialog.show();
+      assert.notEqual(document.activeElement, input,
+        'non-modal dialog should clear focus, even with no dialog content');
+
+      document.body.focus();
+      input.focus();
+      assert.equal(document.activeElement, input,
+          'non-modal should allow background focus');
+    });
+    test('modal disallows background focus', function() {
+      var input = cleanup(document.createElement('input'));
+      input.type = 'text';
+      document.body.appendChild(input);
+
+      dialog.showModal();
+      input.focus();
+
+      if (!document.hasFocus()) {
+        // Browsers won't trigger a focus event if they're not in the
+        // foreground, so we can't intercept it. However, they'll fire one when
+        // restored, before a user can get to any incorrectly focused element.
+        console.warn('background focus test requires document focus');
+        document.documentElement.focus();
+      }
+      assert.notEqual(document.activeElement, input,
+          'modal should disallow background focus');
+    });
+    test('overlay is a sibling of topmost dialog', function() {
+      var stacking = cleanup(document.createElement('div'));
+      stacking.style.opacity = 0.8;  // creates stacking context
+      document.body.appendChild(stacking);
+      stacking.appendChild(dialog);
+      dialog.showModal();
+
+      var overlay = document.querySelector('._dialog_overlay');
+      assert.isNotNull(overlay);
+      assert.equal(overlay.parentNode, dialog.parentNode);
+    });
+    test('overlay is between topmost and remaining dialogs', function() {
+      dialog.showModal();
+
+      var other = cleanup(createDialog());
+      document.body.appendChild(other);
+      other.showModal();
+
+      var overlay = document.querySelector('._dialog_overlay');
+      assert.isNotNull(overlay);
+      assert.equal(overlay.parentNode, other.parentNode);
+
+      assert.isAbove(+other.style.zIndex, +overlay.style.zIndex, 'top-most dialog above overlay');
+      assert.isAbove(+overlay.style.zIndex, +dialog.style.zIndex, 'overlay above other dialogs');
+    });
+  });
+
+  suite('events', function() {
+    test('close event', function() {
+      var closeFired = 0;
+      dialog.addEventListener('close', function() {
+        ++closeFired;
+      });
+
+      dialog.show();
+      assert.equal(closeFired, 0);
+
+      dialog.close();
+      assert.equal(closeFired, 1);
+
+      assert.throws(dialog.close);  // can't close already closed dialog
+      assert.equal(closeFired, 1);
+
+      dialog.showModal();
+      dialog.close();
+      assert.equal(closeFired, 2);
+    });
+    test('cancel event', function() {
+      dialog.showModal();
+      dialog.dispatchEvent(createKeyboardEvent(27));
+      assert.isFalse(dialog.open, 'esc should close modal');
+
+      var cancelFired = 0;
+      dialog.addEventListener('cancel', function() {
+        ++cancelFired;
+      });
+      dialog.showModal();
+      dialog.dispatchEvent(createKeyboardEvent(27));
+      assert.equal(cancelFired, 1, 'expected cancel to be fired');
+      assert.isFalse(dialog.open), 'esc should close modal again';
+
+      // Sanity-check that non-modals aren't effected.
+      dialog.show();
+      dialog.dispatchEvent(createKeyboardEvent(27));
+      assert.isTrue(dialog.open, 'esc should only close modal dialog');
+      assert.equal(cancelFired, 1);
+    });
+    test('overlay click is prevented', function() {
+      dialog.showModal();
+
+      var overlay = document.querySelector('._dialog_overlay');
+      assert.isNotNull(overlay);
+
+      var helper = function(ev) {
+        throw Error('body should not be clicked');
+      };
+      try {
+        document.body.addEventListener('click', helper);
+        overlay.click();
+      } finally {
+        document.body.removeEventListener('click', helper);
+      }
+    });
+  });
+
+  suite('form', function() {
+    test('method attribute is translated to property', function() {
+      var form = document.createElement('form');
+      form.method = 'dialog';
+      assert.equal(form.method, 'dialog');
+
+      form.method = 'PoSt';
+      assert.equal(form.method, 'post');
+      assert.equal(form.getAttribute('method'), 'PoSt');
+    });
+    test('dialog method input', function() {
+      var value = 'ExpectedValue' + Math.random();
+
+      var form = document.createElement('form');
+      try {
+        form.method = 'dialog';
+      } catch (e) {
+        // Setting the method directly throws an exception in <=IE9.
+        form.setAttribute('method', 'dialog');
+      }
+      dialog.appendChild(form);
+
+      var input = document.createElement('input');
+      input.type = 'submit';
+      input.value = value;
+      form.appendChild(input);
+
+      var closeCount = 0;
+      dialog.addEventListener('close', function() {
+        ++closeCount;
+      });
+
+      dialog.show();
+      input.focus();  // emulate user focus action
+      input.click();
+
+      assert.isFalse(dialog.open);
+      assert.equal(dialog.returnValue, value);
+      assert.equal(closeCount, 1);
+    });
+    test('dialog with button preventDefault does not trigger submit', function() {
+      var form = document.createElement('form');
+      form.setAttribute('method', 'dialog');
+      dialog.appendChild(form);
+
+      var button = document.createElement('button');
+      button.value = 'does not matter';
+      form.appendChild(button);
+      button.addEventListener('click', function(ev) {
+        ev.preventDefault();
+      });
+
+      dialog.showModal();
+      button.click();
+
+      assert.isTrue(dialog.open, 'dialog should remain open');
+      assert.equal(dialog.returnValue, '');
+    });
+    test('dialog programmatic submit does not change returnValue', function() {
+      var form = document.createElement('form');
+      form.setAttribute('method', 'dialog');
+
+      dialog.returnValue = 'manually set';  // set before appending
+      dialog.appendChild(form);
+
+      dialog.showModal();
+      form.submit();
+      assert.isFalse(dialog.open);
+
+      assert.equal(dialog.returnValue, 'manually set', 'returnValue should not change');
+    });
+    test('dialog method button', function() {
+      var value = 'ExpectedValue' + Math.random();
+
+      var form = document.createElement('form');
+      form.setAttribute('method', 'dialog');
+      dialog.appendChild(form);
+
+      var button = document.createElement('button');
+      button.value = value;
+      form.appendChild(button);
+
+      dialog.showModal();
+      button.focus();  // emulate user focus action
+      button.click();
+
+      assert.isFalse(dialog.open);
+      assert.equal(dialog.returnValue, value);
+
+      // Clear button value, confirm textContent is not used as value.
+      button.value = 'blah blah';
+      button.removeAttribute('value');
+      button.textContent = value;
+      dialog.show();
+      button.focus();  // emulate user focus action
+      button.click();
+
+      assert.equal(dialog.returnValue, button.value,
+          'don\'t take button textContent as value');
+    });
+    test('boring form inside dialog', function() {
+      var form = document.createElement('form');
+      dialog.appendChild(form);  // don't specify method
+      form.addEventListener('submit', function(ev) {
+        ev.preventDefault();
+      });
+
+      var button = document.createElement('button');
+      button.value = 'Moot';
+      form.appendChild(button);
+
+      dialog.showModal();
+      button.focus();  // emulate user focus action
+      button.click();
+
+      assert.isTrue(dialog.open, 'non-dialog form should not close dialog')
+      assert(!dialog.returnValue);
+    });
+    test('type="image" submitter', function() {
+      var form = document.createElement('form');
+      form.setAttribute('method', 'dialog');
+      dialog.appendChild(form);
+      dialog.show();
+
+      var image = document.createElement('input');
+      image.type = 'image';
+      image.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+      image.setAttribute('value', 'image should not accept value');
+      form.appendChild(image);
+      image.click();
+
+      assert.notEqual(image.getAttribute('value'), dialog.returnValue);
+      assert.equal(dialog.returnValue, '0,0');
+    });
+    test('form submitter across dialogs', function() {
+      var form1 = document.createElement('form');
+      form1.setAttribute('method', 'dialog');
+      dialog.appendChild(form1);
+
+      var button1 = document.createElement('button');
+      button1.value = 'from form1: first value';
+      form1.appendChild(button1);
+      dialog.showModal();
+
+      var dialog2 = createDialog();
+      dialog2.returnValue = 'dialog2 default close value';
+      var form2 = document.createElement('form');
+      form2.setAttribute('method', 'dialog');
+      dialog2.appendChild(form2);
+      dialog2.showModal();
+
+      button1.click();
+      assert.isFalse(dialog.open);
+
+      // nb. this never fires 'submit' so the .returnValue can't be wrong: is there another way
+      // to submit a form that doesn't involve a click (enter implicitly 'clicks') or submit?
+      form2.submit();
+      assert.isFalse(dialog2.open);
+
+      assert.equal(dialog2.returnValue, 'dialog2 default close value',
+          'second dialog shouldn\'t reuse formSubmitter');
+    });
+  });
+
+  suite('order', function() {
+    test('non-modal unchanged', function() {
+      var one = createDialog();
+      var two = createDialog();
+
+      one.style.zIndex = 100;
+      two.style.zIndex = 200;
+      one.show();
+      two.show();
+
+      assert.equal(window.getComputedStyle(one).zIndex, 100);
+      assert.equal(window.getComputedStyle(two).zIndex, 200);
+
+      two.close();
+      assert.equal(window.getComputedStyle(two).zIndex, 200);
+    });
+    test('modal stacking order', function() {
+      dialog.showModal();
+
+      // Create incorrectly-named dialogs: front has a lower z-index, and back
+      // has a higher z-index.
+      var front = createDialog();
+      var back = createDialog();
+      front.style.zIndex = 100;
+      back.style.zIndex = 200;
+
+      // Show back first, then front. Thus we expect back to be behind front.
+      back.showModal();
+      front.showModal();
+
+      var zf = +window.getComputedStyle(front).zIndex;
+      var zb = +window.getComputedStyle(back).zIndex;
+      assert.isAbove(zf, zb, 'showModal order dictates z-index');
+
+      var backBackdrop = back.nextElementSibling;
+      var zbb = +window.getComputedStyle(backBackdrop).zIndex;
+      assert.equal(backBackdrop.className, 'backdrop');
+      assert.isBelow(zbb, zb, 'backdrop below dialog');
+
+      var frontBackdrop = front.nextElementSibling;
+      var zfb = +window.getComputedStyle(frontBackdrop).zIndex
+      assert.equal(frontBackdrop.className, 'backdrop');
+      assert.isBelow(zfb, zf,' backdrop below dialog');
+
+      assert.isAbove(zfb, zb, 'front backdrop is above back dialog');
+
+      front.close();
+      assert.notOk(front.style.zIndex, 'modal close should clear zindex');
+    });
+  });
+
+  suite('press tab key', function() {
+    test('tab key', function() {
+      var dialog = createDialog();
+      dialog.showModal();
+
+      document.documentElement.dispatchEvent(createKeyboardEvent(9));
+
+      var ev = document.createEvent('Events');
+      ev.initEvent('focus', true, true);
+      document.documentElement.dispatchEvent(ev);
+
+      dialog.close();
+    });
+  });
+}();
diff --git a/node_modules/dialog-polyfill/test.html b/node_modules/dialog-polyfill/test.html
new file mode 100644
index 0000000..03e9b0b
--- /dev/null
+++ b/node_modules/dialog-polyfill/test.html
@@ -0,0 +1,49 @@
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE html>
+<meta charset="UTF-8" />
+<link rel="stylesheet" href="node_modules/mocha/mocha.css" />
+<script src="node_modules/mocha/mocha.js"></script>
+<script src="node_modules/chai/chai.js"></script>
+<link rel="stylesheet" type="text/css" href="dist/dialog-polyfill.css" />
+<script src="dist/dialog-polyfill.js"></script>
+<script>
+
+var assert = chai.assert;
+mocha.setup({ ui: 'tdd' });
+
+(function() {
+  var pageError = null;
+
+  window.addEventListener('error', function(event) {
+    pageError = event.filename + ':' + event.lineno + ' ' + event.message;
+  });
+
+  window.addEventListener('load', function() {
+    if (pageError) {
+      suite('page-script-errors', function() {
+        test('no script errors on page', function() {
+          assert.fail(null, null, pageError);
+        });
+      });
+    }
+    mocha.run();
+  });
+})();
+
+</script>
+<div id="mocha"></div>
+<script src="suite.js"></script>
diff --git a/node_modules/dialog-polyfill/tests/actionbar.html b/node_modules/dialog-polyfill/tests/actionbar.html
new file mode 100644
index 0000000..3af6c75
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/actionbar.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="initial-scale=1" />
+<meta charset="UTF-8" />
+<head>
+  <script src="../dist/dialog-polyfill.js"></script>
+  <link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+  <style>
+
+  dialog.actionbar {
+    position: fixed;
+    border: 0;
+    box-sizing: border-box;
+    width: 100%;
+    bottom: 0;
+    background: #fff;
+    transform: translate(0, 100%);
+    transition: transform 0.25s ease-in-out;
+    margin: 0;
+    padding: 0;
+    box-shadow: 0 0 4px rgba(0, 0, 0, 0.25);
+  }
+  dialog.actionbar.appear {
+    transform: translate(0);
+  }
+
+  dialog.actionbar .holder {
+    display: flex;
+    justify-content: space-around;
+    flex-flow: wrap;
+  }
+  dialog.actionbar .holder button {
+    flex-grow: 1;
+    min-width: 200px;
+    margin: 8px;
+    border: 0;
+    font-family: Roboto, Arial, Sans-Serif;
+    font-size: 23px;
+    line-height: 33px;
+    font-weight: 400;
+    color: #666;
+    border-radius: 2px;
+    background: #ccc;
+    display: block;
+    box-sizing: border-box;
+    border: 2px solid transparent;
+  }
+  dialog.actionbar .holder button:focus {
+    outline: 0;
+    border-color: rgba(0, 0, 0, 0.125);
+  }
+  dialog.actionbar .holder button.cancel {
+    background: #fcc;
+  }
+
+  dialog.actionbar::backdrop {
+    background: rgba(0, 0, 0, 0.25);
+    opacity: 0;
+    transition: opacity 0.25s ease-in-out;
+  }
+  dialog.actionbar + .backdrop {
+    background: rgba(0, 0, 0, 0.25);
+    opacity: 0;
+    transition: opacity 0.25s ease-in-out;
+  }
+  dialog.actionbar.appear::backdrop {
+    opacity: 1;
+  }
+  dialog.actionbar.appear + .backdrop {
+    opacity: 1;
+  }
+
+  </style>
+</head>
+<body>
+
+<p>Builds an action bar from a modal dialog.</p>
+
+<button id="show">Show</button>
+
+<script>
+
+var ActionBar = function() {
+  this.options_ = [];
+  this.cancelButton_ = true;
+  this.dialog_ = null;
+};
+
+ActionBar.prototype.enableCancelButton = function(value) {
+  this.cancelButton_ = vaue;
+};
+
+ActionBar.prototype.addOption = function(name, opt_callback) {
+  this.options_.push({name: name, callback: opt_callback || null});
+};
+
+ActionBar.prototype.show = function(opt_callback) {
+  if (this.dialog_ != null) {
+    throw "Can't show ActionBar, already visible";
+  }
+
+  var dialog = document.createElement('dialog');
+  if (!('showModal' in dialog)) {
+    dialogPolyfill.registerDialog(dialog);
+  }
+  dialog.className = 'actionbar';
+
+  var holder = document.createElement('div');
+  holder.className = 'holder';
+  dialog.appendChild(holder);
+
+  dialog.addEventListener('click', function(ev) {
+    if (ev.target == dialog) {
+      dialog.close();
+    }
+  });
+
+  dialog.addEventListener('close', function() {
+    opt_callback && opt_callback(dialog.returnValue);
+
+    var cloneDialog = dialog.cloneNode(true);
+    if (!('showModal' in cloneDialog)) {
+      dialogPolyfill.registerDialog(cloneDialog);
+    }
+
+    document.body.appendChild(cloneDialog);
+    cloneDialog.showModal();
+    cloneDialog.classList.remove('appear');
+    window.setTimeout(function() {
+      cloneDialog.close();  // TODO: needed until DOM removal is safe
+      cloneDialog.parentNode.removeChild(cloneDialog);
+    }, 250);
+
+    if (dialog.parentNode) {
+      dialog.parentNode.removeChild(dialog);
+    }
+    if (this.dialog_ == dialog) {
+      this.dialog_ = null;
+    }
+  }.bind(this));
+
+  this.options_.forEach(function(option) {
+    var button = document.createElement('button');
+    button.textContent = option.name;
+    button.addEventListener('click', function() {
+      if (option.callback) {
+        window.setTimeout(option.callback, 0);
+      }
+      dialog.close(option.name);
+    });
+    holder.appendChild(button);
+  });
+
+  if (this.cancelButton_) {
+    var cancelButton = document.createElement('button');
+    cancelButton.className = 'cancel';
+    cancelButton.textContent = 'Cancel';
+    cancelButton.addEventListener('click', function() {
+      dialog.close();
+    });
+    holder.appendChild(cancelButton);
+  }
+
+  document.body.appendChild(dialog);
+  dialog.showModal();
+  dialog.classList.add('appear');
+
+  this.dialog_ = dialog;
+};
+
+////// DEMO
+
+show.addEventListener('click', function() {
+  var ab = new ActionBar();
+  ab.addOption('Display');
+  ab.addOption('Options');
+  ab.addOption('Longer Choice');
+  ab.addOption('Alert', function() {
+    alert('you hit Alert');
+  });
+  ab.show(function(choice) {
+    console.info('choice was', choice);
+  });
+});
+
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/backdrop.html b/node_modules/dialog-polyfill/tests/backdrop.html
new file mode 100644
index 0000000..77c6d81
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/backdrop.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+<style>
+dialog {
+  height: 100px;
+  width: 100px;
+}
+
+dialog + .backdrop {
+  background-color: rgba(0,255,0,0.5);
+}
+</style>
+</head>
+<body>
+<p>Test for backdrop. The test passes if you see a green background behind the
+box.</p>
+<div id="console"></div>
+<dialog></dialog>
+<button id="inert-button">Can't click me</button>
+<script>
+function writeToConsole(s) {
+  var console = document.getElementById('console');
+  var span = document.createElement('span');
+  span.textContent = s;
+  console.appendChild(span);
+  console.appendChild(document.createElement('br'));
+}
+
+var dialog = document.querySelector('dialog');
+dialogPolyfill.registerDialog(dialog);
+dialog.showModal();
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/dialog-centering.html b/node_modules/dialog-polyfill/tests/dialog-centering.html
new file mode 100644
index 0000000..0eaff60
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/dialog-centering.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+</head>
+<body>
+<p>Test that dialog is centered in the viewport. The test passes if you see a
+box in the center of the screen.</p>
+<div id="console"></div>
+<dialog>Hello</dialog>
+<script>
+function writeToConsole(s) {
+  var console = document.getElementById('console');
+  var span = document.createElement('span');
+  span.textContent = s;
+  console.appendChild(span);
+  console.appendChild(document.createElement('br'));
+}
+
+function checkCentered(dialog) {
+  var expectedTop = (window.innerHeight - dialog.offsetHeight) / 2;
+  var expectedLeft = (window.innerWidth - dialog.offsetWidth) / 2;
+  var rect = dialog.getBoundingClientRect();
+  if (rect.top == expectedTop && rect.left == expectedLeft) {
+    writeToConsole('SUCCESS');
+  } else {
+    writeToConsole('FAIL: expected dialog top,left to be ' +
+        expectedTop + ',' + expectedLeft + ' and was actually ' +
+        rect.top + ',' + rect.left);
+  }
+}
+
+var dialog = document.querySelector('dialog');
+dialogPolyfill.registerDialog(dialog);
+dialog.show();
+checkCentered(dialog);
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/dialog-recentering.html b/node_modules/dialog-polyfill/tests/dialog-recentering.html
new file mode 100644
index 0000000..5d09f21
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/dialog-recentering.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+<style>
+body {
+  height: 10000px;
+}
+dialog {
+  width: 100px;
+}
+#console {
+  position: fixed;
+}
+#ruler {
+  position: absolute;
+  left: 0;
+  width: 100%;
+}
+</style>
+</head>
+<body>
+<p>Test that dialog is recentered if reopened. The test passes if you see a
+box in the center of the screen.</p>
+<div id="ruler"></div>
+<div id="console"></div>
+<dialog></dialog>
+<script>
+function writeToConsole(s) {
+  var console = document.getElementById('console');
+  var span = document.createElement('span');
+  span.textContent = s;
+  console.appendChild(span);
+  console.appendChild(document.createElement('br'));
+}
+
+function windowWidthMinusScrollbar() {
+  return document.getElementById('ruler').offsetWidth;
+}
+
+function checkCentered(dialog) {
+  var expectedTop = (window.innerHeight - dialog.offsetHeight) / 2;
+  var expectedLeft = (windowWidthMinusScrollbar() - dialog.offsetWidth) / 2;
+  var rect = dialog.getBoundingClientRect();
+  if (rect.top == expectedTop && rect.left == expectedLeft) {
+    writeToConsole('SUCCESS');
+  } else {
+    writeToConsole('FAIL: expected dialog top,left to be ' +
+        expectedTop + ',' + expectedLeft + ' and was actually ' +
+        rect.top + ',' + rect.left);
+  }
+}
+
+var dialog = document.querySelector('dialog');
+dialogPolyfill.registerDialog(dialog);
+dialog.show();
+dialog.close();
+window.scrollTo(0, 500);
+dialog.show();
+checkCentered(dialog);
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/fancy-modal-dialog.html b/node_modules/dialog-polyfill/tests/fancy-modal-dialog.html
new file mode 100644
index 0000000..3e10740
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/fancy-modal-dialog.html
@@ -0,0 +1,270 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+<style>
+#close-button {
+    position: absolute;
+    top: 7px;
+    right: 7px;
+    height: 14px;
+    width: 14px;
+    margin: 0;
+    background-image: url('resources/close_dialog.png');
+}
+
+#close-button:hover {
+    background-image: url('resources/close_dialog_hover.png');
+}
+
+dialog {
+/*    width: 50%;
+    border-radius: 3px;
+    opacity: 0;
+    background: white;
+    box-shadow: 0 4px 23px 5px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0,0,0,0.15);
+    color: #333;
+    min-width: 400px;
+    padding: 0;
+    z-index: 100;
+    border: 0;
+    padding: 15px;
+*/}
+
+dialog + .backdrop {
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+}
+
+dialog.no-backdrop + .backdrop {
+    display: none;
+}
+
+.dialog-setting {
+    margin: 30px;
+}
+
+/* keyframes used to pulse the overlay */
+@-webkit-keyframes pulse {
+ 0% {
+   -webkit-transform: scale(1);
+ }
+ 40% {
+   -webkit-transform: scale(1.05);
+  }
+ 60% {
+   -webkit-transform: scale(1.05);
+  }
+ 100% {
+   -webkit-transform: scale(1);
+ }
+}
+
+.pulse {
+  -webkit-animation-duration: 180ms;
+  -webkit-animation-iteration-count: 1;
+  -webkit-animation-name: pulse;
+  -webkit-animation-timing-function: ease-in-out;
+}
+#messages {
+    background-color: red;
+}
+body {
+   font-family: sans-serif;
+}
+
+#open-button {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  background-color: lightgray;
+  border: 1px solid;
+  margin: 10px;
+  padding: 15px;
+}
+
+#open-button:active {
+  background-color: lime;
+  margin: 9px;
+}
+
+#backdrop {
+    display: none;
+    position: fixed;
+    top:0;
+    right:0;
+    bottom:0;
+    left:0;
+    background: rgba(0,0,0,0.5);
+    z-index: 10;
+}
+.post {
+    margin: 10px;
+    padding: 5px;
+    border: 2px solid;
+}
+
+.post-buttons {
+    margin: 5px;
+    text-align: right;
+}
+.post-button:hover {
+    color: green;
+    font-weight: bold;
+}
+</style>
+<body>
+<div id="messages"></div>
+<dialog id="dialog" class="_dialog_fixed">
+    <h3>Reshare</h3>
+    <input type="text" style="width: 75%" value="I am resharing this."><br>
+    <div class="dialog-setting">
+        <input id="click-outside-to-close" type="checkbox">
+        <label for="click-outside-to-close">Close dialog upon clicking outside</label>
+    </div>
+    <div class="dialog-setting">
+        <input id="enable-backdrop" type="checkbox" checked>
+        <label for="enable-backdrop">Enable ::backdrop</label>
+    </div>
+    <div id="close-button"></div>
+</dialog>
+<div id="post-container"></div>
+<script>
+var dialog = document.getElementById('dialog');
+dialogPolyfill.registerDialog(dialog);
+
+function createPostButton(container, text) {
+   var link = document.createElement('a');
+   link.href = 'javascript:void(0)';
+   link.textContent = text;
+   link.className = 'post-button';
+   container.appendChild(link);
+   var span = document.createElement('span');
+   span.textContent = ' ';
+   container.appendChild(span);
+   return link;
+}
+
+SampleText = 'From this spot I rise not, valiant knight, until your ' +
+             'courtesy grants me the boon I seek, one that will redound ' +
+             'to your praise and the benefit of the human race.';
+
+function createPost(container) {
+    var post = document.createElement('div');
+    post.className = 'post';
+    var postContent = document.createElement('div');
+    postContent.className = 'post-content';
+    postContent.textContent = SampleText;
+    post.appendChild(postContent);
+    var postButtons = document.createElement('div');
+    postButtons.className = 'post-buttons';
+    post.appendChild(postButtons);
+    var reshare = createPostButton(postButtons, 'Reshare');
+    reshare.addEventListener('click', openDialog);
+    var reply = createPostButton(postButtons, 'Reply');
+    reply.addEventListener('click', openDialog);
+    createPostButton(postButtons, 'Enjoyed this post');
+
+    container.appendChild(post);
+    return post;
+}
+
+function initPosts() {
+    var container = document.getElementById('post-container');
+    for (var i = 0; i < 25; ++i) {
+       var post = createPost(container);
+    }
+}
+
+function shouldCloseDialogOnClickOutside() {
+    return document.getElementById('click-outside-to-close').checked;
+}
+
+function closeDialog() {
+    if (dialog.open)
+        dialog.close();
+}
+
+function computeCenteredTop(dialog) {
+    dialog.style.transition = '';
+    dialog.showModal();
+    var computedTopPx = window.getComputedStyle(dialog).top;
+    var computedTop = parseInt(computedTopPx.substring(0, computedTopPx.length - 2), 10);
+    dialog.close();
+    return computedTop;
+}
+
+function openDialog() {
+    dialog.style.opacity = 0;
+    dialog.style.transition = 'all 250ms ease';
+
+    dialog.showModal();
+
+    dialog.style.opacity = 1;
+}
+
+function pulseDialog() {
+  if (!dialog.style.webkitAnimation) {
+    return;
+  }
+  // classList isn't supported in IE8, but it's safe to use here as this only
+  // runs inside WebKit/Chrome anyway.
+  dialog.classList.add('pulse');
+  dialog.addEventListener('webkitAnimationEnd', function(e) {
+    dialog.classList.remove('pulse');
+  });
+}
+
+function clickedInDialog(mouseEvent) {
+    var rect = dialog.getBoundingClientRect();
+    return rect.top <= mouseEvent.clientY && mouseEvent.clientY <= rect.top + rect.height
+        && rect.left <= mouseEvent.clientX && mouseEvent.clientX <= rect.left + rect.width;
+}
+
+function handleClickOutsideDialog() {
+    if (!shouldCloseDialogOnClickOutside()) {
+        pulseDialog();
+        return
+    }
+    closeDialog();
+}
+
+document.body.addEventListener('keydown', function(e) {
+    if (e.keyCode == 27)
+        closeDialog();
+});
+
+var enableBackdrop = document.getElementById('enable-backdrop');
+enableBackdrop.addEventListener('change', function(e) {
+  if (this.checked) {
+    dialog.className = '';
+  } else {
+    dialog.className = 'no-backdrop';
+  }
+});
+
+var closeButton = document.getElementById('close-button');
+closeButton.addEventListener('click', function(e) { closeDialog(); });
+
+document.body.addEventListener('click', function(e) {
+  console.info('document body click', e.target);
+    if (!dialog.open)
+        return;
+    if (e.target != document.body)
+        return;
+    handleClickOutsideDialog();
+});
+
+dialog.addEventListener('click', function(e) {
+    if (clickedInDialog(e))
+        return;
+    handleClickOutsideDialog();
+});
+initPosts();
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/form.html b/node_modules/dialog-polyfill/tests/form.html
new file mode 100644
index 0000000..75e1645
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/form.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+</head>
+<body>
+
+  <p>
+Enter a value and submit the form. The close event will be fired and the <code>returnValue</code> of the dialog will be alerted.
+  </p>
+
+<input type="text" placeholder="Focusable pre" />
+
+<dialog>
+  <form method="dialog">
+    <input type="text" placeholder="Enter value" />
+    <input type="reset" value="Reset" />
+    <input type="submit" value="Stuff" />
+    <input type="submit" value="Done" />
+    <button type="submit" value="Button Value Is Different Than Text">Button Submit</button>
+    <button value="Regular Button Value">Button</button>
+  </form>
+</dialog>
+
+<button id="show">Show Dialog</button>
+
+<input type="text" placeholder="Focusable post" />
+
+
+<script>
+var dialog = document.querySelector('dialog');
+dialogPolyfill.registerDialog(dialog);
+dialog.showModal();
+
+dialog.addEventListener('close', function() {
+  var valueEl = dialog.querySelector('input[type="text"]');
+  alert(dialog.returnValue);
+});
+
+show.addEventListener('click', function() {
+  dialog.showModal();
+});
+
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/modal-dialog-stacking.html b/node_modules/dialog-polyfill/tests/modal-dialog-stacking.html
new file mode 100644
index 0000000..e111ee4
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/modal-dialog-stacking.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+<style>
+dialog {
+    padding: 0px;
+    border: none;
+    margin: 0px;
+}
+
+#bottom + .backdrop {
+    top: 100px;
+    left: 100px;
+    height: 300px;
+    width: 300px;
+    background-color: rgb(0, 50, 0);
+    z-index: 100;  /* z-index has no effect. */
+}
+
+#bottom {
+    top: 125px;
+    left: 125px;
+    height: 250px;
+    width: 250px;
+    background-color: rgb(0, 90, 0);
+}
+
+#middle + .backdrop {
+    top: 150px;
+    left: 150px;
+    height: 200px;
+    width: 200px;
+    background-color: rgb(0, 130, 0);
+    z-index: -100;  /* z-index has no effect. */
+}
+
+#middle {
+    top: 175px;
+    left: 175px;
+    height: 150px;
+    width: 150px;
+    background-color: rgb(0, 170, 0);
+}
+
+#top + .backdrop {
+    top: 200px;
+    left: 200px;
+    height: 100px;
+    width: 100px;
+    background-color: rgb(0, 210, 0);
+    z-index: 0;  /* z-index has no effect. */
+}
+
+#top {
+    top: 225px;
+    left: 225px;
+    height: 50px;
+    width: 50px;
+    background-color: rgb(0, 255, 0);
+    z-index: -1000;  /* z-index has no effect. */
+}
+</style>
+</head>
+<body>
+Test for modal dialog and backdrop stacking order. The test passes if there are
+6 boxes enclosed in each other, becoming increasingly smaller and brighter
+green.
+<dialog id="top"></dialog>
+<dialog id="middle"></dialog>
+<dialog id="bottom"></dialog>
+<script>
+var topDialog = document.getElementById('top');
+dialogPolyfill.registerDialog(topDialog);
+var middleDialog = document.getElementById('middle');
+dialogPolyfill.registerDialog(middleDialog);
+var bottomDialog = document.getElementById('bottom');
+dialogPolyfill.registerDialog(bottomDialog);
+
+topDialog.showModal();
+bottomDialog.showModal();
+topDialog.close();  // Just to shuffle the top layer order around a little.
+middleDialog.showModal();
+topDialog.showModal();
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/modal-dialog.html b/node_modules/dialog-polyfill/tests/modal-dialog.html
new file mode 100644
index 0000000..be98e91
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/modal-dialog.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html tabindex="0">
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<meta name="viewport" content="width=device-width, user-scalable=no">
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+<style>
+  html {
+    border: 4px solid white;
+  }
+  html:focus {
+    border-color: red;
+  }
+dialog {
+  width: 100px;
+}
+</style>
+</head>
+<body>
+<p>Test for modal dialog. The test passes if you can click on "Click me" button,
+but can't click or tab to the "Can't click me!" button</p>
+<div id="console"></div>
+
+<input type="text" placeholder="Test 1" tabindex="1" />
+<input type="text" placeholder="Test 2" tabindex="2" />
+<input type="text" placeholder="Test 2.1" tabindex="2" />
+<input type="text" placeholder="Test 3" tabindex="3" />
+
+<input type="text" placeholder="Before dialog" />
+<dialog>
+  <button id="dialog-button">Click me</button>
+  <input type="text" placeholder="Focus me" />
+  <input type="text" placeholder="Focus me again" />
+</dialog>
+<button id="inert-button">Can't click me</button>
+<input type="text" placeholder="Can't focus me" />
+<script>
+function writeToConsole(s) {
+  var console = document.getElementById('console');
+  var span = document.createElement('span');
+  span.textContent = s;
+  console.appendChild(span);
+  console.appendChild(document.createElement('br'));
+}
+
+var dialog = document.querySelector('dialog');
+dialogPolyfill.registerDialog(dialog);
+dialog.showModal();
+
+var dialogButton = document.getElementById('dialog-button');
+dialogButton.addEventListener('click', function(e) {
+  writeToConsole("SUCCESS: dialog's button was clicked");
+});
+
+var inertButton = document.getElementById('inert-button');
+inertButton.addEventListener('click', function(e) {
+  writeToConsole('FAIL: inert button was clicked');
+});
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/resources/close_dialog.png b/node_modules/dialog-polyfill/tests/resources/close_dialog.png
new file mode 100644
index 0000000..793f60e
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/resources/close_dialog.png
Binary files differ
diff --git a/node_modules/dialog-polyfill/tests/resources/close_dialog_hover.png b/node_modules/dialog-polyfill/tests/resources/close_dialog_hover.png
new file mode 100644
index 0000000..fe896f9
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/resources/close_dialog_hover.png
Binary files differ
diff --git a/node_modules/dialog-polyfill/tests/respect-positioned-dialog.html b/node_modules/dialog-polyfill/tests/respect-positioned-dialog.html
new file mode 100644
index 0000000..1272136
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/respect-positioned-dialog.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+<style>
+dialog {
+  display: none;
+  height: 50px;
+  width: 50px;
+  padding: 0;
+  margin: 0;
+}
+
+dialog.left {
+  top: 100px;
+  left: 100px;
+}
+
+#middle {
+  top: 100px;
+  left: 200px;
+}
+</style>
+</head>
+<body>
+<p>Test that dialogs with an explicit static position don't get auto-centered.
+The test passes if there are three boxes aligned in a single row.</p>
+<div id="console"></div>
+<dialog id="left" class="left"></dialog>
+<dialog id="middle"></dialog>
+<dialog id="right"></dialog>
+<script>
+function writeToConsole(s) {
+  var console = document.getElementById('console');
+  var span = document.createElement('span');
+  span.textContent = s;
+  console.appendChild(span);
+  console.appendChild(document.createElement('br'));
+}
+
+var dialogs = document.querySelectorAll('dialog');
+for (var i = 0; i < dialogs.length; ++i)
+  dialogPolyfill.registerDialog(dialogs[i]);
+
+var leftDialog = document.getElementById('left');
+leftDialog.show();
+
+var middleDialog = document.getElementById('middle');
+middleDialog.show();
+
+var rightDialog = document.getElementById('right');
+rightDialog.style.top = '100px';
+rightDialog.style.left = '300px';
+rightDialog.show();
+</script>
+</body>
+</html>
diff --git a/node_modules/dialog-polyfill/tests/stacking.html b/node_modules/dialog-polyfill/tests/stacking.html
new file mode 100644
index 0000000..54de723
--- /dev/null
+++ b/node_modules/dialog-polyfill/tests/stacking.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<meta charset='utf-8'>
+<head>
+<script src="../dist/dialog-polyfill.js"></script>
+<link rel="stylesheet" type="text/css" href="../dist/dialog-polyfill.css">
+</head>
+<body>
+
+  <p>
+The dialog below is incorrectly positioned within a stacking context.
+It should generate a console warning message, and place the <code>_dialog_overlay</code> in a different place in order to try to work around the problem without moving the dialog itself.
+  </p>
+
+<div style="position: absolute; top: 100px; left: 10vw; width: 80vw; height: 400px; background: blue; opacity: 0.7;">
+
+<dialog>
+  <form method="dialog">
+    <input type="submit" />
+  </form>
+  <p>
+    Help, I'm inside an extra stacking context D:
+  </p>
+</dialog>
+
+</div>
+
+<button id="show">Show Dialog</button>
+
+<script>
+var dialog = document.querySelector('dialog');
+dialogPolyfill.registerDialog(dialog);
+dialog.showModal();
+
+show.addEventListener('click', function() {
+  dialog.showModal();
+});
+
+</script>
+</body>
+</html>