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