text: import ViewController from '~/controllers/ViewController';
  import ModalViewTemplate from '~/template/ModalViewTemplate';
  import KeyManager from '~/models/KeyManager';
  import { HandleUnhandledPromise } from '~/helpers/ErrorManager';
  
  export const MODAL_BLUR_RADIUS = '16px';
  
  /**
   * This is like {@link ModalController} but better. This uses ModalTemplate
   */
  export default class ModalViewController extends ViewController {
      /**
       * Creates controller for context
       * @param {HTMLElement} context - you prob want to use .shared
       * @param {Object} opts
       * @param {number} [opts.baseZIndex=0]
       * @param {boolean} bumpAnimation If should default show bump anim
       */
      constructor(context, { baseZIndex = 10, bumpAnimation = true } = {}) {
          super();
  
          /** @private */
          this.context = context;
  
          this._dim = null;
          this._activeTemplate = null;
          this._activeTemplateModel = null;
          this._eventListener = null;
  
          this._removeKeyHandler = null;
  
          this._baseZIndex = baseZIndex;
          this._bumpAnimation = bumpAnimation;
      }
  
      /**
       * Binds an element to trigger to show a modal. If the ID does NOT
       * exist this will not do any binding.
       *
       * @param {string|HTMLElement} node - ID of node or the node itself
       * @param {ModalViewTemplate} template - The modal template to show
       */
      bind(node, template) {
          if (typeof node === 'string') {
              node = document.getElementById(node);
          }
  
          if (node) {
              node.addEventListener('click', () => {
                  this.present(template)
                      .catch(HandleUnhandledPromise);
              });
          }
      }
  
      /**
       * Presents a template.
       * @param {ModalViewTemplate} template - The modal template
       * @param {Object} opts
       * @param {boolean} [opts.bumpAnimation=true] show a little bump when animating
       * @param {string} [opts.alignmentClass=""] class to align with
       */
      async present(template, { bumpAnimation = this._bumpAnimation, alignmentClass = "" } = {}) {
          await this.hide();
  
          const anime = await import('animejs');
  
          const dim = <div class={`modal-view__dim ${alignmentClass}`}/>;
          const instance = template.loadInContext(dim);
  
          const listener = dim.addEventListener('click', (event) => {
              if ((document.body.contains(event.target) || event.target === document.body) && !instance.contains(event.target)) {
                  this.hide()
                      .catch(HandleUnhandledPromise);
              }
          })
  
          this._dim = dim;
          this._eventListener = listener;
          this._activeTemplateModel = template;
          this._activeTemplate = instance; // Set this last to avoid race condition
  
          dim.style.zIndex = this._baseZIndex;
  
          instance.style.zIndex = +dim.style.zIndex + 1;
  
          this.context.appendChild(dim);
  
          this._removeKeyHandler = KeyManager.shared.register('Escape', () => {
              this.hide()
                  .catch(HandleUnhandledPromise);
          });
  
          const timeline = anime.timeline()
              .add({
                  targets: dim,
                  opacity: [0, 1],
                  backdropFilter: ['blur(0px)', `blur(${MODAL_BLUR_RADIUS})`],
                  webkitBackdropFilter: ['blur(0px)', `blur(${MODAL_BLUR_RADIUS})`],
                  duration: 300
              })
  
          if (bumpAnimation) {
              instance.style.opacity = 0;
              timeline.add({
                  offset: '-=50',
                  targets: instance,
                  opacity: [0, 1],
                  top: ['60%', '50%'],
                  easing: 'easeOutBack',
                  duration: 500
              });
          } else {
              instance.style.opacity = 1;
          }
  
          await timeline.finished;
  
          template.controller = this;
      }
  
      /**
       * Hides the current template if there is one
       * @param {Object} opts
       * @param {boolean} [bumpAnimation=true] show a little bump when animating
       */
      async hide({ bumpAnimation = this._bumpAnimation } = {}) {
          if (!this._activeTemplate) return;
  
          this._activeTemplateModel.willUnload();
  
          this._removeKeyHandler?.();
  
          // Avoids race condition
          const instance = this._activeTemplate,
              template = this._activeTemplateModel;
          this._activeTemplate = null;
          this._activeTemplateModel = null;
  
          this._dim.removeEventListener('click', this._eventListener);
  
          const anime = await import('animejs');
  
          this._dim.style.pointerEvents = 'none'
          const timeline = anime.timeline()
              .add({
                  targets: this._dim,
                  opacity: [1, 0],
                  backdropFilter: [`blur(${MODAL_BLUR_RADIUS})`, 'blur(0px)'],
                  webkitBackdropFilter: [`blur(${MODAL_BLUR_RADIUS})`, 'blur(0px)'],
                  elasticity: 0,
                  duration: 300,
                  offset: bumpAnimation ? 400 : 0
              });
  
          if (bumpAnimation) {
              timeline.add({
                  targets: instance,
                  opacity: [1, 0],
                  top: ['50%', '60%'],
                  easing: 'easeInBack',
                  offset: 0,
                  duration: 500,
                  elasticity: 0
              })
          }
  
          await timeline.finished;
          template.didUnload();
  
          this.context.removeChild(this._dim);
  
          this._dim = null;
      }
  
      static shared = new ModalViewController(document.body);
  }
  