Resumo
Um <howto-checkbox>
representa uma opção booleana em um formulário. O tipo mais comum
de caixa de seleção é o de tipo duplo, que permite ao usuário alternar entre duas
opções: marcada e desmarcada.
O elemento tenta aplicar os atributos role="checkbox"
e
tabindex="0"
quando é criado pela primeira vez. O atributo role
ajuda a tecnologia adaptativa,
como um leitor de tela, a informar ao usuário que tipo de controle é esse.
O atributo tabindex
ativa o elemento na ordem de tabulação, tornando-o focalizável e operável
pelo teclado. Para saber mais sobre esses dois tópicos, confira
O que ARIA pode fazer? e Como usar tabindex.
Quando a caixa de seleção é marcada, um atributo booleano checked
é adicionado e
uma propriedade checked
correspondente é definida como true
. Além disso, o elemento define um
atributo aria-checked
como "true"
ou "false"
, dependendo do
estado dele. Clicar na caixa de seleção com o mouse ou a barra de espaço alterna esses
estados marcados.
A caixa de seleção também oferece suporte a um estado disabled
. Se a propriedade disabled
for definida como verdadeira ou o atributo disabled
for aplicado, a caixa de seleção vai definir
aria-disabled="true"
, remover o atributo tabindex
e retornar o foco
para o documento, se a caixa de seleção for o activeElement
atual.
A caixa de seleção é pareada com um elemento howto-label
para garantir que ela tenha um
nome acessível.
Referência
- Instruções: componentes no GitHub (em inglês)
- Padrão de caixa de seleção em práticas de criação ARIA 1.1 (link em inglês)
- O que ARIA pode fazer?
- Como usar o tabindex
Demonstração
Confira a demonstração ao vivo no GitHub
Exemplo 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() {
Defina códigos de tecla para ajudar a lidar com eventos de teclado.
const KEYCODE = {
SPACE: 32,
};
A clonagem de conteúdo de um elemento <template>
tem melhor desempenho do que o uso de innerHTML, porque evita custos extras de análise 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'];
}
O construtor do elemento é executado sempre que uma nova instância é criada. As instâncias são criadas pela análise de HTML, pela chamada de document.createElement('howto-checkbox') ou pela chamada de novo HowToCheckbox() O construtor é um bom lugar para criar o shadow DOM, embora você deva evitar tocar em atributos ou filhos do light DOM, pois eles podem ainda não estar disponíveis.
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback()
é disparado quando o elemento é inserido no DOM. É recomendável definir o role
inicial, o tabindex
, o estado interno e os listeners de eventos de instalação.
connectedCallback() {
if (!this.hasAttribute('role'))
this.setAttribute('role', 'checkbox');
if (!this.hasAttribute('tabindex'))
this.setAttribute('tabindex', 0);
Um usuário pode definir uma propriedade em uma instância de um elemento antes que o protótipo seja conectado a essa classe. O método _upgradeProperty()
verifica se há propriedades da instância e as executa pelos setters de classe adequados. Consulte a seção de propriedades lentas para ver mais detalhes.
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()
é disparado quando o elemento é removido do DOM. É um bom lugar para fazer trabalhos de limpeza, como liberar referências e remover listeners de eventos.
disconnectedCallback() {
this.removeEventListener('keyup', this._onKeyUp);
this.removeEventListener('click', this._onClick);
}
As propriedades e os atributos correspondentes precisam se espelhar. O setter de propriedades para o verificado processa valores true/falsy e os reflete no estado do atributo. Consulte a seção Evitar nova entrada para saber mais.
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');
}
O attributeChangedCallback()
é chamado quando qualquer um dos atributos na matriz observadoAttributes é modificado. É um bom lugar para lidar com efeitos colaterais, como a definição de 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);
O atributo tabindex
não fornece uma maneira de remover completamente a capacidade de foco de um elemento. Elementos com tabindex=-1
ainda podem ser focados com o mouse ou chamando focus()
. Para garantir que um elemento esteja desativado e não seja focalizável, remova o atributo tabindex
.
if (hasValue) {
this.removeAttribute('tabindex');
Se o foco estiver nesse elemento no momento, remova o foco chamando o método HTMLElement.blur()
this.blur();
} else {
this.setAttribute('tabindex', '0');
}
break;
}
}
_onKeyUp(event) {
Não processe atalhos modificadores geralmente usados por tecnologia adaptativa.
if (event.altKey)
return;
switch (event.keyCode) {
case KEYCODE.SPACE:
event.preventDefault();
this._toggleChecked();
break;
Qualquer outro pressionamento de tecla é ignorado e transmitido de volta ao navegador.
default:
return;
}
}
_onClick(event) {
this._toggleChecked();
}
_toggleChecked()
chama o setter marcado e inverte o estado dele. Como _toggleChecked()
é causado apenas por uma ação do usuário, ele também enviará um evento de alteração. Esse evento aparece em uma bolhas para imitar o comportamento 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);
})();