System ostrości

System ostrości śledzi lokalizację użytkownika (ostrość) w edytorze Blockly. Jest on używany przez Blockly i kod niestandardowy do określania, który komponent (blok, pole, kategoria przybornika itp.) jest obecnie aktywny, oraz do przenoszenia tego fokusu na inny komponent.

Ważne jest, aby zrozumieć system ostrości, aby mieć pewność, że Twój kod niestandardowy działa z nim prawidłowo.

Architektura

System ostrości składa się z 3 części:

  • FocusManager to pojedynczy element, który koordynuje fokus we wszystkich elementach Blockly. Jest on używany przez Blockly i kod niestandardowy do określania, który komponent jest aktywny w Blockly, a także do przenoszenia aktywności Blockly na inny komponent. Nasłuchuje też zdarzeń skupienia DOM, synchronizuje skupienie Blockly i DOM oraz zarządza klasami CSS, które wskazują, który komponent jest aktywny.

    Menedżer zaznaczenia jest używany głównie przez Blockly. Jest on czasami używany przez kod niestandardowy do interakcji z systemem ostrości.

  • IFocusableTree to niezależny obszar edytora Blockly, np. obszar roboczy lub przybornik. Składa się z węzłów, na których można ustawić fokus, takich jak bloki i pola. Drzewa mogą też mieć poddrzewa. Na przykład obszar roboczy mutatora na bloku w głównym obszarze roboczym jest poddrzewem głównego obszaru roboczego.

    IFocusableTree jest używany głównie przez menedżera fokusu. Jeśli nie tworzysz niestandardowego zestawu narzędzi, prawdopodobnie nie musisz go wdrażać.

  • IFocusableNode to komponent Blockly, który może być aktywny, np. blok, pole lub kategoria przybornika. Węzły, które można zaznaczyć, mają element DOM, który wyświetla węzeł i który jest zaznaczony w DOM, gdy węzeł jest zaznaczony w Blockly. Pamiętaj, że drzewa to również węzły, które można zaznaczać. Możesz na przykład skupić się na całym obszarze roboczym.

    Metody w IFocusableNode są wywoływane głównie przez menedżera fokusu.

    IFocusableNode jest używany do reprezentowania komponentu, który jest zaznaczony. Gdy na przykład użytkownik wybierze element w menu kontekstowym bloku, blok zostanie przekazany do funkcji wywołania zwrotnego elementu jako IFocusableNode.

    Jeśli piszesz komponenty niestandardowe, może być konieczne wdrożenieIFocusableNode.

Rodzaje ostrości

System ustawiania ostrości definiuje kilka różnych typów ostrości.

Skupienie w Blockly i skupienie w DOM

Dwa główne typy ostrości to ostrość Blockly i ostrość DOM.

  • Focus Blockly określa, który komponent Blockly (blok, pole, kategoria przybornika itp.) jest aktywny. Jest to niezbędne do pracy na poziomie komponentów Blockly. Na przykład wtyczka do nawigacji za pomocą klawiatury umożliwia użytkownikom poruszanie się między komponentami za pomocą klawiszy strzałek, np. z bloku do pola. Podobnie system menu kontekstowego tworzy menu odpowiednie dla bieżącego komponentu, czyli tworzy różne menu dla obszarów roboczych, bloków i komentarzy do obszaru roboczego.

  • DOM focus określa, który element DOM jest aktywny. Jest to niezbędne do pracy na poziomie elementów DOM. Na przykład czytniki ekranu przedstawiają informacje o elemencie, który jest obecnie aktywny w DOM, a klawisz Tab przesuwa (zmienia aktywność) z elementu DOM na element DOM.

Menedżer fokusu synchronizuje fokus Blockly i fokus DOM, więc gdy węzeł (komponent Blockly) ma fokus Blockly, jego bazowy element DOM ma fokus DOM i odwrotnie.

Aktywne i pasywne ustawianie ostrości

Skupienie w Blockly dzieli się na aktywne i pasywne. Aktywne zaznaczenie oznacza, że węzeł będzie otrzymywać dane wejściowe od użytkownika, np. naciśnięcie klawisza. Pasywne skupienie oznacza, że węzeł miał wcześniej aktywne skupienie, ale utracił je, gdy użytkownik przeszedł do węzła w innym drzewie (np. z obszaru roboczego do przybornika) lub całkowicie opuścił edytor Blockly. Jeśli drzewo odzyska fokus, węzeł z fokusem pasywnym odzyska fokus aktywny.

Każde drzewo ma osobny kontekst ostrości. Oznacza to, że fokus może mieć co najwyżej 1 węzeł w drzewie. To, czy fokus jest aktywny czy pasywny, zależy od tego, czy fokus jest ustawiony na drzewie. Na całej stronie może być co najwyżej 1 węzeł z aktywnym fokusem.

Menedżer fokusu używa różnych wyróżnień (klas CSS) w przypadku węzłów, które są aktywnie i pasywnie zaznaczone. Dzięki nim użytkownicy wiedzą, gdzie się znajdują i gdzie wrócą.

Efemeryczne skupienie

Istnieje też inny rodzaj ostrości, zwany ostrą chwilową. Oddzielne procesy, takie jak okna dialogowe lub edytory pól, wysyłają do menedżera fokusu żądanie tymczasowego fokusu. Gdy menedżer fokusu przyznaje fokus tymczasowy, zawiesza system fokusu. W praktyce oznacza to, że takie procesy mogą rejestrować zdarzenia związane z fokusem w DOM i na nie reagować bez obawy, że system fokusu również na nie zareaguje.

Gdy menedżer fokusu przyznaje fokus tymczasowy, zmienia aktywnie fokusowany węzeł na fokus pasywny. Przywraca aktywny fokus, gdy fokus tymczasowy zostanie zwrócony.

Przykłady

Poniższe przykłady pokazują, jak Blockly korzysta z systemu fokusowania. Powinny one pomóc Ci zrozumieć, jak Twój kod pasuje do systemu ostrości i jak może z niego korzystać.

Przenoszenie zaznaczenia za pomocą klawiatury

Załóżmy, że blok z 2 polami jest zaznaczony w Blockly, co wskazuje wyróżnienie (klasa CSS) na elemencie DOM bloku. Załóżmy teraz, że użytkownik naciśnie strzałkę w prawo:

  1. Wtyczka nawigacji za pomocą klawiatury:
    • Otrzymuje zdarzenie naciśnięcia klawisza.
    • Wysyła do systemu nawigacji (części podstawowej biblioteki Blockly) prośbę o przeniesienie zaznaczenia na „następny” komponent.
  2. System nawigacji:
    • Pyta menedżera zaznaczenia, który komponent jest zaznaczony w Blockly. Menedżer fokusu zwraca blok jako IFocusableNode.
    • Określa, że IFocusableNode jest BlockSvg i sprawdza jego reguły poruszania się po blokach, które mówią, że powinien przenieść fokus Blockly z bloku jako całości na pierwsze pole w bloku.
    • Nakazuje menedżerowi zaznaczenia przeniesienie zaznaczenia Blockly na pierwsze pole.
  3. Menedżer ostrości:
    • Aktualizuje stan, aby ustawić fokus Blockly na pierwszym polu.
    • Ustawia fokus DOM na element DOM pola.
    • Przenosi klasę wyróżnienia z elementu bloku do elementu pola.

Przenoszenie zaznaczenia za pomocą myszy

Załóżmy, że użytkownik kliknie drugie pole w bloku. Menedżer fokusu:

  1. Otrzymuje zdarzenie DOM focusout w elemencie DOM pierwszego pola i zdarzenie focusin w elemencie DOM drugiego pola.
  2. Określa, że element DOM, który otrzymał fokus, odpowiada drugiemu polu.
  3. Aktualizuje swój stan, aby ustawić fokus Blockly na drugim polu. (Menedżer fokusu nie musi ustawiać fokusu DOM, ponieważ przeglądarka już to zrobiła).
  4. Przenosi klasę wyróżnienia z elementu pierwszego pola do elementu drugiego pola.

Inne przykłady

Oto inne przykłady:

  • Gdy użytkownik przeciągnie blok z przybornika do obszaru roboczego, moduł obsługi zdarzeń myszy utworzy nowy blok i wywoła menedżera fokusu, aby ustawić fokus Blockly na tym bloku.

  • Gdy blok zostanie usunięty, jego metoda dispose wywołuje menedżera fokusu, aby przenieść fokus na blok nadrzędny.

  • Skróty klawiszowe używają znaku IFocusableNode, aby wskazać komponent Blockly, do którego odnosi się skrót.

  • Menu kontekstowe używają parametru IFocusableNode, aby określić komponent Blockly, w którym zostało wywołane menu.

Dostosowywanie i system ostrości

Dostosowując Blockly, musisz się upewnić, że Twój kod działa prawidłowo z systemem ostrości. Możesz też użyć systemu ostrości, aby zidentyfikować i ustawić aktualnie zaznaczony węzeł.

Bloki niestandardowe i zawartość przybornika

Najczęstszym sposobem dostosowywania Blockly jest definiowanie bloków niestandardowych i dostosowywanie zawartości przybornika. Żadne z tych działań nie ma wpływu na system ostrości.

Klasy niestandardowe

Klasy niestandardowe mogą wymagać implementacji jednego lub obu interfejsów fokusu (IFocusableTreeIFocusableNode). Nie zawsze jest to oczywiste.

Niektóre klasy zdecydowanie wymagają implementacji interfejsów zaznaczania. Obejmują one:

  • Klasa, która implementuje niestandardowy przybornik. Ta klasa musi implementować IFocusableTreeIFocusableNode.

  • Klasy, które tworzą widoczny komponent (np. pole lub ikonę), do którego użytkownicy mogą przejść. Te klasy muszą implementować interfejs IFocusableNode.

Niektóre klasy muszą implementować interfejs IFocusableNode, nawet jeśli nie tworzą widocznego komponentu lub tworzą widoczny komponent, do którego użytkownicy nie mogą przejść. Obejmują one:

  • Klasy, które implementują interfejs rozszerzający IFocusableNode.

    Na przykład ikona przenoszenia w wtyczce do nawigacji za pomocą klawiatury wyświetla strzałkę w 4 kierunkach, która wskazuje, że blok można przesuwać za pomocą klawiszy strzałek. Sama ikona nie jest widoczna (strzałka w 4 kierunkach to dymek) i użytkownicy nie mogą do niej przejść. Ikona musi jednak implementować interfejs IFocusableNode, ponieważ ikony implementują interfejsIIcon, a interfejsIIcon rozszerza interfejs IFocusableNode.

  • Klasy używane w interfejsie API, który wymaga IFocusableNode.

    Na przykład klasa FlyoutSeparator tworzy odstęp między 2 elementami w wyskakującym menu. Nie tworzy żadnych elementów DOM, więc nie ma widocznego komponentu i użytkownicy nie mogą do niego przejść. Musi jednak implementować interfejs IFocusableNode, ponieważ jest przechowywany w obiekcie FlyoutItem, a konstruktor FlyoutItem wymaga interfejsu IFocusableNode.

  • Klasy rozszerzające klasę, która implementuje interfejs IFocusableNode.

    Na przykład ToolboxSeparator rozszerza ToolboxItem, które implementuje IFocusableNode. Chociaż separatory w przyborniku mają widoczny komponent, użytkownicy nie mogą się po nich poruszać, ponieważ nie można ich używać i nie zawierają przydatnych treści.

Inne klasy tworzą widoczne komponenty, do których użytkownik może przejść, ale nie muszą implementować IFocusableNode. Obejmują one:

  • Klasy, które tworzą widoczny komponent zarządzający własnym fokusem, np. edytor pola lub okno. (Pamiętaj, że takie klasy muszą przejmować tymczasową aktywność po uruchomieniu i zwracać ją po zakończeniu. Użycie WidgetDiv lub DropDownDiv rozwiąże ten problem).

Niektóre klasy nie wchodzą w interakcję z systemem ostrości i nie muszą implementować interfejsów IFocusableTree ani IFocusableNode. Obejmują one:

  • Klasy, które tworzą widoczny komponent, do którego użytkownicy nie mogą przejść ani na którym nie mogą wykonać żadnej czynności, a który nie zawiera informacji, które mógłby wykorzystać czytnik ekranu. Na przykład czysto dekoracyjne tło w grze.

  • klasy całkowicie niezwiązane z systemem ostrości, np. klasy implementujące IMetricsManager lub IVariableMap;

Jeśli nie masz pewności, czy Twoja klasa będzie wchodzić w interakcję z systemem ostrości, przetestuj ją za pomocą wtyczki do nawigacji za pomocą klawiatury. Jeśli to się nie uda, może być konieczne wdrożenie IFocusableTree lub IFocusableNode. Jeśli się to uda, ale nadal masz wątpliwości, przeczytaj kod, który korzysta z Twojej klasy, aby sprawdzić, czy któryś z interfejsów jest wymagany lub czy występują inne interakcje.

Implementowanie interfejsów skupienia

Najprostszym sposobem implementacji interfejsów IFocusableTree lub IFocusableNode jest rozszerzenie klasy, która implementuje te interfejsy. Jeśli na przykład tworzysz niestandardowy zestaw narzędzi, rozszerz Toolbox, który implementuje IFocusableTreeIFocusableNode. Jeśli tworzysz pole niestandardowe, rozszerz Field, które implementuje IFocusableNode. Sprawdź, czy Twój kod nie koliduje z kodem interfejsu ostrości w klasie bazowej.

Jeśli rozszerzysz klasę, która implementuje interfejs fokusu, zwykle nie musisz zastępować żadnych metod. Najczęstszym wyjątkiem jest wartość IFocusableNode.canBeFocused, którą musisz zastąpić, jeśli nie chcesz, aby użytkownicy mogli przejść do Twojego komponentu.

Rzadziej zachodzi potrzeba zastąpienia metod wywołania zwrotnego fokusu (onTreeFocusonTreeBlurIFocusableTree oraz onNodeFocusonNodeBlurIFocusableNode). Pamiętaj, że próba zmiany fokusu (wywołanie FocusManager.focusNode lub FocusManager.focusTree) w tych metodach powoduje wyjątek.

Jeśli piszesz komponent niestandardowy od podstaw, musisz samodzielnie zaimplementować interfejsy fokusu. Więcej informacji znajdziesz w dokumentacji referencyjnej dotyczącej IFocusableTreeIFocusableNode.

Po zaimplementowaniu klasy przetestuj ją za pomocą wtyczki do nawigacji za pomocą klawiatury, aby sprawdzić, czy możesz (lub nie możesz) przejść do komponentu.

Korzystanie z menedżera ostrości

Niektóre klasy niestandardowe korzystają z menedżera fokusu. Najczęstsze powody to uzyskanie aktualnie wybranego węzła i skupienie się na innym węźle. Aby uzyskać menedżera fokusu, wywołaj funkcję Blockly.getFocusManager:

const focusManager = Blockly.getFocusManager();

Aby uzyskać aktualnie zaznaczony węzeł, wywołaj funkcję getFocusedNode:

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

Aby przenieść fokus na inny węzeł, wywołaj funkcję focusNode:

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

Aby przenieść zaznaczenie na drzewo, wywołaj funkcję focusTree. Spowoduje to również ustawienie zaznaczenia węzła na węzeł główny drzewa.

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

Innym częstym powodem używania menedżera fokusu jest przejmowanie i zwracanie tymczasowego fokusu. Funkcja takeEphemeralFocus zwraca lambdę, którą musisz wywołać, aby zwrócić tymczasowe ustawienie ostrości.

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

Jeśli używasz WidgetDiv lub DropDownDiv, zajmą się one tymczasowym zaznaczeniem.

Tabulatory

System zaznaczania ustawia punkt tabulacji (tabindex0) w elemencie głównym wszystkich drzew (główny obszar roboczy, przybornik i wysuwane obszary robocze). Umożliwia to użytkownikom poruszanie się po głównych obszarach edytora Blockly za pomocą klawisza Tab, a następnie (za pomocą wtyczki do nawigacji za pomocą klawiatury) poruszanie się po tych obszarach za pomocą klawiszy strzałek. Nie zmieniaj tych przystanków tabulacji, ponieważ utrudni to zarządzanie nimi menedżerowi zaznaczenia.

Zazwyczaj należy unikać ustawiania tabulatorów w innych elementach DOM używanych przez Blockly, ponieważ zakłóca to model Blockly, w którym klawisz Tab służy do przechodzenia między obszarami edytora, a klawisze strzałek – w tych obszarach. Dodatkowo takie tabulatory mogą nie działać zgodnie z oczekiwaniami. Dzieje się tak, ponieważ każdy węzeł, na którym można ustawić fokus, deklaruje element DOM jako element, na którym można ustawić fokus. Jeśli ustawisz tabulator na elemencie podrzędnym elementu, na którym można ustawić fokus, a użytkownik przejdzie do tego elementu za pomocą klawisza Tab, menedżer fokusu przeniesie fokus DOM na zadeklarowany element, na którym można ustawić fokus.

Możesz bezpiecznie ustawiać tabulatory w elementach aplikacji, które znajdują się poza edytorem Blockly. Gdy użytkownik przechodzi z edytora do takiego elementu, menedżer fokusu zmienia fokus Blockly z aktywnego na pasywny. Ze względu na ułatwienia dostępu ustaw właściwość tabindex na 0 lub -1, zgodnie z zaleceniami w opisie atrybutu tabindex w MDN.

Skupienie DOM

Ze względu na dostępność aplikacje powinny unikać wywoływania metody focus w elementach DOM. Użytkownicy czytników ekranu mogą się wtedy zdezorientować, ponieważ nagle zostają przeniesieni w nieznane miejsce w aplikacji.

Dodatkowym problemem jest to, że menedżer fokusu reaguje na zdarzenia fokusu, ustawiając fokus DOM na najbliższym przodku lub samym elemencie, który ma fokus i jest zadeklarowanym elementem, na którym można ustawić fokus. Może się on różnić od elementu, na którym wywołano funkcję focus. (Jeśli nie ma najbliższego przodka lub elementu, na którym można ustawić fokus, np. gdy funkcja focus jest wywoływana w przypadku elementu poza edytorem Blockly, menedżer fokusu po prostu zmienia aktywnie zaznaczony węzeł na pasywny fokus).

Elementy, które można ustawiać

Elementy z możliwością pozycjonowania to komponenty, które są umieszczane na obszarze roboczym i implementują interfejs IPositionable. Przykłady to kosz i plecak w wtyczce plecak. Elementy z możliwością pozycjonowania nie są jeszcze zintegrowane z systemem ostrości.