Sistema di messa a fuoco

Il sistema di messa a fuoco tiene traccia della posizione (focus) dell'utente in un editor Blockly. Viene utilizzato da Blockly e dal codice personalizzato per determinare quale componente (blocco, campo, categoria della toolbox e così via) ha attualmente lo stato attivo e per spostarlo su un altro componente.

È importante comprendere il sistema di messa a fuoco per assicurarsi che il codice personalizzato funzioni correttamente.

Architettura

Il sistema di messa a fuoco è composto da tre parti:

  • FocusManager è un singleton che coordina lo stato attivo in tutto Blockly. Viene utilizzato da Blockly e dal codice personalizzato per scoprire quale componente ha il focus di Blockly, nonché per spostare il focus di Blockly su un altro componente. Inoltre, ascolta gli eventi di focus DOM, sincronizza il focus di Blockly e il focus DOM e gestisce le classi CSS che indicano quale componente ha il focus.

    Il gestore del focus viene utilizzato principalmente da Blockly. A volte viene utilizzato dal codice personalizzato per interagire con il sistema di messa a fuoco.

  • Un IFocusableTree è un'area indipendente di un editor Blockly, ad esempio un workspace o una toolbox. È composto da nodi attivabili, come blocchi e campi. Gli alberi possono anche avere sottoalberi. Ad esempio, uno spazio di lavoro mutatore su un blocco nello spazio di lavoro principale è un sottoalbero dello spazio di lavoro principale.

    IFocusableTree viene utilizzato principalmente dal gestore della messa a fuoco. A meno che tu non scriva una cassetta degli attrezzi personalizzata, probabilmente non dovrai implementarla.

  • Un IFocusableNode è un componente Blockly che può avere lo stato attivo, ad esempio un blocco, un campo o una categoria della toolbox. I nodi attivabili hanno un elemento DOM che visualizza il nodo e che ha lo stato attivo DOM quando il nodo ha lo stato attivo Blockly. Tieni presente che gli alberi sono anche nodi attivabili. Ad esempio, puoi concentrarti sull'intero spazio di lavoro.

    I metodi in IFocusableNode vengono chiamati principalmente dal gestore dello stato attivo.

    IFocusableNode stesso viene utilizzato per rappresentare il componente attivo. Ad esempio, quando un utente seleziona un elemento nel menu contestuale di un blocco, il blocco viene passato alla funzione di callback dell'elemento come IFocusableNode.

    Se scrivi componenti personalizzati, potresti dover implementare IFocusableNode.

Tipi di messa a fuoco

Il sistema di messa a fuoco definisce diversi tipi di messa a fuoco.

Focus di Blockly e focus DOM

I due tipi principali di messa a fuoco sono la messa a fuoco di Blockly e la messa a fuoco del DOM.

  • Focus di Blockly specifica quale componente di Blockly (blocco, campo, categoria della tavolozza ecc.) ha lo stato attivo. È necessario per lavorare a livello di componenti Blockly. Ad esempio, il plug-in di navigazione da tastiera consente agli utenti di utilizzare i tasti freccia per spostarsi da un componente all'altro, ad esempio da un blocco a un campo. Allo stesso modo, il sistema di menu contestuale crea un menu appropriato per il componente corrente, ovvero crea menu diversi per spazi di lavoro, blocchi e commenti dello spazio di lavoro.

  • Stato attivo DOM specifica quale elemento DOM ha lo stato attivo. È necessario per lavorare a livello di elementi DOM. Ad esempio, gli screen reader presentano informazioni sull'elemento che attualmente ha lo stato attivo del DOM e i tasti Tab si spostano (cambiano lo stato attivo) da un elemento DOM all'altro.

Il gestore dello stato attivo mantiene sincronizzati lo stato attivo di Blockly e del DOM, quindi quando un nodo (componente Blockly) ha lo stato attivo di Blockly, l'elemento DOM sottostante ha lo stato attivo del DOM e viceversa.

Focus attivo e passivo

La messa a fuoco di Blockly è ulteriormente suddivisa in messa a fuoco attiva e messa a fuoco passiva. Il focus attivo indica che un nodo riceverà l'input utente, ad esempio la pressione di un tasto. Il focus passivo indica che un nodo aveva precedentemente il focus attivo, ma l'ha perso quando l'utente si è spostato su un nodo in un altro albero (ad esempio, si è spostato dallo spazio di lavoro alla casella degli strumenti) o si è allontanato completamente dall'editor Blockly. Se l'albero riacquisisce lo stato attivo, il nodo con stato attivo passivo riacquisisce lo stato attivo.

Ogni albero ha un contesto di messa a fuoco separato. ovvero al massimo un nodo dell'albero può avere lo stato attivo. Se lo stato attivo è attivo o passivo dipende dal fatto che l'albero sia selezionato. Può esserci al massimo un nodo con il focus attivo sull'intera pagina.

Il gestore del focus utilizza evidenziazioni diverse (classi CSS) per i nodi attivi e passivi. Questi consentono agli utenti di capire dove si trovano e dove torneranno.

Focus temporaneo

Esiste un altro tipo di messa a fuoco chiamato messa a fuoco effimera. Flussi di lavoro separati, come finestre di dialogo o editor di campi, richiedono lo stato attivo temporaneo al gestore dello stato attivo. Quando il gestore della messa a fuoco concede la messa a fuoco temporanea, sospende il sistema di messa a fuoco. Da un punto di vista pratico, ciò significa che questi flussi di lavoro possono acquisire e agire sugli eventi di messa a fuoco del DOM senza doversi preoccupare che anche il sistema di messa a fuoco agisca su di essi.

Quando il gestore dello stato attivo concede lo stato attivo temporaneo, cambia il nodo attualmente attivo in stato attivo passivo. Ripristina la messa a fuoco attiva quando viene restituita la messa a fuoco temporanea.

Esempi

I seguenti esempi illustrano come Blockly utilizza il sistema di messa a fuoco. Questi dovrebbero aiutarti a capire come il tuo codice si inserisce nel sistema di messa a fuoco e come potrebbe utilizzarlo.

Spostare lo stato attivo con la tastiera

Supponiamo che un blocco con due campi abbia lo stato attivo di Blockly, come indicato da un'evidenziazione (classe CSS) sull'elemento DOM del blocco. Ora supponiamo che l'utente prema la freccia destra:

  1. Il plug-in di navigazione da tastiera:
    • Riceve un evento di pressione di un tasto.
    • Chiede al sistema di navigazione (una parte di Blockly di base) di spostare lo stato attivo sul componente "successivo".
  2. Il sistema di navigazione:
    • Chiede al gestore dello stato attivo quale componente ha lo stato attivo di Blockly. Il gestore dello stato attivo restituisce il blocco come IFocusableNode.
    • Determina che IFocusableNode è un BlockSvg e ne esamina le regole per la navigazione tra i blocchi, che stabiliscono che il focus di Blockly deve spostarsi dal blocco nel suo complesso al primo campo del blocco.
    • Indica al gestore dello stato attivo di spostare lo stato attivo di Blockly sul primo campo.
  3. Il focus manager:
    • Aggiorna il suo stato per impostare lo stato attivo di Blockly sul primo campo.
    • Imposta lo stato attivo DOM sull'elemento DOM del campo.
    • Sposta la classe di evidenziazione dall'elemento del blocco all'elemento del campo.

Spostare lo stato attivo con il mouse

Supponiamo ora che l'utente faccia clic sul secondo campo del blocco. Il gestore della messa a fuoco:

  1. Riceve un evento DOM focusout sull'elemento DOM del primo campo e un evento focusin sull'elemento DOM del secondo campo.
  2. Determina che l'elemento DOM che ha ricevuto lo stato attivo corrisponde al secondo campo.
  3. Aggiorna il suo stato per impostare lo stato attivo di Blockly sul secondo campo. Il gestore del focus non deve impostare il focus DOM perché il browser lo ha già fatto.
  4. Sposta la classe di evidenziazione dall'elemento del primo campo all'elemento del secondo campo.

Altri esempi

Ecco altri esempi:

  • Quando un utente trascina un blocco dalla tavolozza allo spazio di lavoro, il gestore dell'evento del mouse crea un nuovo blocco e chiama il gestore del focus per impostare il focus di Blockly su quel blocco.

  • Quando un blocco viene eliminato, il relativo metodo dispose chiama il gestore della messa a fuoco per spostare la messa a fuoco sul blocco padre.

  • Le scorciatoie da tastiera utilizzano IFocusableNode per identificare il componente Blockly a cui si applica la scorciatoia.

  • I menu contestuali utilizzano IFocusableNode per identificare il componente Blockly su cui è stato richiamato il menu.

Personalizzazioni e sistema di messa a fuoco

Quando personalizzi Blockly, devi assicurarti che il codice funzioni correttamente con il sistema di messa a fuoco. Puoi anche utilizzare il sistema di messa a fuoco per identificare e impostare il nodo attualmente selezionato.

Blocchi personalizzati e contenuti della cassetta degli attrezzi

Il modo più comune per personalizzare Blockly è definire blocchi personalizzati e personalizzare i contenuti della cassetta degli attrezzi. Nessuna di queste azioni ha effetto sul sistema di messa a fuoco.

Classi personalizzate

Le classi personalizzate potrebbero dover implementare una o entrambe le interfacce di messa a fuoco (IFocusableTree e IFocusableNode). Non è sempre ovvio quando ciò accade.

Alcune classi devono chiaramente implementare interfacce di messa a fuoco. tra cui:

  • Una classe che implementa una casella degli strumenti personalizzata. Questa classe deve implementare IFocusableTree e IFocusableNode.

  • Classi che creano un componente visibile (ad esempio un campo o un'icona) a cui gli utenti possono accedere. Queste classi devono implementare IFocusableNode.

Alcune classi devono implementare IFocusableNode anche se non creano un componente visibile o creano un componente visibile a cui gli utenti non possono accedere. tra cui:

  • Classi che implementano un'interfaccia che estende IFocusableNode.

    Ad esempio, l'icona di spostamento nel plug-in di navigazione da tastiera mostra una freccia a quattro direzioni che indica che il blocco può essere spostato con i tasti freccia. L'icona stessa non è visibile (la freccia a quattro direzioni è una bolla) e gli utenti non possono accedervi. Tuttavia, l'icona deve implementare IFocusableNode perché le icone implementanoIIcon e IIcon estende IFocusableNode.

  • Classi utilizzate in un'API che richiede un IFocusableNode.

    Ad esempio, la classe FlyoutSeparator crea uno spazio tra due elementi in un riquadro a comparsa. Non crea elementi DOM, quindi non ha un componente visibile e gli utenti non possono accedervi. Tuttavia, deve implementare IFocusableNode perché è memorizzato in un FlyoutItem e il costruttore FlyoutItem richiede un IFocusableNode.

  • Classi che estendono una classe che implementa IFocusableNode.

    Ad esempio, ToolboxSeparator estende ToolboxItem, che implementa IFocusableNode. Sebbene i separatori della barra degli strumenti abbiano un componente visibile, gli utenti non possono accedervi perché non possono essere utilizzati e non contengono contenuti utili.

Altre classi creano componenti visibili a cui l'utente può accedere, ma non devono implementare IFocusableNode. tra cui:

  • Classi che creano un componente visibile che gestisce il proprio stato attivo, ad esempio un editor di campi o una finestra di dialogo. Tieni presente che queste classi devono acquisire il focus temporaneo all'inizio e restituirlo al termine. Se utilizzi WidgetDiv o DropDownDiv, questo problema verrà risolto automaticamente.

Infine, alcune classi non interagiscono con il sistema di messa a fuoco e non devono implementare IFocusableTree o IFocusableNode. tra cui:

  • Classi che creano un componente visibile a cui gli utenti non possono accedere o interagire e che non contiene informazioni che uno screen reader potrebbe utilizzare. Ad esempio, uno sfondo puramente decorativo in un gioco.

  • Classi completamente estranee al sistema di messa a fuoco, ad esempio classi che implementano IMetricsManager o IVariableMap.

Se non sai se la tua classe interagirà con il sistema di messa a fuoco, testala con il plug-in di navigazione da tastiera. Se non va a buon fine, potresti dover implementare IFocusableTree o IFocusableNode. Se l'operazione va a buon fine, ma hai ancora dubbi, leggi il codice che utilizza la tua classe per vedere se è richiesta una delle due interfacce o se sono presenti altre interazioni.

Implementare le interfacce di messa a fuoco

Il modo più semplice per implementare IFocusableTree o IFocusableNode è estendere una classe che implementa queste interfacce. Ad esempio, se stai creando una casella degli strumenti personalizzata, estendi Toolbox, che implementa IFocusableTree e IFocusableNode. Se stai creando un campo personalizzato, estendi Field, che implementa IFocusableNode. Assicurati che il codice non interferisca con il codice dell'interfaccia di messa a fuoco nella classe base.

Se estendi una classe che implementa un'interfaccia di messa a fuoco, in genere non devi eseguire l'override di alcun metodo. L'eccezione più comune è IFocusableNode.canBeFocused, che devi ignorare se non vuoi che gli utenti accedano al tuo componente.

Meno comune è la necessità di eseguire l'override dei metodi di callback dello stato attivo (onTreeFocus e onTreeBlur in IFocusableTree e onNodeFocus e onNodeBlur in IFocusableNode). Tieni presente che il tentativo di modificare lo stato attivo (chiamata FocusManager.focusNode o FocusManager.focusTree) da questi metodi genera un'eccezione.

Se scrivi un componente personalizzato da zero, dovrai implementare tu stesso le interfacce di messa a fuoco. Per ulteriori informazioni, consulta la documentazione di riferimento per IFocusableTree e IFocusableNode.

Dopo aver implementato la classe, testala con il plug-in di navigazione da tastiera per verificare se puoi (o non puoi) navigare fino al componente.

Utilizzare la gestione della messa a fuoco

Alcune classi personalizzate utilizzano il gestore dello stato attivo. I motivi più comuni per farlo sono ottenere il nodo attualmente selezionato e selezionare un nodo diverso. Per ottenere il gestore della messa a fuoco, chiama Blockly.getFocusManager:

const focusManager = Blockly.getFocusManager();

Per ottenere il nodo attivo al momento, chiama getFocusedNode:

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

Per spostare lo stato attivo su un altro nodo, chiama focusNode:

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

Per spostare lo stato attivo di selezione su un albero, chiama focusTree. Inoltre, il focus del nodo viene impostato sul nodo radice dell'albero.

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

L'altro motivo comune per utilizzare il gestore della messa a fuoco è acquisire e restituire la messa a fuoco temporanea. La funzione takeEphemeralFocus restituisce una lambda che devi chiamare per restituire lo stato attivo temporaneo.

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

Se utilizzi WidgetDiv o DropDownDiv, questi gestiranno lo stato attivo temporaneo per te.

Tabulazioni

Il sistema di messa a fuoco imposta un punto di tabulazione (tabindex di 0) sull'elemento radice di tutti gli alberi (l'area di lavoro principale, la barra degli strumenti e le aree di lavoro a comparsa). In questo modo gli utenti possono utilizzare il tasto Tab per spostarsi tra le regioni principali di un editor Blockly e poi (utilizzando il plug-in di navigazione da tastiera) utilizzare i tasti freccia per spostarsi all'interno di queste regioni. Non modificare queste tabulazioni, in quanto ciò interferirà con la capacità del gestore dello stato attivo di gestirle.

In genere, devi evitare di impostare interruzioni di tabulazione su altri elementi DOM utilizzati da Blockly, in quanto ciò interferisce con il modello di Blockly di utilizzo del tasto Tab per spostarsi tra le aree dell'editor e dei tasti freccia all'interno di queste aree. Inoltre, queste tabulazioni potrebbero non funzionare come previsto. Questo perché ogni nodo selezionabile dichiara un elemento DOM come elemento selezionabile. Se imposti un punto di tabulazione su un elemento discendente dell'elemento selezionabile e l'utente passa a quell'elemento, il gestore dello stato attivo sposterà lo stato attivo del DOM sull'elemento selezionabile dichiarato.

È sicuro impostare le tabulazioni sugli elementi dell'applicazione che si trovano al di fuori dell'editor Blockly. Quando l'utente passa dall'editor a un elemento di questo tipo, il gestore del focus cambia il focus di Blockly da attivo a passivo. Per l'accessibilità, devi impostare la proprietà tabindex su 0 o -1, come consigliato dall'avviso nella descrizione dell'attributo tabindex di MDN.

Focus DOM

Per motivi di accessibilità, le applicazioni devono evitare di chiamare il metodo focus sugli elementi DOM. Questo disorienta gli utenti di screen reader, che vengono improvvisamente spostati in una posizione sconosciuta dell'applicazione.

Un altro problema è che il gestore dello stato attivo reagisce agli eventi di stato attivo impostando lo stato attivo DOM sull'elemento antenato o sull'elemento stesso più vicino dell'elemento attivo che è un elemento dichiaratamente attivabile. Potrebbe essere diverso dall'elemento su cui è stato chiamato focus. Se non è presente un antenato o un elemento stesso più vicino su cui è possibile impostare lo stato attivo, ad esempio quando focus viene chiamato su un elemento esterno all'editor Blockly, il gestore dello stato attivo modifica semplicemente il nodo attivo in stato attivo passivo.

Elementi posizionabili

I componenti posizionabili sono componenti posizionati sopra lo spazio di lavoro e implementano IPositionable. Alcuni esempi sono il cestino e lo zaino nel plug-in Zaino. Gli elementi posizionabili non sono ancora integrati nel sistema di messa a fuoco.