El sistema de enfoque hace un seguimiento de la ubicación (enfoque) del usuario en un editor de Blockly. Blockly y el código personalizado lo usan para determinar qué componente (bloque, campo, categoría de la caja de herramientas, etc.) tiene el enfoque actualmente y para mover ese enfoque a otro componente.
Es importante comprender el sistema de enfoque para garantizar que tu código personalizado funcione correctamente con él.
Arquitectura
El sistema de enfoque tiene tres partes:
FocusManager
es un singleton que coordina el enfoque en todo Blockly. Blockly y el código personalizado lo usan para averiguar qué componente tiene el enfoque de Blockly, así como para mover el enfoque de Blockly a otro componente. También escucha los eventos de enfoque del DOM, sincroniza el enfoque de Blockly y el enfoque del DOM, y administra las clases CSS que indican qué componente tiene el enfoque.El administrador de enfoque lo usa principalmente Blockly. A veces, el código personalizado lo usa para interactuar con el sistema de enfoque.
Un
IFocusableTree
es un área independiente de un editor de Blockly, como un espacio de trabajo o una caja de herramientas. Se compone de nodos enfocables, como bloques y campos. Los árboles también pueden tener subárboles. Por ejemplo, un espacio de trabajo de mutador en un bloque del espacio de trabajo principal es un subárbol del espacio de trabajo principal.IFocusableTree
se usa principalmente en el administrador de enfoque. A menos que escribas una caja de herramientas personalizada, probablemente no necesites implementarla.Un
IFocusableNode
es un componente de Blockly que puede tener el enfoque, como un bloque, un campo o una categoría de la caja de herramientas. Los nodos enfocables tienen un elemento DOM que muestra el nodo y que tiene el enfoque del DOM cuando el nodo tiene el enfoque de Blockly. Ten en cuenta que los árboles también son nodos enfocables. Por ejemplo, puedes enfocarte en el espacio de trabajo como un todo.El administrador de enfoque llama principalmente a los métodos en
IFocusableNode
.IFocusableNode
se usa para representar el componente que tiene el enfoque. Por ejemplo, cuando un usuario selecciona un elemento en el menú contextual de un bloque, el bloque se pasa a la función de devolución de llamada del elemento como unIFocusableNode
.Si escribes componentes personalizados, es posible que debas implementar
IFocusableNode
.
Tipos de enfoque
El sistema de enfoque define varios tipos diferentes de enfoque.
Enfoque de Blockly y enfoque del DOM
Los dos tipos principales de enfoque son el enfoque de Blockly y el enfoque del DOM.
Enfoque de Blockly especifica qué componente de Blockly (bloque, campo, categoría de la caja de herramientas, etc.) tiene el enfoque. Es necesario para trabajar a nivel de los componentes de Blockly. Por ejemplo, el complemento de navegación con el teclado permite a los usuarios usar las teclas de flecha para moverse de un componente a otro, como de un bloque a un campo. Del mismo modo, el sistema de menú contextual crea un menú adecuado para el componente actual, es decir, crea menús diferentes para los espacios de trabajo, los bloques y los comentarios del espacio de trabajo.
Enfoque del DOM especifica qué elemento DOM tiene el enfoque. Es necesario para trabajar a nivel de los elementos del DOM. Por ejemplo, los lectores de pantalla presentan información sobre el elemento que actualmente tiene el enfoque del DOM, y las pestañas se mueven (cambian el enfoque) de un elemento DOM a otro.
El administrador de enfoque mantiene sincronizados el enfoque de Blockly y el enfoque del DOM, de modo que, cuando un nodo (componente de Blockly) tiene el enfoque de Blockly, su elemento DOM subyacente tiene el enfoque del DOM y viceversa.
Enfoque activo y pasivo
El enfoque de Blockly se divide en enfoque activo y enfoque pasivo. El enfoque activo significa que un nodo recibirá la entrada del usuario, como la presión de una tecla. El enfoque pasivo significa que un nodo tenía enfoque activo anteriormente, pero lo perdió cuando el usuario se movió a un nodo en otro árbol (por ejemplo, se movió del espacio de trabajo a la caja de herramientas) o se alejó del editor de Blockly por completo. Si el árbol recupera el enfoque, el nodo enfocado de forma pasiva recuperará el enfoque activo.
Cada árbol tiene un contexto de enfoque independiente. Es decir, como máximo, un nodo del árbol puede tener el enfoque. Si ese enfoque es activo o pasivo, depende de si el árbol tiene el enfoque. Puede haber, como máximo, un nodo con enfoque activo en toda la página.
El administrador de enfoque usa diferentes resaltados (clases CSS) para los nodos enfocados de forma activa y pasiva. Permiten que los usuarios comprendan dónde están y a dónde volverán.
Enfoque efímero
Existe otro tipo de enfoque llamado enfoque efímero. Los flujos de trabajo separados, como los diálogos o los editores de campos, solicitan un enfoque efímero del administrador de enfoque. Cuando el administrador de enfoque otorga el enfoque efímero, suspende el sistema de enfoque. Desde un punto de vista práctico, esto significa que estos flujos de trabajo pueden capturar eventos de enfoque del DOM y actuar en consecuencia sin tener que preocuparse de que el sistema de enfoque también actúe sobre ellos.
Cuando el administrador de enfoque otorga un enfoque efímero, cambia el nodo enfocado de forma activa a un enfoque pasivo. Restablece el enfoque activo cuando se devuelve el enfoque efímero.
Ejemplos
En los siguientes ejemplos, se ilustra cómo Blockly usa el sistema de enfoque. Deberían ayudarte a comprender cómo se adapta tu código al sistema de enfoque y cómo podría usarlo.
Cómo mover el enfoque con el teclado
Supongamos que un bloque con dos campos tiene el enfoque de Blockly, como lo indica un resaltado (clase CSS) en el elemento DOM del bloque. Ahora, supongamos que el usuario presiona la flecha hacia la derecha:
- El complemento de navegación con el teclado:
- Recibe un evento de presión de tecla.
- Le pide al sistema de navegación (una parte del núcleo de Blockly) que mueva el enfoque al componente "siguiente".
- El sistema de navegación:
- Pregunta al administrador de enfoque qué componente tiene el enfoque de Blockly. El administrador de enfoque devuelve el bloque como un
IFocusableNode
. - Determina que
IFocusableNode
es unBlockSvg
y observa sus reglas para navegar por los bloques, que indican que debe mover el enfoque de Blockly del bloque como un todo al primer campo del bloque. - Indica al administrador de enfoque que mueva el enfoque de Blockly al primer campo.
- Pregunta al administrador de enfoque qué componente tiene el enfoque de Blockly. El administrador de enfoque devuelve el bloque como un
- El administrador de enfoque:
- Actualiza su estado para establecer el enfoque de Blockly en el primer campo.
- Establece el enfoque del DOM en el elemento DOM del campo.
- Mueve la clase de resaltado del elemento del bloque al elemento del campo.
Mueve el enfoque con el mouse
Ahora, supongamos que el usuario hace clic en el segundo campo del bloque. El administrador de enfoque:
- Recibe un evento
focusout
del DOM en el elemento DOM del primer campo y un eventofocusin
en el elemento DOM del segundo campo. - Determina que el elemento DOM que recibió el enfoque corresponde al segundo campo.
- Actualiza su estado para establecer el enfoque de Blockly en el segundo campo. (El administrador de enfoque no necesita establecer el enfoque del DOM porque el navegador ya lo hizo).
- Mueve la clase de resaltado del elemento del primer campo al elemento del segundo campo.
Otros ejemplos
A continuación, puedes ver algunos otros ejemplos:
Cuando un usuario arrastra un bloque desde la caja de herramientas al espacio de trabajo, el controlador de eventos del mouse crea un bloque nuevo y llama al administrador de enfoque para establecer el enfoque de Blockly en ese bloque.
Cuando se borra un bloque, su método
dispose
llama al administrador de enfoque para mover el enfoque al bloque superior.Las combinaciones de teclas usan
IFocusableNode
para identificar el componente de Blockly al que se aplica la combinación.Los menús contextuales usan
IFocusableNode
para identificar el componente de Blockly en el que se invocó el menú.
Personalizaciones y el sistema de enfoque
Cuando personalizas Blockly, debes asegurarte de que tu código funcione correctamente con el sistema de enfoque. También puedes usar el sistema de enfoque para identificar y establecer el nodo enfocado actualmente.
Bloques personalizados y contenido de la caja de herramientas
La forma más común de personalizar Blockly es definir bloques personalizados y personalizar el contenido de la caja de herramientas. Ninguna de estas acciones afecta el sistema de enfoque.
Clases personalizadas
Es posible que las clases personalizadas deban implementar una o ambas interfaces de enfoque (IFocusableTree
y IFocusableNode
). No siempre es obvio cuándo es así.
Algunas clases claramente necesitan implementar interfaces de enfoque. Estos incluyen los siguientes:
Clase que implementa una caja de herramientas personalizada. Esta clase debe implementar
IFocusableTree
yIFocusableNode
.Clases que crean un componente visible (como un campo o un ícono) al que los usuarios pueden navegar. Estas clases deben implementar
IFocusableNode
.
Algunas clases deben implementar IFocusableNode
, aunque no creen un componente visible o creen un componente visible al que los usuarios no pueden navegar. Estos incluyen los siguientes:
Clases que implementan una interfaz que extiende
IFocusableNode
.Por ejemplo, el ícono de movimiento en el complemento de navegación con el teclado muestra una flecha de cuatro direcciones que indica que el bloque se puede mover con las teclas de flecha. El ícono en sí no es visible (la flecha de cuatro direcciones es una burbuja) y los usuarios no pueden navegar hasta él. Sin embargo, el ícono debe implementar
IFocusableNode
porque los íconos implementanIIcon
yIIcon
extiendeIFocusableNode
.Clases que se usan en una API que requiere un
IFocusableNode
.Por ejemplo, la clase
FlyoutSeparator
crea un espacio entre dos elementos en un menú desplegable. No crea ningún elemento DOM, por lo que no tiene un componente visible y los usuarios no pueden navegar a él. Sin embargo, debe implementarIFocusableNode
porque se almacena en unFlyoutItem
y el constructorFlyoutItem
requiere unIFocusableNode
.Clases que extienden una clase que implementa
IFocusableNode
Por ejemplo,
ToolboxSeparator
extiendeToolboxItem
, que implementaIFocusableNode
. Si bien los separadores de la barra de herramientas tienen un componente visible, los usuarios no pueden navegar a ellos porque no se pueden usar y no tienen contenido útil.
Otras clases crean componentes visibles a los que el usuario puede navegar, pero no necesitan implementar IFocusableNode
. Estos incluyen los siguientes:
- Clases que crean un componente visible que administra su propio enfoque, como un editor de campos o un diálogo. (Ten en cuenta que estas clases deben tomar el foco efímero cuando comienzan y devolverlo cuando terminan). Si usas
WidgetDiv
oDropDownDiv
, se controlará esto por ti.
Por último, algunas clases no interactúan con el sistema de enfoque y no necesitan implementar IFocusableTree
ni IFocusableNode
. Estos incluyen los siguientes:
Clases que crean un componente visible al que los usuarios no pueden navegar ni interactuar, y que no contiene información que un lector de pantalla pueda usar. Por ejemplo, un fondo puramente decorativo en un juego.
Clases que no están relacionadas en absoluto con el sistema de enfoque, como las que implementan
IMetricsManager
oIVariableMap
.
Si no sabes con certeza si tu clase interactuará con el sistema de enfoque, pruébala con el complemento de navegación con el teclado. Si esto falla, es posible que debas implementar IFocusableTree
o IFocusableNode
. Si se ejecuta correctamente, pero aún no tienes certeza, lee el código que usa tu clase para ver si se requiere alguna interfaz o si hay otras interacciones.
Implementa interfaces de enfoque
La forma más sencilla de implementar IFocusableTree
o IFocusableNode
es extender una clase que implemente estas interfaces. Por ejemplo, si creas una caja de herramientas personalizada, extiende Toolbox
, que implementa IFocusableTree
y IFocusableNode
. Si creas un campo personalizado, extiende Field
, que implementa IFocusableNode
. Asegúrate de que tu código no interfiera con el código de la interfaz de enfoque en la clase base.
Si extiendes una clase que implementa una interfaz de enfoque, por lo general, no necesitarás anular ningún método. La excepción más común es IFocusableNode.canBeFocused
, que debes anular si no quieres que los usuarios naveguen a tu componente.
Es menos común la necesidad de anular los métodos de devolución de llamada de enfoque (onTreeFocus
y onTreeBlur
en IFocusableTree
y onNodeFocus
y onNodeBlur
en IFocusableNode
). Ten en cuenta que intentar cambiar el enfoque (llamar a FocusManager.focusNode
o FocusManager.focusTree
) desde estos métodos genera una excepción.
Si escribes un componente personalizado desde cero, deberás implementar las interfaces de enfoque por tu cuenta. Consulta la documentación de referencia de IFocusableTree
y IFocusableNode
para obtener más información.
Después de implementar tu clase, pruébala con el complemento de navegación con el teclado para verificar que puedes (o no puedes) navegar a tu componente.
Cómo usar el administrador de enfoque
Algunas clases personalizadas usan el administrador de enfoque. Los motivos más comunes para hacerlo son obtener el nodo enfocado actualmente y enfocar un nodo diferente. Para obtener el administrador de enfoque, llama a Blockly.getFocusManager
:
const focusManager = Blockly.getFocusManager();
Para obtener el nodo enfocado actualmente, llama a getFocusedNode
:
const focusedNode = focusManager.getFocusedNode();
// Do something with the focused node.
Para mover el enfoque a un nodo diferente, llama a focusNode
:
// Move focus to a different block.
focusManager.focusNode(myOtherBlock);
Para mover el enfoque a un árbol, llama a focusTree
. Esto también establece el enfoque del nodo en el nodo raíz del árbol.
// Move focus to the main workspace.
focusManager.focusTree(myMainWorkspace);
Otro motivo común para usar el administrador de enfoque es tomar y devolver el enfoque efímero. La función takeEphemeralFocus
devuelve una lambda que debes llamar para devolver el enfoque efímero.
const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();
Si usas WidgetDiv
o DropDownDiv
, se controlará el enfoque efímero por ti.
Paradas de tabulación
El sistema de enfoque establece una parada de tabulación (tabindex
de 0
) en el elemento raíz de todos los árboles (el espacio de trabajo principal, la caja de herramientas y los espacios de trabajo emergentes). Esto permite a los usuarios usar la tecla Tab para navegar por las regiones principales de un editor de Blockly y, luego (con el complemento de navegación con el teclado), usar las teclas de flecha para navegar dentro de esas regiones. No cambies estas detenciones de tabulación, ya que esto interferirá con la capacidad del administrador de enfoque para administrarlas.
En general, debes evitar establecer paradas de tabulación en otros elementos del DOM que usa Blockly, ya que esto interfiere con el modelo de Blockly de usar la tecla Tab para navegar entre las áreas del editor y las teclas de flecha dentro de esas áreas. Además, es posible que estos topes de tabulación no funcionen según lo previsto. Esto se debe a que cada nodo enfocable declara un elemento DOM como su elemento enfocable. Si estableces una detención de tabulación en un elemento secundario del elemento enfocable y el usuario tabula hasta ese elemento, el administrador de enfoque moverá el enfoque del DOM al elemento enfocable declarado.
Es seguro establecer paradas de tabulación en elementos de tu aplicación que estén fuera del editor de Blockly. Cuando el usuario presiona la tecla Tab desde el editor hasta uno de estos elementos, el administrador de enfoque cambia el enfoque de Blockly de activo a pasivo. Para mejorar la accesibilidad, debes establecer la propiedad tabindex
en 0
o -1
, como se recomienda en la advertencia de la descripción del atributo tabindex
de MDN.
Enfoque del DOM
Por motivos de accesibilidad, las aplicaciones deben evitar llamar al método focus
en elementos DOM. Esto desorienta a los usuarios de lectores de pantalla, ya que se los traslada repentinamente a una ubicación desconocida en la aplicación.
Un problema adicional es que el administrador de enfoque reacciona a los eventos de enfoque estableciendo el enfoque del DOM en el elemento enfocado más cercano (ya sea un ancestro o el mismo elemento) que sea un elemento enfocable declarado. Puede ser diferente del elemento en el que se llamó a focus
. (Si no hay un elemento superior o propio enfocable más cercano, como cuando se llama a focus
en un elemento fuera del editor de Blockly, el administrador de enfoque solo cambia el nodo enfocado de forma activa al enfoque pasivo).
Elementos posicionables
Los elementos posicionables son componentes que se posicionan sobre el espacio de trabajo y que implementan IPositionable
.
Por ejemplo, la papelera y la mochila en el complemento de mochila.
Los elementos posicionables aún no están integrados en el sistema de enfoque.