O sistema de foco acompanha a localização (foco) do usuário em um editor do Blockly. Ele é usado pelo Blockly e por código personalizado para determinar qual componente (bloco, campo, categoria da caixa de ferramentas etc.) está com o foco e para mover esse foco para outro componente.
É importante entender o sistema de foco para garantir que seu código personalizado funcione corretamente com ele.
Arquitetura
O sistema de foco tem três partes:
O
FocusManager
é um singleton que coordena o foco em todo o Blockly. Usado pelo Blockly e por código personalizado para descobrir qual componente tem o foco do Blockly, além de mover o foco do Blockly para um componente diferente. Ele também detecta eventos de foco do DOM, sincroniza o foco do Blockly e do DOM e gerencia as classes CSS que indicam qual componente está em foco.O gerenciador de foco é usado principalmente pelo Blockly. Às vezes, ele é usado por código personalizado para interagir com o sistema de foco.
Um
IFocusableTree
é uma área independente de um editor do Blockly, como um espaço de trabalho ou uma caixa de ferramentas. Ele é composto por nós focalizáveis, como blocos e campos. As árvores também podem ter subárvores. Por exemplo, um espaço de trabalho mutator em um bloco no espaço de trabalho principal é uma subárvore do espaço de trabalho principal.IFocusableTree
é usado principalmente pelo gerenciador de foco. A menos que você crie uma caixa de ferramentas personalizada, provavelmente não será necessário implementá-la.Um
IFocusableNode
é um componente do Blockly que pode ter foco, como um bloco, campo ou categoria da caixa de ferramentas. Os nós focalizáveis têm um elemento DOM que mostra o nó e que tem foco DOM quando o nó tem foco do Blockly. As árvores também são nós focalizáveis. Por exemplo, você pode se concentrar no espaço de trabalho como um todo.Os métodos em
IFocusableNode
são chamados principalmente pelo gerenciador de foco.O próprio
IFocusableNode
é usado para representar o componente que tem foco. Por exemplo, quando um usuário seleciona um item no menu de contexto de um bloco, o bloco é transmitido para a função de callback do item como umIFocusableNode
.Se você escrever componentes personalizados, talvez seja necessário implementar
IFocusableNode
.
Tipos de foco
O sistema de foco define vários tipos diferentes de foco.
Foco do Blockly e foco do DOM
Os dois tipos principais de foco são o do Blockly e o do DOM.
O foco do Blockly especifica qual componente do Blockly (bloco, campo, categoria da caixa de ferramentas etc.) está em foco. É necessário para trabalhar no nível dos componentes do Blockly. Por exemplo, o plug-in de navegação pelo teclado permite que os usuários usem as teclas de seta para passar de um componente para outro, como de um bloco para um campo. Da mesma forma, o sistema de menu de contexto cria um menu adequado para o componente atual. Ou seja, ele cria menus diferentes para espaços de trabalho, blocos e comentários do espaço de trabalho.
O foco do DOM especifica qual elemento do DOM está em foco. É necessário para trabalhar no nível dos elementos DOM. Por exemplo, os leitores de tela apresentam informações sobre o elemento que tem o foco do DOM no momento, e as guias se movem (mudam o foco) de um elemento DOM para outro.
O gerenciador de foco mantém o foco do Blockly e do DOM sincronizados. Assim, quando um nó (componente do Blockly) tem o foco do Blockly, o elemento DOM subjacente tem o foco do DOM e vice-versa.
Foco ativo e passivo
O foco do Blockly é dividido em foco ativo e foco passivo. O foco ativo significa que um nó vai receber a entrada do usuário, como o pressionamento de uma tecla. O foco passivo significa que um nó tinha foco ativo, mas o perdeu quando o usuário passou para um nó em outra árvore (por exemplo, do espaço de trabalho para a caixa de ferramentas) ou saiu do editor do Blockly. Se a árvore recuperar o foco, o nó com foco passivo vai recuperar o foco ativo.
Cada árvore tem um contexto de foco separado. Ou seja, no máximo um nó na árvore pode ter foco. Se esse foco é ativo ou passivo depende de se a árvore tem foco. Pode haver no máximo um nó com foco ativo em toda a página.
O gerenciador de foco usa diferentes destaques (classes CSS) para nós ativos e passivamente focados. Assim, os usuários entendem onde estão e para onde vão voltar.
Foco efêmero
Há outro tipo de foco chamado foco efêmero. Fluxos de trabalho separados, como caixas de diálogo ou editores de campo, solicitam foco efêmero do gerenciador de foco. Quando o gerenciador de foco concede foco efêmero, ele suspende o sistema de foco. Na prática, isso significa que esses fluxos de trabalho podem capturar e agir em eventos de foco do DOM sem se preocupar com a possibilidade de o sistema de foco também agir neles.
Quando o gerenciador de foco concede foco efêmero, ele muda o nó ativamente focado para foco passivo. Ele restaura o foco ativo quando o foco temporário é retornado.
Exemplos
Os exemplos a seguir ilustram como o Blockly usa o sistema de foco. Elas ajudam a entender como seu código se encaixa no sistema de foco e como ele pode usar esse sistema.
Mover o foco com o teclado
Suponha que um bloco com dois campos tenha o foco do Blockly, conforme indicado por um destaque (classe CSS) no elemento DOM do bloco. Agora suponha que o usuário pressione a seta para a direita:
- O plug-in de navegação por teclado:
- Recebe um evento de pressionamento de tecla.
- Pede ao sistema de navegação (parte do Blockly principal) para mover o foco para o componente "próximo".
- O sistema de navegação:
- Pergunta ao gerenciador de foco qual componente tem o foco do Blockly. O gerenciador de foco retorna o bloco como um
IFocusableNode
. - Determina que o
IFocusableNode
é umBlockSvg
e analisa as regras dele para navegar pelos blocos, que afirmam que ele deve mover o foco do Blockly do bloco como um todo para o primeiro campo do bloco. - Diz ao gerenciador de foco para mover o foco do Blockly para o primeiro campo.
- Pergunta ao gerenciador de foco qual componente tem o foco do Blockly. O gerenciador de foco retorna o bloco como um
- O gerenciador de foco:
- Atualiza o estado para definir o foco do Blockly no primeiro campo.
- Define o foco do DOM no elemento DOM do campo.
- Move a classe de destaque do elemento do bloco para o elemento do campo.
Mover o foco com o mouse
Agora suponha que o usuário clique no segundo campo do bloco. O gerenciador de foco:
- Recebe um evento DOM
focusout
no elemento DOM do primeiro campo e um eventofocusin
no elemento DOM do segundo campo. - Determina que o elemento DOM que recebeu o foco corresponde ao segundo campo.
- Atualiza o estado para definir o foco do Blockly no segundo campo. O gerenciador de foco não precisa definir o foco do DOM porque o navegador já fez isso.
- Move a classe de destaque do elemento do primeiro campo para o elemento do segundo campo.
Outros exemplos
Veja outros exemplos:
Quando um usuário arrasta um bloco da caixa de ferramentas para o espaço de trabalho, o gerenciador de eventos do mouse cria um novo bloco e chama o gerenciador de foco para definir o foco do Blockly nesse bloco.
Quando um bloco é excluído, o método
dispose
chama o gerenciador de foco para mover o foco para o elemento pai do bloco.Os atalhos do teclado usam
IFocusableNode
para identificar o componente do Blockly a que o atalho se aplica.Os menus de contexto usam
IFocusableNode
para identificar o componente do Blockly em que o menu foi invocado.
Personalizações e o sistema de foco
Ao personalizar o Blockly, verifique se o código funciona corretamente com o sistema de foco. Você também pode usar o sistema de foco para identificar e definir o nó atualmente em foco.
Blocos personalizados e conteúdo da caixa de ferramentas
A maneira mais comum de personalizar o Blockly é definir blocos personalizados e personalizar o conteúdo da caixa de ferramentas. Nenhuma dessas ações afeta o sistema de foco.
Classes personalizadas
As classes personalizadas podem precisar implementar uma ou ambas as interfaces de foco
(IFocusableTree
e IFocusableNode
). Nem sempre é óbvio quando esse é
o caso.
Algumas classes precisam implementar interfaces de foco. São eles:
Uma classe que implementa uma caixa de ferramentas personalizada. Essa classe precisa implementar
IFocusableTree
eIFocusableNode
.Classes que criam um componente visível (como um campo ou ícone) que os usuários podem acessar. Essas classes precisam implementar
IFocusableNode
.
Algumas classes precisam implementar IFocusableNode
mesmo que não criem um
componente visível ou criem um componente visível que os usuários não podem navegar
para. São eles:
Classes que implementam uma interface que estende
IFocusableNode
.Por exemplo, o ícone de mover no plug-in de navegação pelo teclado mostra uma seta de quatro vias que indica que o bloco pode ser movido com as teclas de seta. O ícone não fica visível (a seta de quatro vias é uma bolha), e os usuários não podem navegar até ele. No entanto, o ícone precisa implementar
IFocusableNode
porque os ícones implementamIIcon
eIIcon
estendeIFocusableNode
.Classes usadas em uma API que exige um
IFocusableNode
.Por exemplo, a classe
FlyoutSeparator
cria uma lacuna entre dois itens em um menu flutuante. Ele não cria elementos DOM, então não tem um componente visível e os usuários não podem navegar até ele. No entanto, ele precisa implementarIFocusableNode
porque é armazenado em umFlyoutItem
, e o construtorFlyoutItem
exige umIFocusableNode
.Classes que estendem uma classe que implementa
IFocusableNode
.Por exemplo,
ToolboxSeparator
estendeToolboxItem
, que implementaIFocusableNode
. Embora os separadores da caixa de ferramentas tenham um componente visível, os usuários não podem navegar até eles porque não podem ser usados e não têm conteúdo útil.
Outras classes criam componentes visíveis que o usuário pode acessar, mas não precisam implementar IFocusableNode
. São eles:
- Classes que criam um componente visível que gerencia o próprio foco, como
um editor de campo ou uma caixa de diálogo. Essas classes precisam assumir o foco
efêmero quando são iniciadas e devolvê-lo quando são
encerradas. Usar
WidgetDiv
ouDropDownDiv
vai resolver isso para você.)
Por fim, algumas classes não interagem com o sistema de foco e não precisam implementar IFocusableTree
ou IFocusableNode
. São eles:
Classes que criam um componente visível que os usuários não podem navegar ou interagir e que não contém informações que um leitor de tela possa usar. Por exemplo, um plano de fundo puramente decorativo em um jogo.
Classes totalmente não relacionadas ao sistema de foco, como classes que implementam
IMetricsManager
ouIVariableMap
.
Se você não tiver certeza se sua classe vai interagir com o sistema de foco,
teste-a com o plug-in de navegação por teclado. Se isso falhar, talvez seja necessário
implementar IFocusableTree
ou IFocusableNode
. Se a operação for bem-sucedida, mas você ainda tiver dúvidas, leia o código que usa sua classe para ver se alguma interface é necessária ou se há outras interações.
Implementar interfaces de foco
A maneira mais fácil de implementar IFocusableTree
ou IFocusableNode
é estender uma
classe que implementa essas interfaces. Por exemplo, se você estiver criando uma caixa de ferramentas
personalizada, estenda Toolbox
, que implementa IFocusableTree
e
IFocusableNode
. Se você estiver criando um campo personalizado, estenda Field
, que
implementa IFocusableNode
. Verifique se o código não interfere
com o código da interface de foco na classe base.
Se você estender uma classe que implementa uma interface de foco, geralmente não precisará substituir nenhum método. A exceção mais comum é IFocusableNode.canBeFocused
, que precisa ser substituída se você não quiser que os usuários naveguem até o componente.
É menos comum precisar substituir os métodos de callback de foco (onTreeFocus
e onTreeBlur
em IFocusableTree
e onNodeFocus
e onNodeBlur
em
IFocusableNode
). Tentar mudar o foco (chamar
FocusManager.focusNode
ou FocusManager.focusTree
) nesses métodos resulta
em uma exceção.
Se você escrever um componente personalizado do zero, precisará implementar as interfaces de foco por conta própria. Consulte a documentação de referência para
IFocusableTree
e
IFocusableNode
para mais
informações.
Depois de implementar a classe, teste-a com o plug-in de navegação por teclado para verificar se é possível (ou não) navegar até o componente.
Usar o gerenciador de foco
Algumas classes personalizadas usam o gerenciador de foco. Os motivos mais comuns para isso
são receber o nó atualmente em foco e focar em um nó diferente. Para receber o gerenciador de foco, chame Blockly.getFocusManager
:
const focusManager = Blockly.getFocusManager();
Para receber o nó em foco no momento, chame getFocusedNode
:
const focusedNode = focusManager.getFocusedNode();
// Do something with the focused node.
Para mover o foco para um nó diferente, chame focusNode
:
// Move focus to a different block.
focusManager.focusNode(myOtherBlock);
Para mover o foco para uma árvore, chame focusTree
. Isso também define o foco do nó no nó raiz da árvore.
// Move focus to the main workspace.
focusManager.focusTree(myMainWorkspace);
Outro motivo comum para usar o gerenciador de foco é capturar e retornar o foco
temporário. A função takeEphemeralFocus
retorna uma lambda que você precisa chamar para
retornar o foco efêmero.
const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();
Se você usar
WidgetDiv
ou
DropDownDiv
,
eles vão processar o foco temporário para você.
Tabulações
O sistema de foco define uma parada de tabulação (tabindex
de 0
) no elemento raiz de todas as árvores (o espaço de trabalho principal, a caixa de ferramentas e os espaços de trabalho flutuantes). Isso permite que
os usuários usem a tecla Tab para navegar pelas principais regiões de um editor do Blockly
e, em seguida, (usando o plug-in de navegação por teclado) usem as teclas de seta para navegar
nessas regiões. Não mude essas paradas de tabulação, porque isso vai interferir na capacidade do gerenciador de foco de gerenciá-las.
Em geral, evite definir paradas de tabulação em outros elementos DOM usados pelo Blockly, já que isso interfere no modelo do Blockly de usar a tecla Tab para navegar entre as áreas do editor e as teclas de seta dentro dessas áreas. Além disso, essas paradas de tabulação podem não funcionar como esperado. Isso acontece porque cada nó focalizável declara um elemento DOM como elemento focalizável. Se você definir uma parada de tabulação em um descendente do elemento focalizável e o usuário usar a tecla Tab para acessar esse elemento, o gerenciador de foco moverá o foco do DOM para o elemento focalizável declarado.
É seguro definir paradas de tabulação em elementos do aplicativo que estão fora do editor do Blockly. Quando o usuário pressiona a tecla Tab do editor para um elemento desse tipo, o gerenciador de foco muda o foco do Blockly de ativo para passivo. Para acessibilidade, defina a propriedade tabindex
como 0
ou -1
, conforme recomendado pelo aviso na descrição do atributo tabindex
da MDN.
Foco do DOM
Por motivos de acessibilidade, os aplicativos precisam evitar chamar o método focus
em elementos DOM. Isso desorienta os usuários de leitores de tela, já que eles são
movidos repentinamente para um local desconhecido no aplicativo.
Outro problema é que o gerenciador de foco reage a eventos de foco definindo o foco do DOM no ancestral mais próximo ou no próprio elemento em foco que é um elemento focalizável declarado. Isso pode ser diferente do elemento em que
focus
foi chamado. Se não houver um ancestral ou elemento focalizável mais próximo, como quando focus
é chamado em um elemento fora do editor do Blockly, o gerenciador de foco apenas muda o nó ativamente focalizado para foco passivo.
Posicionáveis
Os elementos posicionáveis são componentes que ficam acima do espaço de trabalho e implementam IPositionable
.
Exemplos são a lixeira e a mochila no plug-in
backpack.
Os elementos posicionáveis ainda não estão integrados ao sistema de foco.