Resumo
Um <howto-checkbox>
representa uma opção booleana em um formulário. O tipo mais comum
de caixa de seleção é um 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
auxiliar, como um leitor de tela, a informar ao usuário que tipo de controle é esse.
O atributo tabindex
opta pelo elemento na ordem de guias, tornando-o focado e operável
pelo teclado. Para saber mais sobre esses dois tópicos, consulte
O que a ARIA pode fazer? e Usar tabindex.
Quando a caixa de seleção está marcada, ela adiciona um atributo booleano checked
e define
uma propriedade checked
correspondente como true
. Além disso, o elemento define um
atributo aria-checked
como "true"
ou "false"
, dependendo do
estado. 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 definirá
aria-disabled="true"
, removerá o atributo tabindex
e retornará o foco
ao documento se a caixa de seleção for a activeElement
atual.
A caixa de seleção é associada a um elemento howto-label
para garantir que ela tenha um
nome acessível.
Referência
- HowTo: Componentes no GitHub
- Padrão de caixa de seleção nas práticas de criação de ARIA 1.1
- O que a ARIA pode fazer?
- Como usar o tabindex
Demonstração
Conferir 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() {
Define códigos de tecla para ajudar a processar eventos de teclado.
const KEYCODE = {
SPACE: 32,
};
Clonar conteúdos de um elemento <template>
tem melhor desempenho do que usar innerHTML porque evita custos de análise de HTML adicionais.
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 analisando HTML, chamando document.createElement('howto-checkbox') ou chamando new HowToCheckbox(); O construtor é um bom lugar para criar shadow DOM, mas evite tocar em atributos ou filhos do DOM leve, porque eles podem não estar disponíveis ainda.
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback()
é acionado quando o elemento é inserido no DOM. É um bom lugar para definir o role
inicial, o tabindex
, o estado interno e instalar listeners de eventos.
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()
vai verificar se há propriedades de instância e executá-las nos setters de classe adequados. Consulte a seção Propriedades lazy para 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()
é acionado quando o elemento é removido do DOM. É um bom lugar para fazer a 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 ser espelhados. O setter de propriedade para "checked" lida com valores verdadeiros/falsos e os reflete no estado do atributo. Consulte a seção evitar reentrada para mais detalhes.
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()
é chamado quando qualquer um dos atributos na matriz observedAttributes é alterado. É um bom lugar para lidar com efeitos colaterais, como definir 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 oferece uma maneira de remover totalmente a capacidade de foco de um elemento. Os elementos com tabindex=-1
ainda podem ser focados com um 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, remova-o chamando o método HTMLElement.blur()
.
this.blur();
} else {
this.setAttribute('tabindex', '0');
}
break;
}
}
_onKeyUp(event) {
Não processa atalhos de modificador normalmente usados por tecnologia adaptativa.
if (event.altKey)
return;
switch (event.keyCode) {
case KEYCODE.SPACE:
event.preventDefault();
this._toggleChecked();
break;
Qualquer outra tecla pressionada é ignorada e transmitida de volta ao navegador.
default:
return;
}
}
_onClick(event) {
this._toggleChecked();
}
_toggleChecked()
chama o setter de verificado e inverte o estado dele. Como _toggleChecked()
é causado apenas por uma ação do usuário, ele também envia um evento de mudança. Esse evento é transmitido 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);
})();