HowTo Components – практический флажок

<howto-checkbox> представляет собой логический параметр в форме. Наиболее распространенным типом флажка является двойной тип, который позволяет пользователю переключаться между двумя вариантами выбора: отмеченным и снятым.

Элемент пытается самостоятельно применить атрибуты role="checkbox" и tabindex="0" при первом создании. Атрибут role помогает вспомогательным технологиям, таким как программа чтения с экрана, сообщать пользователю, что это за элемент управления. Атрибут tabindex включает элемент в порядок табуляции, что делает его доступным для фокусировки и управления с клавиатуры. Чтобы узнать больше об этих двух темах, ознакомьтесь со статьей Что может сделать ARIA? и Использование tabindex .

Когда флажок установлен, он добавляет логический атрибут checked и присваивает соответствующему свойству checked значение true . Кроме того, элемент устанавливает для атрибута aria-checked значение "true" или "false" в зависимости от его состояния. Нажатие на флажок с помощью мыши или клавиши пробела переключает эти отмеченные состояния.

Флажок также поддерживает disabled состояние. Если для свойства disabled установлено значение true или применен атрибут disabled , флажок устанавливает aria-disabled="true" , ​​удаляет атрибут tabindex и возвращает фокус документу, если флажок является текущим activeElement .

Флажок связан с элементом howto-label чтобы обеспечить доступное имя .

Ссылка

Демо

Посмотреть живую демонстрацию на GitHub

Пример использования

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

Код

(function() {

Определите коды клавиш, которые помогут обрабатывать события клавиатуры.

  const KEYCODE = {
    SPACE: 32,
  };

Клонирование содержимого из элемента <template> более эффективно, чем использование InnerHTML, поскольку позволяет избежать дополнительных затрат на анализ 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'];
    }

Конструктор элемента запускается каждый раз при создании нового экземпляра. Экземпляры создаются либо путем анализа HTML, вызова document.createElement('howto-checkbox') или вызова new HowToCheckbox(); Конструктор — хорошее место для создания теневого DOM, однако вам следует избегать касания каких-либо атрибутов или дочерних элементов светлого DOM, поскольку они могут быть еще недоступны.

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

connectedCallback() срабатывает, когда элемент вставляется в DOM. Это хорошее место для установки начальной role , tabindex , внутреннего состояния и установки прослушивателей событий.

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

Пользователь может установить свойство экземпляра элемента до того, как его прототип будет подключен к этому классу. Метод _upgradeProperty() проверит наличие всех свойств экземпляра и пропустит их через соответствующие установщики классов. Дополнительную информацию см. в разделе ленивых свойств .

      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() срабатывает, когда элемент удаляется из DOM. Это хорошее место для очистки, например освобождения ссылок и удаления прослушивателей событий.

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

Свойства и соответствующие им атрибуты должны отражать друг друга. Установщик свойства для проверенного обрабатывает значения правдивости/ложности и отображает их в состоянии атрибута. Дополнительные сведения см. в разделе «Избежание повторного входа» .

    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');
    }

attributeChangedCallback() вызывается при изменении любого из атрибутов в массиве наблюдаемыхАтрибутов. Это хорошее место для обработки побочных эффектов, таких как установка атрибутов 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);

Атрибут tabindex не позволяет полностью отключить возможность фокусировки элемента. Элементы с tabindex=-1 по-прежнему можно фокусировать с помощью мыши или путем вызова focus() . Чтобы убедиться, что элемент отключен и не доступен для фокусировки, удалите атрибут tabindex .

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

Если фокус в данный момент находится на этом элементе, снимите с него фокус, вызвав метод HTMLElement.blur()

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

    _onKeyUp(event) {

Не обрабатывайте сочетания клавиш-модификаторов, которые обычно используются вспомогательными технологиями.

      if (event.altKey)
        return;

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

Любое другое нажатие клавиши игнорируется и передается обратно в браузер.

        default:
          return;
      }
    }

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

_toggleChecked() вызывает проверенный установщик и меняет его состояние. Поскольку _toggleChecked() вызывается только действием пользователя, он также отправляет событие изменения. Это событие всплывает, чтобы имитировать собственное поведение <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);
})();