Componentes de HowTo: Casilla de verificación para instructivos

Resumen

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

El elemento intenta aplicar automáticamente los atributos role="checkbox" y tabindex="0" cuando se crea por primera vez. El atributo role ayuda a asistivar como un lector de pantalla, le dice al usuario qué tipo de control es. El atributo tabindex habilita el elemento en el orden de tabulación, lo que lo convierte en teclado enfocable y operable. Para obtener más información sobre estos dos temas, consulta ¿Qué puede hacer ARIA? y Usar tabindex.

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

La casilla de verificación también admite un estado disabled. Si la propiedad disabled si se configura como verdadero o se aplica el atributo disabled, la casilla de verificación establece 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 se vincula con un elemento howto-label para garantizar que tenga un nombre accesible.

Referencia

Demostración

Mira una 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>

Código

(function() {

Define códigos de teclas 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 el uso de 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. Las instancias se crean analizando HTML, llamando a document.createElement('howto-checkbox') o llamando 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 se inserta el elemento en el DOM. Es un buen lugar para establecer los objetos de escucha de eventos de instalación role y tabindex iniciales 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 trabajos 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 establecedor de propiedades para los valores marcados controla los valores veraces o falsos, y los refleja en el estado del atributo. Consulta la sección evitar reingresiones para obtener más detalles.

    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 array observableAttributes. 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. Los elementos con tabindex=-1 pueden enfocarse con el mouse o llamando a focus(). Para asegurarte de que un elemento esté inhabilitado y no enfocable, quita el atributo tabindex.

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

Si el enfoque está actualmente en este elemento, para quitar el foco, llama al método HTMLElement.blur().

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

    _onKeyUp(event) {

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

      if (event.altKey)
        return;

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

Cualquier otra presión de teclas se ignora y se envía de vuelta al navegador.

        default:
          return;
      }
    }

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

_toggleChecked() llama al método set marcado y cambia su estado. Como _toggleChecked() solo se debe a una acción del usuario, también enviará un evento de cambio. Este evento aparece como burbujas 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);
})();