/**
 * @archivo dom.js
 * @módulo dom
 * /
importar documento desde 'global/document';
importar ventana desde 'global/window';
importar fs desde '../fullscreen-api';
importar registro desde './log.js';
importar {isObject} desde './obj';
importar estilo computado desde './estilo computado';
importar * como navegador desde './navegador';

/ **
 * Detectar si un valor es una cadena con caracteres que no sean espacios en blanco.
 *
 * @privado
 * @param {cadena} cadena
 * La cadena a comprobar
 *
 * @return {booleano}
 * Será `verdadero` si la cadena no está en blanco, `falso` en caso contrario.
 *
 * /
función esNonBlankString(str) {
  // usamos str.trim ya que recortará cualquier carácter de espacio en blanco
  // desde el anverso o reverso de los caracteres que no son espacios en blanco. alias
  // Cualquier cadena que contenga caracteres que no sean espacios en blanco
  // aún los contiene después de `trim` pero solo cadenas de espacios en blanco
  // tendrá una longitud de 0, fallando esta verificación.
  devuelve el tipo de cadena === 'cadena' && Booleano(str.trim());
}

/ **
 * Lanza un error si la cadena pasada tiene espacios en blanco. Esto es usado por
 * métodos de clase para ser relativamente coherentes con la API classList.
 *
 * @privado
 * @param {cadena} cadena
 * La cadena para buscar espacios en blanco.
 *
 * @throws {Error}
 * Lanza un error si hay espacios en blanco en la cadena.
 * /
función throwIfWhitespace(str) {
  // str.indexOf en lugar de regex porque str.indexOf tiene un rendimiento más rápido.
  si (str.indexOf(' ') > = 0) {
    throw new Error('la clase tiene espacios en blanco ilegales');
  }
}

/ **
 * Producir una expresión regular para hacer coincidir un className dentro de un elemento className.
 *
 * @privado
 * @param {cadena} nombre de clase
 * El className para generar RegExp.
 *
 * @return {RegExp}
 * RegExp que buscará un `className` específico en un elemento
 * nombre de la clase.
 * /
function claseRegExp(nombreClase) {
  return new RegExp('(^|\\s)' + className + '($|\\s)');
}

/ **
 * Si la interfaz DOM actual parece ser real (es decir, no simulada).
 *
 * @return {booleano}
 * Será `verdadero` si el DOM parece ser real, `falso` en caso contrario.
 * /
función de exportación isReal() {
  // Tanto el documento como la ventana nunca estarán indefinidos gracias a `global`.
  devolver documento === ventana.documento;
}

/ **
 * Determina, a través de la tipificación pato, si un valor es o no un elemento DOM.
 *
 * valor @param {Mixto}
 * El valor a consultar.
 *
 * @return {booleano}
 * Será `verdadero` si el valor es un elemento DOM, `falso` en caso contrario.
 * /
función de exportación isEl(valor) {
  devolver esObjeto(valor) && valor.tipoNodo === 1;
}

/ **
 * Determina si el DOM actual está incrustado en un iframe.
 *
 * @return {booleano}
 * Será `verdadero` si el DOM está incrustado en un iframe, `falso`
 * de lo contrario.
 * /
función de exportación isInFrame() {

  // Necesitamos probar/atrapar aquí porque Safari arrojará errores al intentar
  // para obtener `parent` o `self`
  intentar {
    return ventana.padre !== ventana.self;
  } atrapar (x) {
    devolver verdadero;
  }
}

/ **
 * Crea funciones para consultar el DOM usando un método dado.
 *
 * @privado
 * Método @param {cadena}
 * El método con el que crear la consulta.
 *
 * @return {Función}
 * El método de consulta
 * /
función createQuerier(método) {
  función de retorno (selector, contexto) {
    if (!isNonBlankString(selector)) {
      devolver documento[método](nulo);
    }
    if (no es una cadena en blanco (contexto)) {
      contexto = documento.querySelector(contexto);
    }

    const ctx = isEl(contexto) ? contexto : documento;

    devolver ctx[método] && ctx[método](selector);
  };
}

/ **
 * Crea un elemento y aplica propiedades, atributos e inserta contenido.
 *
 * @param {cadena} [tagName='div']
 * Nombre de la etiqueta a crear.
 *
 * @param {Objeto} [propiedades={}]
 * Propiedades del elemento a aplicar.
 *
 * @param {Objeto} [atributos={}]
 * Atributos del elemento a aplicar.
 *
 * @param {módulo:dom~ContentDescriptor} contenido
 * Un objeto descriptor de contenido.
 *
 * @return {Elemento}
 * El elemento que se creó.
 * /
función de exportación createEl(tagName = 'div', propiedades = {}, atributos = {}, contenido) {
  const el = document.createElement(tagName);

  Object.getOwnPropertyNames(properties).forEach(function(propName) {
    const val = propiedades[propName];

    // Ver #2176
    // Originalmente estábamos aceptando propiedades y atributos en el
    // mismo objeto, pero eso no funciona tan bien.
    if (propName.indexOf('aria-') !== -1 || propName === 'rol' || propName === 'tipo') {
      log.warn('Estableciendo atributos en el segundo argumento de createEl()\n' +
               'ha sido desaprobado. Usa el tercer argumento en su lugar.\n' +
               `createEl(tipo, propiedades, atributos). Intentando establecer ${propName} en ${val}.`);
      el.setAttribute(propName, val);

    // Manejar textContent ya que no es compatible en todas partes y tenemos un
    // método para ello.
    } else if (propName === 'textContent') {
      contenido de texto(el, val);
    } else if (el[propName] !== val || propName === 'tabIndex') {
      el[nombreprop] = val;
    }
  });

  Object.getOwnPropertyNames(atributos).forEach(función(attrName) {
    el.setAttribute(attrName, atributos[attrName]);
  });

  si (contenido) {
    appendContent(el, contenido);
  }

  volver el;
}

/ **
 * Inyecta texto en un elemento, reemplazando por completo cualquier contenido existente.
 *
 * @param {Elemento} el
 * El elemento para agregar contenido de texto en
 *
 * @param {cadena} texto
 * El contenido de texto a agregar.
 *
 * @return {Elemento}
 * El elemento con contenido de texto añadido.
 * /
función de exportación textContent(el, texto) {
  if (typeof el.textContent === 'indefinido') {
    el.innerText = texto;
  } else {
    el.textContent = texto;
  }
  volver el;
}

/ **
 * Insertar un elemento como el primer nodo secundario de otro
 *
 * @param {Elemento} hijo
 * Elemento a insertar
 *
 * @param {Elemento} padre
 * Elemento para insertar niño en
 * /
función de exportación prependTo (hijo, padre) {
  if (padre.primerhijo) {
    parent.insertBefore(child, parent.firstChild);
  } else {
    padre.appendChild(hijo);
  }
}

/ **
 * Comprobar si un elemento tiene un nombre de clase.
 *
 * @param {Elemento} elemento
 * Elemento a comprobar
 *
 * @param {cadena} classToCheck
 * Nombre de la clase para verificar
 *
 * @return {booleano}
 * Será `verdadero` si el elemento tiene una clase, `falso` en caso contrario.
 *
 * @throws {Error}
 * Lanza un error si `classToCheck` tiene espacios en blanco.
 * /
función de exportación hasClass(elemento, classToCheck) {
  throwIfWhitespace(claseParaComprobar);
  si (elemento.classList) {
    return elemento.classList.contains(classToCheck);
  }
  return classRegExp(classToCheck).test(element.className);
}

/ **
 * Agregar un nombre de clase a un elemento.
 *
 * @param {Elemento} elemento
 * Elemento para agregar el nombre de la clase.
 *
 * @param {cadena} classToAdd
 * Nombre de clase para agregar.
 *
 * @return {Elemento}
 * El elemento DOM con el nombre de clase agregado.
 * /
función de exportación addClass(elemento, classToAdd) {
  si (elemento.classList) {
    elemento.classList.add(classToAdd);

  // No es necesario `throwIfWhitespace` aquí porque `hasElClass` lo hará
  // en el caso de que classList no sea compatible.
  } else if (!hasClass(elemento, classToAdd)) {
    element.className = (element.className + ' ' + classToAdd).trim();
  }

  elemento de retorno;
}

/ **
 * Eliminar un nombre de clase de un elemento.
 *
 * @param {Elemento} elemento
 * Elemento para eliminar un nombre de clase.
 *
 * @param {cadena} classToRemove
 * Nombre de clase para eliminar
 *
 * @return {Elemento}
 * El elemento DOM con el nombre de clase eliminado.
 * /
función de exportación removeClass(elemento, classToRemove) {
  // Proteger en caso de que el jugador sea desechado
  si (!elemento) {
    log.warn("Se llamó a removeClass con un elemento que no existe");
    devolver nulo;
  }
  si (elemento.classList) {
    elemento.classList.remove(classToRemove);
  } else {
    throwIfWhitespace(claseParaEliminar);
    elemento.nombreClase = elemento.nombreClase.split(/\s+/).filtro(función(c) {
      return c !== classToRemove;
    }).unirse(' ');
  }

  elemento de retorno;
}

/ **
 * La definición de devolución de llamada para toggleClass.
 *
 * Módulo @callback:dom~PredicateCallback
 * @param {Elemento} elemento
 * El elemento DOM del Componente.
 *
 * @param {cadena} classToToggle
 * El `className` que quiere ser alternado
 *
 * @return {booleano|indefinido}
 * Si se devuelve `true`, `classToToggle` se agregará a la
 * `elemento`. Si es `falso`, `classToToggle` se eliminará de
 * el `elemento`. Si es `indefinido`, se ignorará la devolución de llamada.
 * /

/ **
 * Agrega o elimina un nombre de clase a/de un elemento dependiendo de un opcional
 * condición o la presencia/ausencia del nombre de la clase.
 *
 * @param {Elemento} elemento
 * El elemento para alternar un nombre de clase.
 *
 * @param {cadena} classToToggle
 * La clase que debe alternarse.
 *
 * @param {booleano|módulo:dom~PredicateCallback} [predicado]
 * Ver el valor devuelto para {@link module:dom~PredicateCallback}
 *
 * @return {Elemento}
 * El elemento con una clase que ha sido alternada.
 * /
función de exportación toggleClass (elemento, classToToggle, predicado) {

  // Esto NO PUEDE usar `classList` internamente porque IE11 no es compatible con
  // ¡segundo parámetro del método `classList.toggle()`! lo cual está bien porque
  // `classList` será utilizado por las funciones de agregar/eliminar.
  const has = hasClass(elemento, classToToggle);

  if (tipo de predicado === 'función') {
    predicado = predicado (elemento, classToToggle);
  }

  if (tipo de predicado !== 'booleano') {
    predicado = !tiene;
  }

  // Si la operación de clase necesaria coincide con el estado actual del
  // elemento, no se requiere ninguna acción.
  si (predicado === tiene) {
    devolver;
  }

  si (predicado) {
    addClass(elemento, classToToggle);
  } else {
    removeClass(elemento, classToToggle);
  }

  elemento de retorno;
}

/ **
 * Aplicar atributos a un elemento HTML.
 *
 * @param {Elemento} el
 * Elemento al que añadir atributos.
 *
 * @param {Objeto} [atributos]
 * Atributos a aplicar.
 * /
función de exportación setAttributes(el, atributos) {
  Object.getOwnPropertyNames(atributos).forEach(función(attrName) {
    const attrValue = atributos[attrName];

    if (attrValue === null || typeof attrValue === 'indefinido' || attrValue === falso) {
      el.removeAttribute(attrName);
    } else {
      el.setAttribute(attrName, (attrValue === true ? '' : attrValue));
    }
  });
}

/ **
 * Obtenga los valores de atributo de un elemento, tal como se define en la etiqueta HTML.
 *
 * Los atributos no son lo mismo que las propiedades. Están definidos en la etiqueta.
 * o con setAttribute.
 *
 * Etiqueta @param {Elemento}
 * Elemento del que obtener atributos de etiqueta.
 *
 * @return {Objeto}
 * Todos los atributos del elemento. Los atributos booleanos serán `verdadero` o
 * `falso`, los demás serán cadenas.
 * /
función de exportación getAttributes(etiqueta) {
  constante obj = {};

  // atributos booleanos conocidos
  // podemos verificar las propiedades booleanas coincidentes, pero no todos los navegadores
  // y no todas las etiquetas conocen estos atributos, por lo que aún queremos verificarlos manualmente
  const booleanos conocidos = ',' + 'autoplay,controles,playsinline,loop,muted,default,defaultMuted' + ',';

  si (etiqueta && etiqueta.atributos && etiqueta.atributos.longitud > 0) {
    const attrs = etiqueta.atributos;

    for (sea i = attrs.length - 1; i > = 0; i--) {
      const atributoNombre = atributos[i].nombre;
      let attrVal = attrs[i].value;

      // comprueba si hay valores booleanos conocidos
      // la propiedad del elemento coincidente devolverá un valor para typeof
      if (tipodeetiqueta[nombreAtributo] === 'booleano' || booleanos conocidos.indexOf(',' + nombreAtributo + ',') !== -1) {
        // el valor de un atributo booleano incluido suele ser un valor vacío
        // cadena ('') que sería igual a falso si solo buscamos un valor falso.
        // tampoco queremos admitir código incorrecto como autoplay='false'
        attrVal = (attrVal !== null) ? verdadero Falso;
      }

      obj[nombreatributo] = valoratributo;
    }
  }

  devolver objeto;
}

/ **
 * Obtener el valor del atributo de un elemento.
 *
 * @param {Elemento} el
 * Un elemento DOM.
 *
 * atributo @param {cadena}
 * Atributo para obtener el valor de.
 *
 * @return {cadena}
 * El valor del atributo.
 * /
función de exportación getAttribute(el, atributo) {
  devuelve el.getAttribute(atributo);
}

/ **
 * Establecer el valor del atributo de un elemento.
 *
 * @param {Elemento} el
 * Un elemento DOM.
 *
 * atributo @param {cadena}
 * Atributo a establecer.
 *
 * @param {cadena} valor
 * Valor para establecer el atributo.
 * /
función de exportación setAttribute(el, atributo, valor) {
  el.setAttribute(atributo, valor);
}

/ **
 * Eliminar el atributo de un elemento.
 *
 * @param {Elemento} el
 * Un elemento DOM.
 *
 * atributo @param {cadena}
 * Atributo a eliminar.
 * /
función de exportación removeAttribute(el, atributo) {
  el.removeAttribute(atributo);
}

/ **
 * Intento de bloquear la capacidad de seleccionar texto.
 * /
función de exportación blockTextSelection() {
  documento.cuerpo.focus();
  documento.onselectstart = function() {
    falso retorno;
  };
}

/ **
 * Desactivar el bloqueo de selección de texto.
 * /
función de exportación desbloquearTextSelection() {
  documento.onselectstart = function() {
    devolver verdadero;
  };
}

/ **
 * Idéntica a la función `getBoundingClientRect` nativa, pero asegura que
 * el método es compatible (está en todos los navegadores que afirmamos admitir)
 * y que el elemento esté en el DOM antes de continuar.
 *
 * Esta función contenedora también corrige propiedades que no proporcionan algunos
 * navegadores más antiguos (a saber, IE8).
 *
 * Además, algunos navegadores no admiten agregar propiedades a un
 * Objeto `ClientRect`/`DOMRect`; entonces, lo copiamos superficialmente con el estándar
 * propiedades (excepto `x` e `y` que no son ampliamente compatibles). Esto ayuda
 * evitar implementaciones donde las claves no son enumerables.
 *
 * @param {Elemento} el
 * Elemento cuyo `ClientRect` queremos calcular.
 *
 * @return {Objeto|indefinido}
 * Siempre devuelve un objeto simple, o `indefinido` si no puede.
 * /
función de exportación getBoundingClientRect(el) {
  Me caí && el.getBoundingClientRect && el.parentNode) {
    const rect = el.getBoundingClientRect();
    resultado constante = {};

    ['abajo', 'altura', 'izquierda', 'derecha', 'arriba', 'ancho'].forEach(k => {
      if (rect[k] !== indefinido) {
        resultado[k] = rect[k];
      }
    });

    if (!resultado.altura) {
      resultado.altura = parseFloat(computedStyle(el, 'altura'));
    }

    if (!resultado.ancho) {
      resultado.ancho = parseFloat(computedStyle(el, 'ancho'));
    }

    resultado devuelto;
  }
}

/ **
 * Representa la posición de un elemento DOM en la página.
 *
 * @typedef {Objeto} módulo:dom~Posición
 *
 * @property queda {número}
 * Píxeles a la izquierda.
 *
 * @propiedad {número} superior
 * Píxeles desde la parte superior.
 * /

/ **
 * Obtener la posición de un elemento en el DOM.
 *
 * Utiliza la técnica `getBoundingClientRect` de John Resig.
 *
 * @ver http://ejohn.org/blog/getboundingclientrec-is-awesome/
 *
 * @param {Elemento} el
 * Elemento desde el que obtener la compensación.
 *
 * @return {módulo:dom~Posición}
 * La posición del elemento que se pasó.
 * /
función de exportación findPosition(el) {
  si (!el || (el && !el.offsetParent)) {
    regreso {
      izquierda: 0,
      arriba: 0,
      ancho: 0,
      altura: 0
    };
  }
  const ancho = el.offsetWidth;
  const altura = el.offsetHeight;
  deja a la izquierda = 0;
  sea arriba = 0;

  while (el.offsetParent && el !== documento[fs.fullscreenElement]) {
    izquierda += el.offsetLeft;
    top += el.offsetTop;

    el = el.offsetParent;
  }

  regreso {
    izquierda,
    arriba,
    ancho,
    altura
  };
}

/ **
 * Representa las coordenadas x e y de un elemento DOM o puntero del mouse.
 *
 * @typedef {Objeto} módulo:dom~Coordenadas
 *
 * @propiedad {número} x
 * coordenada x en píxeles
 *
 * @propiedad {número} y
 * coordenada y en píxeles
 * /

/ **
 * Obtener la posición del puntero dentro de un elemento.
 *
 * La base de las coordenadas es la parte inferior izquierda del elemento.
 *
 * @param {Elemento} el
 * Elemento sobre el que colocar la posición del puntero.
 *
 * @param {EventTarget~Evento} evento
 * Objeto de evento.
 *
 * @return {módulo:dom~Coordenadas}
 * Un objeto de coordenadas correspondiente a la posición del ratón.
 *
 * /
función de exportación getPointerPosition(el, evento) {
  constante traducida = {
    X: 0,
    y: 0
  };

  si (navegador.IS_IOS) {
    let elemento = el;

    mientras (elemento && item.nodeName.toLowerCase() !== 'html') {
      const transformar = estilo calculado (elemento, 'transformar');

      si (/^matriz/.prueba(transformar)) {
        valores const = transform.slice(7, -1).split(/,\s/).map(Number);

        traducido.x += valores[4];
        traducido.y += valores[5];
      } más si (/^matrix3d/.test(transformar)) {
        valores const = transform.slice(9, -1).split(/,\s/).map(Number);

        traducido.x += valores[12];
        traducido.y += valores[13];
      }

      elemento = elemento.nodopadre;
    }
  }

  posición constante = {};
  const boxTarget = findPosition(event.target);
  const box = buscarPosición(el);
  const cajaW = caja.ancho;
  const cajaH = caja.altura;
  let offsetY = event.offsetY - (box.top - boxTarget.top);
  let offsetX = event.offsetX - (box.left - boxTarget.left);

  si (evento.toques cambiados) {
    offsetX = event.changedTouches[0].pageX - box.left;
    offsetY = event.changedTouches[0].pageY + box.top;
    si (navegador.IS_IOS) {
      offsetX -= traducido.x;
      offsetY -= traducido.y;
    }
  }

  position.y = (1 - Math.max(0, Math.min(1, offsetY / boxH)));
  position.x = Math.max(0, Math.min(1, offsetX / boxW));
  posición de retorno;
}

/ **
 * Determina, mediante digitación pato, si un valor es o no un nodo de texto.
 *
 * valor @param {Mixto}
 * Comprobar si este valor es un nodo de texto.
 *
 * @return {booleano}
 * Será `verdadero` si el valor es un nodo de texto, `falso` en caso contrario.
 * /
función de exportación isTextNode(valor) {
  devolver esObjeto(valor) && valor.tipoNodo === 3;
}

/ **
 * Vacía el contenido de un elemento.
 *
 * @param {Elemento} el
 * El elemento para vaciar a los niños de
 *
 * @return {Elemento}
 * El elemento sin hijos
 * /
exportar función vaciarEl(el) {
  while (el.primerNiño) {
    el.removeChild(el.firstChild);
  }
  volver el;
}

/ **
 * Este es un valor mixto que describe el contenido que se inyectará en el DOM
 * a través de algún método. Puede ser de los siguientes tipos:
 *
 * Tipo | Descripción
 * -----------|-------------
 * `cadena` | El valor se normalizará en un nodo de texto.
 * `Elemento` | El valor se aceptará tal cual.
 * `NodoTexto` | El valor se aceptará tal cual.
 * `Matriz` | Una matriz unidimensional de cadenas, elementos, nodos de texto o funciones. Estas funciones deben devolver una cadena, un elemento o un nodo de texto (se ignorará cualquier otro valor de retorno, como una matriz).
 * `Función` | Una función, que se espera que devuelva una cadena, un elemento, un nodo de texto o una matriz, cualquiera de los otros valores posibles descritos anteriormente. Esto significa que un descriptor de contenido podría ser una función que devuelva una matriz de funciones, pero esas funciones de segundo nivel deben devolver cadenas, elementos o nodos de texto.
 *
 * @typedef {cadena|Elemento|TextNode|Array|Función} módulo:dom~ContentDescriptor
 * /

/ **
 * Normaliza el contenido para una eventual inserción en el DOM.
 *
 * Esto permite una amplia gama de métodos de definición de contenido, pero ayuda a proteger
 * de caer en la trampa de simplemente escribir en `innerHTML`, lo que podría
 * ser una preocupación XSS.
 *
 * El contenido de un elemento se puede pasar en varios tipos y
 * combinaciones, cuyo comportamiento es el siguiente:
 *
 * @param {módulo:dom~ContentDescriptor} contenido
 * Un valor descriptor de contenido.
 *
 * @return {Array}
 * Todo el contenido que se pasó, normalizado a una matriz de
 * elementos o nodos de texto.
 * /
función de exportación normalizeContent(contenido) {

  // Primero, invoca el contenido si es una función. Si produce una matriz,
  // eso debe suceder antes de la normalización.
  if (tipo de contenido === 'función') {
    contenido = contenido();
  }

  // A continuación, normalizar a una matriz, para que uno o varios elementos se puedan normalizar,
  // filtrado y devuelto.
  return (Array.isArray(contenido) ? contenido : [contenido]).map(valor => {

    // Primero, invoque el valor si es una función para producir un nuevo valor,
    // que posteriormente se normalizará a un Nodo de algún tipo.
    if (tipo de valor === 'función') {
      valor = valor();
    }

    if (isEl(valor) || isTextNode(valor)) {
      valor de retorno;
    }

    if (tipo de valor === 'cadena' && (/\S/).prueba(valor)) {
      volver document.createTextNode(valor);
    }
  }).filtro(valor => valor);
}

/ **
 * Normaliza y agrega contenido a un elemento.
 *
 * @param {Elemento} el
 * Elemento para agregar contenido normalizado.
 *
 * @param {módulo:dom~ContentDescriptor} contenido
 * Un valor descriptor de contenido.
 *
 * @return {Elemento}
 * El elemento con contenido normalizado adjunto.
 * /
función de exportación appendContent(el, contenido) {
  normalizeContent(contenido).forEach(nodo => el.appendChild(nodo));
  volver el;
}

/ **
 * Normaliza e inserta contenido en un elemento; esto es identico a
 * `appendContent()`, excepto que primero vacía el elemento.
 *
 * @param {Elemento} el
 * Elemento para insertar contenido normalizado.
 *
 * @param {módulo:dom~ContentDescriptor} contenido
 * Un valor descriptor de contenido.
 *
 * @return {Elemento}
 * El elemento con contenido normalizado insertado.
 * /
exportar función insertarContenido(el, contenido) {
  return appendContent(emptyEl(el), contenido);
}

/ **
 * Compruebe si un evento fue un solo clic izquierdo.
 *
 * @param {EventTarget~Evento} evento
 * Objeto de evento.
 *
 * @return {booleano}
 * Será `verdadero` si se hace un solo clic con el botón izquierdo, `falso` en caso contrario.
 * /
función de exportación isSingleLeftClick(evento) {
  // Nota: si crea algo arrastrable, asegúrese de
  // invocarlo tanto en el evento `mousedown` como `mousemove`,
  // de lo contrario, `mousedown` debería ser suficiente para un botón

  if (event.button === indefinido && event.buttons === indefinido) {
    // ¿Por qué necesitamos `botones`?
    // Porque el ratón del medio a veces tiene esto:
    // e.button === 0 y e.buttons === 4
    // Además, queremos evitar la combinación de clics, algo así como
    // MANTENGA presionado el botón central y luego haga clic con el botón izquierdo, eso sería
    // boton electronico === 0, boton electronico === 5
    // simplemente `botón` no va a funcionar

    // Muy bien, entonces, ¿qué hace este bloque?
    // esto es para Chrome `simular dispositivos móviles`
    // Yo también quiero apoyar esto

    devolver verdadero;
  }

  if (evento.boton === 0 && event.buttons === indefinido) {
    // Pantalla táctil, a veces en algún dispositivo específico, `botones`
    // no tiene nada (safari en ios, blackberry...)

    devolver verdadero;
  }

  // El evento `mouseup` con un solo clic izquierdo tiene
  // `botón` y `botones` igual a 0
  if (evento.tipo === 'mouseup' && evento.boton === 0 &&
      evento.botones === 0) {
    devolver verdadero;
  }

  if (evento.botón !== 0 || evento.botones !== 1) {
    // Esta es la razón por la que tenemos el bloque if else arriba
    // si hay algún caso especial, podemos atraparlo y dejarlo pasar
    // lo hacemos arriba, cuando lleguemos aquí, esto definitivamente
    // no es-clic-izquierdo

    falso retorno;
  }

  devolver verdadero;
}

/ **
 * Encuentra un solo elemento DOM que coincida con `selector` dentro del opcional
 * `contexto` de otro elemento DOM (predeterminado a `document`).
 *
 * Selector @param {cadena}
 * Un selector de CSS válido, que se pasará a `querySelector`.
 *
 * @param {Elemento|Cadena} [contexto=documento]
 * Un elemento DOM dentro del cual consultar. También puede ser un selector
 * cadena en cuyo caso se utilizará el primer elemento coincidente
 * como contexto. Si falta (o ningún elemento coincide con el selector), cae
 * volver a `documento`.
 *
 * @return {Elemento|null}
 * El elemento que se encontró o nulo.
 * /
export const $ = createQuerier('querySelector');

/ **
 * Encuentra todos los elementos DOM que coincidan con `selector` dentro del opcional
 * `contexto` de otro elemento DOM (predeterminado a `document`).
 *
 * Selector @param {cadena}
 * Un selector de CSS válido, que se pasará a `querySelectorAll`.
 *
 * @param {Elemento|Cadena} [contexto=documento]
 * Un elemento DOM dentro del cual consultar. También puede ser un selector
 * cadena en cuyo caso se utilizará el primer elemento coincidente
 * como contexto. Si falta (o ningún elemento coincide con el selector), cae
 * volver a `documento`.
 *
 * @return {Lista de nodos}
 * Una lista de elementos de los elementos que se encontraron. Estará vacío si ninguno
 * fueron encontrados.
 *
 * /
export const $$ = createQuerier('querySelectorAll');