Componentes de HowTo: Casilla de verificación para instructivos

Resumen

Un <howto-checkbox> representa una opción booleana de un formulario. El tipo de casilla de verificación más común es un tipo doble que le permite al usuario alternar entre dos opciones: marcada y desmarcada.

El elemento intenta aplicar automáticamente los atributos role="checkbox" y tabindex="0" cuando se crea por primera vez. El atributo role ayuda a la tecnología de accesibilidad, como un lector de pantalla, a indicarle al usuario qué tipo de control es. El atributo tabindex habilita el elemento en el orden de tabulación, lo que permite que el teclado pueda enfocarse y operar. Para obtener más información sobre estos dos temas, consulta ¿Qué puede hacer ARIA? y Cómo usar tabindex.

Cuando la casilla de verificación está marcada, se agrega un atributo booleano checked y se establece una propiedad checked correspondiente en true. Además, el elemento establece un atributo aria-checked en "true" o "false", según su estado. Si haces clic en la casilla de verificación con un mouse o la barra espaciadora, se activan o desactivan los estados marcados.

La casilla de verificación también admite un estado disabled. Si se configura la propiedad disabled como verdadera o se aplica el atributo disabled, la casilla de verificación configura aria-disabled="true", quita el atributo tabindex y muestra el foco al documento si la casilla de verificación es la activeElement actual.

La casilla de verificación está vinculada con un elemento howto-label para garantizar que tenga un nombre accesible.

Reference

Demostración

Ver la demostración en vivo en GitHub

Ejemplo de uso

<style>
  howto-checkbox {
    vertical-align: middle;
  }
  howto-label {
    vertical-align: middle;
    display: inline-block;
    font-weight: bold;
    font-family: sans-serif;
    font-size: 20px;
    margin-left: 8px;
  }
</style>

<howto-checkbox id="join-checkbox"></howto-checkbox>
<howto-label for="join-checkbox">Join Newsletter</howto-label>

Escribe código

(function() {

Define códigos de tecla para ayudar a controlar los eventos del teclado.

  const KEYCODE = {
    SPACE: 32,
  };

La clonación de contenido de un elemento <template> tiene un mejor rendimiento que usar internalHTML, ya que evita los costos adicionales de análisis de HTML.

  const template = document.createElement('template');

  template.innerHTML = `
    <style>
      :host {
        display: inline-block;
        background: url('../images/unchecked-checkbox.svg') no-repeat;
        background-size: contain;
        width: 24px;
        height: 24px;
      }
      :host([hidden]) {
        display: none;
      }
      :host([checked]) {
        background: url('../images/checked-checkbox.svg') no-repeat;
        background-size: contain;
      }
      :host([disabled]) {
        background:
          url('../images/unchecked-checkbox-disabled.svg') no-repeat;
        background-size: contain;
      }
      :host([checked][disabled]) {
        background:
          url('../images/checked-checkbox-disabled.svg') no-repeat;
        background-size: contain;
      }
    </style>
  `;


  class HowToCheckbox extends HTMLElement {
    static get observedAttributes() {
      return ['checked', 'disabled'];
    }

El constructor del elemento se ejecuta cada vez que se crea una instancia nueva. Para crear instancias, se debe analizar HTML, llamar a document.createElement('howto-checkbox') o llamar al nuevo HowToCheckbox(). El constructor es un buen lugar para crear shadow DOM, aunque debes evitar tocar atributos o elementos secundarios del Light DOM, ya que es posible que aún no estén disponibles.

    constructor() {
      super();
      this.attachShadow({mode: 'open'});
      this.shadowRoot.appendChild(template.content.cloneNode(true));
    }

connectedCallback() se activa cuando el elemento se inserta en el DOM. Es un buen lugar para establecer los objetos de escucha iniciales de eventos de instalación, role, tabindex y el estado interno.

    connectedCallback() {
      if (!this.hasAttribute('role'))
        this.setAttribute('role', 'checkbox');
      if (!this.hasAttribute('tabindex'))
        this.setAttribute('tabindex', 0);

Un usuario puede configurar una propiedad en una instancia de un elemento, antes de que su prototipo se conecte a esta clase. El método _upgradeProperty() verificará las propiedades de la instancia y las ejecutará a través de los métodos set de clase adecuados. Consulta la sección de propiedades diferidas para obtener más detalles.

      this._upgradeProperty('checked');
      this._upgradeProperty('disabled');

      this.addEventListener('keyup', this._onKeyUp);
      this.addEventListener('click', this._onClick);
    }

    _upgradeProperty(prop) {
      if (this.hasOwnProperty(prop)) {
        let value = this[prop];
        delete this[prop];
        this[prop] = value;
      }
    }

disconnectedCallback() se activa cuando se quita el elemento del DOM. Es un buen lugar para realizar tareas de limpieza, como liberar referencias y quitar objetos de escucha de eventos.

    disconnectedCallback() {
      this.removeEventListener('keyup', this._onKeyUp);
      this.removeEventListener('click', this._onClick);
    }

Las propiedades y sus atributos correspondientes deben reflejarse entre sí. El método set de propiedades para la verificación controla los valores veraces o falsos, y los refleja en el estado del atributo. Para obtener más información, consulta la sección sobre cómo evitar reentrabilidad.

    set checked(value) {
      const isChecked = Boolean(value);
      if (isChecked)
        this.setAttribute('checked', '');
      else
        this.removeAttribute('checked');
    }

    get checked() {
      return this.hasAttribute('checked');
    }

    set disabled(value) {
      const isDisabled = Boolean(value);
      if (isDisabled)
        this.setAttribute('disabled', '');
      else
        this.removeAttribute('disabled');
    }

    get disabled() {
      return this.hasAttribute('disabled');
    }

Se llama a attributeChangedCallback() cuando se cambia cualquiera de los atributos del arreglo observado. Es un buen lugar para controlar efectos secundarios, como configurar atributos ARIA.

    attributeChangedCallback(name, oldValue, newValue) {
      const hasValue = newValue !== null;
      switch (name) {
        case 'checked':
          this.setAttribute('aria-checked', hasValue);
          break;
        case 'disabled':
          this.setAttribute('aria-disabled', hasValue);

El atributo tabindex no proporciona una forma de quitar por completo la capacidad de enfoque de un elemento. Aún puedes enfocar los elementos con tabindex=-1 con un mouse o llamando a focus(). Para asegurarte de que un elemento esté inhabilitado y no se pueda enfocar, quita el atributo tabindex.

          if (hasValue) {
            this.removeAttribute('tabindex');

Si el enfoque está actualmente en este elemento, desenfócalo llamando al método HTMLElement.blur().

            this.blur();
          } else {
            this.setAttribute('tabindex', '0');
          }
          break;
      }
    }

    _onKeyUp(event) {

No uses las combinaciones de teclas de modificador que usa normalmente la tecnología de accesibilidad.

      if (event.altKey)
        return;

      switch (event.keyCode) {
        case KEYCODE.SPACE:
          event.preventDefault();
          this._toggleChecked();
          break;

Se ignorará cualquier otra presión de teclas y se pasará al navegador.

        default:
          return;
      }
    }

    _onClick(event) {
      this._toggleChecked();
    }

_toggleChecked() llama al método set verificado y cambia su estado. Dado que _toggleChecked() solo se debe a una acción del usuario, también enviará un evento de cambio. Este evento aparece en una burbuja para imitar el comportamiento nativo de <input type=checkbox>.

    _toggleChecked() {
      if (this.disabled)
        return;
      this.checked = !this.checked;
      this.dispatchEvent(new CustomEvent('change', {
        detail: {
          checked: this.checked,
        },
        bubbles: true,
      }));
    }
  }

  customElements.define('howto-checkbox', HowToCheckbox);
})();