/**
 * @archivo modal-dialog.js
 * /
importar * como Dom desde './utils/dom';
importar componente desde './componente';
importar ventana desde 'global/window';
importar documento desde 'global/document';
importar código clave desde 'código clave';

const MODAL_CLASS_NAME = 'vjs-modal-dialog';

/**
 * El `ModalDialog` se muestra sobre el video y sus controles, lo que bloquea
 * interacción con el jugador hasta que se cierre.
 *
 * Los cuadros de diálogo modales incluyen un botón "Cerrar" y se cerrarán cuando ese botón
 * está activado - o cuando se presiona ESC en cualquier lugar.
 *
 * Componente @extiende
 * /
clase ModalDialog extiende Componente {

  /**
   * Crear una instancia de esta clase.
   *
   * @param {Jugador} jugador
   * El `Jugador` al que se debe adjuntar esta clase.
   *
   * @param {Objeto} [opciones]
   * El almacén de clave/valor de las opciones del jugador.
   *
   * @param {Mixto} [opciones.contenido=indefinido]
   * Proporcionar contenido personalizado para este modal.
   *
   * @param {cadena} [opciones.descripción]
   * Una descripción de texto para el modal, principalmente para la accesibilidad.
   *
   * @param {booleano} [opciones.fillAlways=false]
   * Normalmente, los modales se llenan automáticamente solo la primera vez
   * ellos abren. Esto le dice al modal que actualice su contenido.
   * cada vez que se abre.
   *
   * @param {cadena} [opciones.etiqueta]
   * Una etiqueta de texto para el modal, principalmente para la accesibilidad.
   *
   * @param {booleano} [opciones.pauseOnOpen=true]
   * Si es `verdadero`, la reproducción se pausará si se reproduce cuando
   * el modal se abre, y se reanuda cuando se cierra.
   *
   * @param {booleano} [opciones.temporal=verdadero]
   * Si es `verdadero`, el modal solo se puede abrir una vez; será
   * eliminado tan pronto como esté cerrado.
   *
   * @param {booleano} [opciones.uncloseable=false]
   * Si `verdadero`, el usuario no podrá cerrar el modal
   * a través de la interfaz de usuario de las formas normales. El cierre programático es
   * aun posible.
   * /
  constructor(jugador, opciones) {
    super(jugador, opciones);

    this.handleKeyDown_ = (e) => this.handleKeyDown(e);
    esto.cerrar_ = (e) => esto.cerrar(e);
    this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;

    this.closeable(!this.options_.uncloseable);
    este.contenido(esta.opciones_.contenido);

    // Asegúrese de que contentEl esté definido DESPUÉS de inicializar cualquier elemento secundario
    // porque solo queremos los contenidos del modal en el contentEl
    // (no los elementos de la interfaz de usuario como el botón de cerrar).
    this.contentEl_ = Dom.createEl('div', {
      className: `${MODAL_CLASS_NAME}-contenido`
    }, {
      rol: 'documento'
    });

    this.descEl_ = Dom.createEl('p', {
      className: `${MODAL_CLASS_NAME}-descripción vjs-control-text`,
      id: this.el().getAttribute('aria-descrito por')
    });

    Dom.textContent(this.descEl_, this.description());
    this.el_.appendChild(this.descEl_);
    this.el_.appendChild(this.contentEl_);
  }

  /**
   * Crear el elemento DOM de `ModalDialog`
   *
   * @return {Elemento}
   * El elemento DOM que se crea.
   * /
  crearEl() {
    return super.createEl('div', {
      nombre de clase: this.buildCSSClass(),
      índice de tabulación: -1
    }, {
      'aria-descrita por': `${this.id()}_description`,
      'aria-oculto': 'verdadero',
      'aria-etiqueta': this.label(),
      'rol': 'diálogo'
    });
  }

  disponer () {
    this.contentEl_ = null;
    esto.descEl_ = nulo;
    this.previouslyActiveEl_ = null;

    super.dispose();
  }

  /**
   * Construye el DOM predeterminado `className`.
   *
   * @return {cadena}
   * El DOM `className` para este objeto.
   * /
  construirClaseCSS() {
    devuelve `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
  }

  /**
   * Devuelve la cadena de etiquetas para este modal. Se utiliza principalmente para accesibilidad.
   *
   * @return {cadena}
   * la etiqueta localizada o cruda de este modal.
   * /
  etiqueta() {
    return this.localize(this.options_.label || 'Ventana modal');
  }

  /**
   * Devuelve la cadena de descripción para este modal. Utilizado principalmente para
   * accesibilidad.
   *
   * @return {cadena}
   * La descripción localizada o cruda de este modal.
   * /
  descripción() {
    let desc = this.options_.description || this.localize('Esta es una ventana modal.');

    // Agregue un mensaje de cierre universal si el modal se puede cerrar.
    if (esto.cerrable()) {
      desc += ' ' + this.localize('Este modal se puede cerrar presionando la tecla Escape o activando el botón de cerrar.');
    }

    volver desc;
  }

  /**
   * Abre el modal.
   *
   * @fires ModalDialog#beforemodalopen
   * @incendios ModalDialog#modalopen
   * /
  abierto() {
    if (!esto.abierto_) {
      const jugador = este.jugador();

      /**
        * Activado justo antes de que se abra un `ModalDialog`.
        *
        * @event ModalDialog#beforemodalopen
        * @type {Objetivo del evento~Evento}
        * /
      this.trigger('beforemodalopen');
      this.opened_ = true;

      // Rellene el contenido si el modal nunca se ha abierto antes y
      // nunca se ha llenado.
      if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
        esto.llenar();
      }

      // Si el jugador estaba jugando, pausarlo y tomar nota de su anterior
      // estado de reproducción.
      this.wasPlaying_ = !player.paused();

      if (this.options_.pauseOnOpen && this.wasPlaying_) {
        jugador.pausa();
      }

      this.on('keydown', this.handleKeyDown_);

      // Ocultar controles y anotar si estaban habilitados.
      this.hadControls_ = player.controls();
      jugador.controles (falso);

      este espectáculo();
      this.conditionalFocus_();
      this.el().setAttribute('aria-hidden', 'false');

      /**
        * Activado justo después de abrir un `ModalDialog`.
        *
        * @event ModalDialog#modalopen
        * @type {Objetivo del evento~Evento}
        * /
      this.trigger('modalopen');
      this.hasBeenOpened_ = true;
    }
  }

  /**
   * Si `ModalDialog` está actualmente abierto o cerrado.
   *
   * @param {booleano} [valor]
   * Si se da, abrirá (`true`) o cerrará (`false`) el modal.
   *
   * @return {booleano}
   * el estado abierto actual del modaldialog
   * /
  abierto (valor) {
    if (tipo de valor === 'booleano') {
      este valor ? 'abierto cerrado']();
    }
    devuelve esto.abierto_;
  }

  /**
   * Cierra el modal, no hace nada si `ModalDialog` está
   * no abierto.
   *
   * @fires ModalDialog#beforemodalclose
   * @fires ModalDialog#modalclose
   * /
  cerca() {
    if (!esto.abierto_) {
      devolver;
    }
    const jugador = este.jugador();

    /**
      * Activado justo antes de que se cierre un `ModalDialog`.
      *
      * @event ModalDialog#beforemodalclose
      * @type {Objetivo del evento~Evento}
      * /
    this.trigger('antes del cierre modal');
    esto.abierto_ = falso;

    si (esto.estabaJugando_ && this.options_.pauseOnOpen) {
      jugador.jugar();
    }

    this.off('keydown', this.handleKeyDown_);

    if (this.hadControls_) {
      jugador.controles(verdadero);
    }

    esto. ocultar ();
    this.el().setAttribute('aria-hidden', 'true');

    /**
      * Activado justo después de que se cierra un `ModalDialog`.
      *
      * @event ModalDialog#modalclose
      * @type {Objetivo del evento~Evento}
      * /
    this.trigger('modalclose');
    this.condicionalBlur_();

    if (esta.opciones_.temporal) {
      esto.dispose();
    }
  }

  /**
   * Verifique si `ModalDialog` se puede cerrar a través de la interfaz de usuario.
   *
   * @param {booleano} [valor]
   * Si se da como valor booleano, establecerá la opción `cierrable`.
   *
   * @return {booleano}
   * Devuelve el valor final de la opción cerrable.
   * /
  cerrable(valor) {
    if (tipo de valor === 'booleano') {
      const cerrable = this.closeable_ = !!valor;
      let close = this.getChild('closeButton');

      // Si esto se está cerrando y no tiene un botón de cierre, agregue uno.
      si (cerrable && !cerca) {

        // El botón de cerrar debe ser un elemento secundario del modal, no su
        // elemento de contenido, así que cambie temporalmente el elemento de contenido.
        const temp = this.contentEl_;

        this.contentEl_ = this.el_;
        close = this.addChild('closeButton', {controlText: 'Cerrar diálogo modal'});
        this.contentEl_ = temp;
        this.on(cerrar, 'cerrar', this.close_);
      }

      // Si esto se hace imposible de cerrar y tiene un botón de cierre, elimínelo.
      si (! cerrable && cerca) {
        this.off(cerrar, 'cerrar', this.close_);
        this.removeChild(cerrar);
        cerrar.dispose();
      }
    }
    devuelve esto.cerrable_;
  }

  /**
   * Rellene el elemento de contenido del modal con la opción "contenido" del modal.
   * El elemento de contenido se vaciará antes de que se produzca este cambio.
   * /
  llenar() {
    esto.llenarCon(este.contenido());
  }

  /**
   * Rellene el elemento de contenido del modal con contenido arbitrario.
   * El elemento de contenido se vaciará antes de que se produzca este cambio.
   *
   * @fires ModalDialog#beforemodalfill
   * @fires ModalDialog#modalfill
   *
   * @param {Mixto} [contenido]
   * Se aplican las mismas reglas que se aplican a la opción `contenido`.
   * /
  llenar con (contenido) {
    const contenidoEl = this.contentEl();
    const parentEl = contentEl.parentNode;
    const nextSiblingEl = contentEl.nextSibling;

    /**
      * Activado justo antes de que `ModalDialog` se llene de contenido.
      *
      * @event ModalDialog#beforemodalfill
      * @type {Objetivo del evento~Evento}
      * /
    this.trigger('antesdelrellenomodal');
    this.hasBeenFilled_ = true;

    // Separar el elemento de contenido del DOM antes de realizar
    // manipulación para evitar modificar el DOM en vivo varias veces.
    parentEl.removeChild(contentEl);
    esto.vacío();
    Dom.insertContent(contenidoEl, contenido);
    /**
     * Activado justo después de que un `ModalDialog` esté lleno de contenido.
     *
     * @event ModalDialog#modalfill
     * @type {Objetivo del evento~Evento}
     * /
    this.trigger('modalfill');

    // Vuelva a inyectar el elemento de contenido rellenado.
    if (siguienteSiblingEl) {
      parentEl.insertBefore(contentEl, nextSiblingEl);
    } else {
      padreEl.appendChild(contenidoEl);
    }

    // asegúrese de que el botón de cerrar sea el último en el diálogo DOM
    const closeButton = this.getChild('closeButton');

    si (botóncerrar) {
      parentEl.appendChild(closeButton.el_);
    }
  }

  /**
   * Vacía el elemento de contenido. Esto sucede cada vez que se llena el modal.
   *
   * @fires ModalDialog#beforemodalempty
   * @fires ModalDialog#modalempty
   * /
  vacío() {
    /**
    * Activado justo antes de que se vacíe un `ModalDialog`.
    *
    * @event ModalDialog#beforemodalempty
    * @type {Objetivo del evento~Evento}
    * /
    this.trigger('beforemodalempty');
    Dom.emptyEl(this.contentEl());

    /**
    * Activado justo después de vaciar un `ModalDialog`.
    *
    * @event ModalDialog#modalempty
    * @type {Objetivo del evento~Evento}
    * /
    this.trigger('modalempty');
  }

  /**
   * Obtiene o establece el contenido modal, que se normaliza antes de ser
   * renderizado en el DOM.
   *
   * Esto no actualiza el DOM ni llena el modal, pero se llama durante
   * ese proceso.
   *
   * @param {Mixto} [valor]
   * Si está definido, establece el valor de contenido interno que se utilizará en el
   * próxima(s) llamada(s) a `llenar`. Este valor se normaliza antes de ser
   * insertado. Para "borrar" el valor del contenido interno, pase `null`.
   *
   * @return {Mixto}
   * El contenido actual del diálogo modal
   * /
  contenido (valor) {
    if (tipo de valor! == 'indefinido') {
      this.content_ = valor;
    }
    devolver este.content_;
  }

  /**
   * enfocar condicionalmente el diálogo modal si el foco estaba previamente en el jugador.
   *
   * @privado
   * /
  enfoque condicional_() {
    const activeEl = documento.activeElement;
    const jugadorEl = este.jugador_.el_;

    this.previouslyActiveEl_ = null;

    if (jugadorEl.contains(activoEl) || jugadorEl === activoEl) {
      this.previouslyActiveEl_ = activeEl;

      este.enfoque();
    }
  }

  /**
   * desenfocar condicionalmente el elemento y volver a enfocar el último elemento enfocado
   *
   * @privado
   * /
  condicionalBlur_() {
    if (this.previouslyActiveEl_) {
      this.previouslyActiveEl_.focus();
      this.previouslyActiveEl_ = null;
    }
  }

  /**
   * Manejador de teclas. Se adjunta cuando el modal está enfocado.
   *
   * @escucha tecla abajo
   * /
  handleKeyDown(evento) {

    // No permita que las pulsaciones de teclado salgan del cuadro de diálogo modal.
    event.stopPropagation();

    if (keycode.isEventKey(evento, 'Escape') && esto.cerrable()) {
      event.preventDefault();
      esto.cerrar();
      devolver;
    }

    // salir temprano si no es una tecla de tabulación
    if (!keycode.isEventKey(evento, 'Tab')) {
      devolver;
    }

    const focusableEls = this.focusableEls_();
    const activeEl = this.el_.querySelector(':foco');
    dejar focusIndex;

    para (sea i = 0; i < enfocableEls.longitud; i++) {
      if (activeEl === focusableEls[i]) {
        índice de enfoque = i;
        romper;
      }
    }

    if (document.activeElement === this.el_) {
      índice de enfoque = 0;
    }

    if (evento.shiftKey && índice de enfoque === 0) {
      focusableEls[focusableEls.longitud - 1].focus();
      event.preventDefault();
    } más si (!event.shiftKey && focusIndex === focusableEls.longitud - 1) {
      enfocableEls[0].focus();
      event.preventDefault();
    }
  }

  /**
   * obtener todos los elementos enfocables
   *
   * @privado
   * /
  enfocableEls_() {
    const allChildren = this.el_.querySelectorAll('*');

    return Array.prototype.filter.call(allChildren, (hijo) => {
      return ((instancia secundaria de ventana.HTMLAnchorElement ||
               instancia secundaria de ventana.HTMLAreaElement) && niño.hasAttribute('href')) ||
             ((instancia secundaria de ventana.HTMLInputElement ||
               instancia secundaria de ventana.HTMLSelectElement ||
               instancia secundaria de ventana.HTMLTextAreaElement ||
               instancia secundaria de ventana.HTMLButtonElement) && !child.hasAttribute('deshabilitado')) ||
             (instancia secundaria de ventana.HTMLIFrameElement ||
               instancia secundaria de ventana.HTMLObjectElement ||
               instancia secundaria de ventana.HTMLEmbedElement) ||
             (hijo.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1) ||
             (child.hasAttribute('contenteditable'));
    });
  }
}

/**
 * Opciones predeterminadas para las opciones predeterminadas de `ModalDialog`.
 *
 * @type {Objeto}
 * @privado
 * /
ModalDialog.prototipo.opciones_ = {
  pauseOnOpen: verdadero,
  temporal: verdadero
};

Componente.registerComponent('ModalDialog', ModalDialog);
exportar ModalDialog predeterminado;