Système de mise au point

Le système de focus suit l'emplacement (focus) de l'utilisateur dans un éditeur Blockly. Il est utilisé par Blockly et par le code personnalisé pour déterminer quel composant (bloc, champ, catégorie de boîte à outils, etc.) est actuellement sélectionné et pour déplacer cette sélection vers un autre composant.

Il est important de comprendre le système de mise au point pour vous assurer que votre code personnalisé fonctionne correctement avec celui-ci.

Architecture

Le système de mise au point comporte trois parties :

  • FocusManager est un singleton qui coordonne la mise au point dans Blockly. Il est utilisé par Blockly et par le code personnalisé pour déterminer quel composant a la mise au point Blockly, ainsi que pour déplacer la mise au point Blockly vers un autre composant. Il écoute également les événements de sélection DOM, synchronise la sélection Blockly et la sélection DOM, et gère les classes CSS qui indiquent quel composant est sélectionné.

    Le gestionnaire de focus est principalement utilisé par Blockly. Il est parfois utilisé par le code personnalisé pour interagir avec le système de mise au point.

  • Un IFocusableTree est une zone indépendante d'un éditeur Blockly, comme un espace de travail ou une boîte à outils. Il se compose de nœuds sélectionnables, tels que des blocs et des champs. Les arbres peuvent également comporter des sous-arbres. Par exemple, un espace de travail de mutateur sur un bloc de l'espace de travail principal est un sous-arbre de l'espace de travail principal.

    IFocusableTree est principalement utilisé par le gestionnaire de mise au point. À moins que vous n'écriviez une boîte à outils personnalisée, vous n'aurez probablement pas besoin de l'implémenter.

  • Un IFocusableNode est un composant Blockly qui peut avoir le focus, comme un bloc, un champ ou une catégorie de boîte à outils. Les nœuds sélectionnables ont un élément DOM qui affiche le nœud et qui est sélectionné dans le DOM lorsque le nœud est sélectionné dans Blockly. Notez que les arbres sont également des nœuds sélectionnables. Par exemple, vous pouvez vous concentrer sur l'espace de travail dans son ensemble.

    Les méthodes de IFocusableNode sont principalement appelées par le gestionnaire de mise au point.

    IFocusableNode lui-même est utilisé pour représenter le composant sélectionné. Par exemple, lorsqu'un utilisateur sélectionne un élément dans le menu contextuel d'un bloc, le bloc est transmis à la fonction de rappel de l'élément en tant que IFocusableNode.

    Si vous écrivez des composants personnalisés, vous devrez peut-être implémenter IFocusableNode.

Types de ciblage

Le système de mise au point définit différents types de mise au point.

Focus Blockly et focus DOM

Il existe deux principaux types de focus : le focus Blockly et le focus DOM.

  • Focus Blockly spécifie le composant Blockly (bloc, champ, catégorie de la boîte à outils, etc.) qui est sélectionné. Il est nécessaire pour travailler au niveau des composants Blockly. Par exemple, le plug-in de navigation au clavier permet aux utilisateurs d'utiliser les touches fléchées pour passer d'un composant à un autre, par exemple d'un bloc à un champ. De même, le système de menu contextuel crée un menu adapté au composant actuel, c'est-à-dire qu'il crée différents menus pour les espaces de travail, les blocs et les commentaires d'espace de travail.

  • Focus DOM : spécifie l'élément DOM sélectionné. Il est nécessaire pour travailler au niveau des éléments DOM. Par exemple, les lecteurs d'écran présentent des informations sur l'élément qui a actuellement le focus DOM, et les tabulations déplacent (changent de focus) d'un élément DOM à un autre.

Le gestionnaire de focus maintient la synchronisation entre le focus Blockly et le focus DOM. Ainsi, lorsqu'un nœud (composant Blockly) a le focus Blockly, son élément DOM sous-jacent a le focus DOM, et inversement.

Focus actif et passif

La mise au point Blockly est divisée en mise au point active et mise au point passive. La mise au point active signifie qu'un nœud recevra une saisie utilisateur, telle qu'une pression sur une touche. La mise au point passive signifie qu'un nœud avait auparavant une mise au point active, mais qu'il l'a perdue lorsque l'utilisateur est passé à un nœud d'un autre arbre (par exemple, de l'espace de travail à la boîte à outils) ou qu'il a quitté l'éditeur Blockly. Si l'arborescence retrouve le focus, le nœud ciblé passivement retrouve le focus actif.

Chaque arborescence possède un contexte de sélection distinct. Autrement dit, au maximum un nœud de l'arborescence peut être sélectionné. La sélection est active ou passive selon que l'arborescence est sélectionnée ou non. Il ne peut y avoir qu'un seul nœud avec une mise au point active sur l'ensemble de la page.

Le gestionnaire de focus utilise différentes mises en surbrillance (classes CSS) pour les nœuds actifs et passifs. Ils permettent aux utilisateurs de comprendre où ils se trouvent et où ils reviendront.

Focus éphémère

Il existe un autre type de sélection appelé sélection éphémère. Les workflows distincts, tels que les boîtes de dialogue ou les éditeurs de champs, demandent une mise au point éphémère au gestionnaire de mise au point. Lorsque le gestionnaire de mise au point accorde une mise au point éphémère, il suspend le système de mise au point. D'un point de vue pratique, cela signifie que ces workflows peuvent capturer les événements de focus DOM et agir en conséquence sans se soucier du fait que le système de focus puisse également agir sur eux.

Lorsque le gestionnaire de focus accorde un focus éphémère, il remplace le nœud actif par un focus passif. Elle restaure la mise au point active lorsque la mise au point éphémère est renvoyée.

Exemples

Les exemples suivants illustrent la façon dont Blockly utilise le système de sélection. Ils devraient vous aider à comprendre comment votre code s'intègre au système de mise au point et comment il peut l'utiliser.

Déplacer la sélection au clavier

Supposons qu'un bloc comportant deux champs soit sélectionné dans Blockly, comme indiqué par une mise en surbrillance (classe CSS) sur l'élément DOM du bloc. Supposons maintenant que l'utilisateur appuie sur la flèche vers la droite :

  1. Le plug-in de navigation au clavier :
    • Reçoit un événement d'appui sur une touche.
    • Demande au système de navigation (qui fait partie du cœur de Blockly) de déplacer le curseur sur le composant "suivant".
  2. Le système de navigation :
    • Demande au gestionnaire de sélection quel composant est sélectionné dans Blockly. Le gestionnaire de sélection renvoie le bloc sous la forme d'un IFocusableNode.
    • Détermine que IFocusableNode est un BlockSvg et examine ses règles de navigation dans les blocs, qui stipulent qu'il doit déplacer la sélection Blockly du bloc dans son ensemble vers le premier champ du bloc.
    • Indique au gestionnaire de sélection de déplacer la sélection Blockly vers le premier champ.
  3. Le gestionnaire de mise au point :
    • Met à jour son état pour définir la mise au point de Blockly sur le premier champ.
    • Définit le focus DOM sur l'élément DOM du champ.
    • Déplace la classe de mise en surbrillance de l'élément du bloc vers l'élément du champ.

Déplacer le curseur avec la souris

Supposons maintenant que l'utilisateur clique sur le deuxième champ du bloc. Gestionnaire de focus :

  1. Reçoit un événement DOM focusout sur l'élément DOM du premier champ et un événement focusin sur l'élément DOM du deuxième champ.
  2. Détermine que l'élément DOM qui a reçu le focus correspond au deuxième champ.
  3. Met à jour son état pour définir la mise au point Blockly sur le deuxième champ. (Le gestionnaire de focus n'a pas besoin de définir le focus DOM, car le navigateur l'a déjà fait.)
  4. Déplace la classe de mise en surbrillance de l'élément du premier champ vers l'élément du deuxième champ.

Autres exemples

Autres exemples :

  • Lorsqu'un utilisateur fait glisser un bloc de la boîte à outils vers l'espace de travail, le gestionnaire d'événements de la souris crée un bloc et appelle le gestionnaire de focus pour définir le focus Blockly sur ce bloc.

  • Lorsqu'un bloc est supprimé, sa méthode dispose appelle le gestionnaire de focus pour déplacer le focus vers le bloc parent.

  • Les raccourcis clavier utilisent IFocusableNode pour identifier le composant Blockly auquel le raccourci s'applique.

  • Les menus contextuels utilisent IFocusableNode pour identifier le composant Blockly sur lequel le menu a été appelé.

Personnalisations et système de focus

Lorsque vous personnalisez Blockly, vous devez vous assurer que votre code fonctionne correctement avec le système de sélection. Vous pouvez également utiliser le système de mise au point pour identifier et définir le nœud actuellement sélectionné.

Blocs personnalisés et contenu de la boîte à outils

La façon la plus courante de personnaliser Blockly consiste à définir des blocs personnalisés et à personnaliser le contenu de la boîte à outils. Aucune de ces actions n'a d'incidence sur le système de mise au point.

Classes personnalisées

Les classes personnalisées peuvent avoir besoin d'implémenter une ou les deux interfaces de mise au point (IFocusableTree et IFocusableNode). Il n'est pas toujours évident de savoir quand c'est le cas.

Certaines classes doivent clairement implémenter des interfaces de focus. Exemples :

  • Classe qui implémente une boîte à outils personnalisée. Cette classe doit implémenter IFocusableTree et IFocusableNode.

  • Classes qui créent un composant visible (tel qu'un champ ou une icône) vers lequel les utilisateurs peuvent accéder. Ces classes doivent implémenter IFocusableNode.

Certaines classes doivent implémenter IFocusableNode même si elles ne créent pas de composant visible ou si elles créent un composant visible vers lequel les utilisateurs ne peuvent pas naviguer. Exemples :

  • Classes qui implémentent une interface qui étend IFocusableNode.

    Par exemple, l'icône de déplacement du plug-in de navigation au clavier affiche une flèche à quatre directions qui indique que le bloc peut être déplacé à l'aide des touches fléchées. L'icône elle-même n'est pas visible (la flèche à quatre directions est une bulle) et les utilisateurs ne peuvent pas y accéder. Toutefois, l'icône doit implémenter IFocusableNode, car les icônes implémentent IIcon et IIcon étend IFocusableNode.

  • Classes utilisées dans une API qui nécessite un IFocusableNode.

    Par exemple, la classe FlyoutSeparator crée un espace entre deux éléments d'un menu volant. Il ne crée aucun élément DOM. Il n'a donc pas de composant visible et les utilisateurs ne peuvent pas y accéder. Toutefois, il doit implémenter IFocusableNode, car il est stocké dans un FlyoutItem et le constructeur FlyoutItem nécessite un IFocusableNode.

  • Classes qui étendent une classe qui implémente IFocusableNode.

    Par exemple, ToolboxSeparator étend ToolboxItem, qui implémente IFocusableNode. Bien que les séparateurs de la boîte à outils aient un composant visible, les utilisateurs ne peuvent pas y accéder, car ils ne peuvent pas être utilisés et ne contiennent aucun contenu utile.

D'autres classes créent des composants visibles vers lesquels l'utilisateur peut naviguer, mais n'ont pas besoin d'implémenter IFocusableNode. Exemples :

  • Classes qui créent un composant visible qui gère sa propre sélection, comme un éditeur de champ ou une boîte de dialogue. (Notez que ces classes doivent prendre le focus éphémère lorsqu'elles commencent et le rendre lorsqu'elles se terminent. L'utilisation de WidgetDiv ou DropDownDiv gérera cela pour vous.)

Enfin, certaines classes n'interagissent pas avec le système de mise au point et n'ont pas besoin d'implémenter IFocusableTree ni IFocusableNode. Exemples :

  • Classes qui créent un composant visible auquel les utilisateurs ne peuvent pas accéder ni sur lequel ils ne peuvent pas agir, et qui ne contiennent aucune information qu'un lecteur d'écran pourrait utiliser. Par exemple, un arrière-plan purement décoratif dans un jeu.

  • Classes sans aucun rapport avec le système de mise au point, telles que les classes qui implémentent IMetricsManager ou IVariableMap.

Si vous n'êtes pas sûr que votre classe interagisse avec le système de mise au point, testez-la avec le plug-in de navigation au clavier. Si cela ne fonctionne pas, vous devrez peut-être implémenter IFocusableTree ou IFocusableNode. Si l'opération réussit, mais que vous avez encore des doutes, lisez le code qui utilise votre classe pour voir si l'une ou l'autre des interfaces est requise ou s'il existe d'autres interactions.

Implémenter des interfaces de focus

Le moyen le plus simple d'implémenter IFocusableTree ou IFocusableNode consiste à étendre une classe qui implémente ces interfaces. Par exemple, si vous créez une boîte à outils personnalisée, étendez Toolbox, qui implémente IFocusableTree et IFocusableNode. Si vous créez un champ personnalisé, étendez Field, qui implémente IFocusableNode. Assurez-vous que votre code n'interfère pas avec le code de l'interface de sélection de la classe de base.

Si vous étendez une classe qui implémente une interface de sélection, vous n'aurez généralement pas besoin de remplacer de méthodes. L'exception la plus courante est IFocusableNode.canBeFocused, que vous devez remplacer si vous ne souhaitez pas que les utilisateurs accèdent à votre composant.

Il est moins courant de devoir remplacer les méthodes de rappel de focus (onTreeFocus et onTreeBlur dans IFocusableTree, onNodeFocus et onNodeBlur dans IFocusableNode). Notez que toute tentative de modification du focus (appel de FocusManager.focusNode ou FocusManager.focusTree) à partir de ces méthodes entraîne une exception.

Si vous écrivez un composant personnalisé à partir de zéro, vous devrez implémenter vous-même les interfaces de mise au point. Pour en savoir plus, consultez la documentation de référence sur IFocusableTree et IFocusableNode.

Une fois votre classe implémentée, testez-la avec le plug-in de navigation au clavier pour vérifier que vous pouvez (ou non) accéder à votre composant.

Utiliser le gestionnaire de focus

Certaines classes personnalisées utilisent le gestionnaire de mise au point. Les raisons les plus courantes de le faire sont d'obtenir le nœud actuellement sélectionné et de se concentrer sur un autre nœud. Pour obtenir le gestionnaire de sélection, appelez Blockly.getFocusManager :

const focusManager = Blockly.getFocusManager();

Pour obtenir le nœud actuellement sélectionné, appelez getFocusedNode :

const focusedNode = focusManager.getFocusedNode();
// Do something with the focused node.

Pour déplacer le focus vers un autre nœud, appelez focusNode :

// Move focus to a different block.
focusManager.focusNode(myOtherBlock);

Pour déplacer le curseur vers un arbre, appelez focusTree. Cela définit également le focus du nœud sur le nœud racine de l'arborescence.

// Move focus to the main workspace.
focusManager.focusTree(myMainWorkspace);

L'autre raison courante d'utiliser le gestionnaire de mise au point est de prendre et de renvoyer la mise au point éphémère. La fonction takeEphemeralFocus renvoie un lambda que vous devez appeler pour renvoyer le focus éphémère.

const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();

Si vous utilisez WidgetDiv ou DropDownDiv, ils géreront la mise au point éphémère pour vous.

Taquets de tabulation

Le système de focus définit une tabulation (tabindex de 0) sur l'élément racine de tous les arbres (l'espace de travail principal, la boîte à outils et les espaces de travail du menu déroulant). Cela permet aux utilisateurs d'utiliser la touche de tabulation pour naviguer dans les régions principales d'un éditeur Blockly, puis (à l'aide du plug-in de navigation au clavier) d'utiliser les touches fléchées pour naviguer dans ces régions. Ne modifiez pas ces taquets de tabulation, car cela nuirait à la capacité du gestionnaire de focus à les gérer.

En général, vous devez éviter de définir des taquets de tabulation sur d'autres éléments DOM utilisés par Blockly, car cela interfère avec le modèle de Blockly qui utilise la touche de tabulation pour naviguer entre les zones de l'éditeur et les touches fléchées dans ces zones. De plus, ces taquets de tabulation peuvent ne pas fonctionner comme prévu. En effet, chaque nœud pouvant être sélectionné déclare un élément DOM comme élément pouvant être sélectionné. Si vous définissez une tabulation sur un descendant de l'élément pouvant recevoir le focus et que l'utilisateur tabule sur cet élément, le gestionnaire de focus déplacera le focus DOM sur l'élément pouvant recevoir le focus déclaré.

Vous pouvez définir des taquets de tabulation sur les éléments de votre application qui se trouvent en dehors de l'éditeur Blockly. Lorsque l'utilisateur passe de l'éditeur à un tel élément à l'aide de la touche de tabulation, le gestionnaire de focus fait passer le focus Blockly d'actif à passif. Pour l'accessibilité, vous devez définir la propriété tabindex sur 0 ou -1, comme recommandé par l'avertissement dans la description de l'attribut tabindex sur MDN.

Focus DOM

Pour des raisons d'accessibilité, les applications doivent éviter d'appeler la méthode focus sur les éléments DOM. Cela désoriente les utilisateurs de lecteurs d'écran, car ils sont soudainement déplacés vers un emplacement inconnu dans l'application.

Un autre problème est que le gestionnaire de focus réagit aux événements de focus en définissant le focus DOM sur l'ancêtre le plus proche ou sur l'élément lui-même qui est un élément focusable déclaré. Il peut être différent de l'élément sur lequel focus a été appelé. (S'il n'y a pas d'ancêtre ou de soi-même le plus proche pouvant être sélectionné, par exemple lorsque focus est appelé sur un élément en dehors de l'éditeur Blockly, le gestionnaire de focus change simplement le nœud actif en focus passif.)

Positionnables

Les éléments positionnables sont des composants placés au-dessus de l'espace de travail et implémentent IPositionable. Par exemple, la corbeille et le sac à dos dans le plug-in Sac à dos. Les éléments positionnables ne sont pas encore intégrés au système de mise au point.