焦點系統會追蹤 Blockly 編輯器中使用者所在的位置 (焦點)。Blockly 和自訂程式碼會使用這個屬性,判斷目前哪個元件 (區塊、欄位、工具箱類別等) 具有焦點,並將焦點移至其他元件。
請務必瞭解焦點系統,確保自訂程式碼能與該系統正常運作。
架構
專注模式系統包含三個部分:
FocusManager
是單例,可協調 Blockly 中所有項目的焦點。Blockly 和自訂程式碼會使用這個函式,找出 Blockly 焦點所在的元件,以及將 Blockly 焦點移至其他元件。此外,它也會監聽 DOM 焦點事件、同步處理 Blockly 焦點和 DOM 焦點,並管理 CSS 類別,指出哪個元件具有焦點。焦點管理工具主要由 Blockly 使用。有時自訂程式碼會使用這個方法與焦點系統互動。
IFocusableTree
是 Blockly 編輯器中的獨立區域,例如工作區或工具箱。由可聚焦的節點組成,例如區塊和欄位。樹狀結構也可以有子樹狀結構。舉例來說,主工作區中某個區塊的變動器工作區,就是主工作區的子樹狀結構。IFocusableTree
主要由焦點管理員使用。除非您編寫自訂工具箱,否則可能不需要實作。IFocusableNode
是可聚焦的 Blockly 元件,例如區塊、欄位或工具箱類別。可聚焦節點具有顯示節點的 DOM 元素,且節點具有 Blockly 焦點時,該元素具有 DOM 焦點。請注意,樹狀結構也是可聚焦的節點。舉例來說,您可以著重於整個工作區。IFocusableNode
中的方法主要由焦點管理工具呼叫。IFocusableNode
本身是用來表示具有焦點的元件。舉例來說,當使用者選取方塊內容選單中的項目時,系統會將方塊做為IFocusableNode
傳遞至項目的回呼函式。如果您編寫自訂元件,可能需要實作
IFocusableNode
。
焦點類型
專注模式系統定義了多種不同類型的專注模式。
Blockly 焦點和 DOM 焦點
主要有兩種焦點類型:Blockly 焦點和 DOM 焦點。
Blockly focus 會指定哪個 Blockly 元件 (方塊、欄位、工具箱類別等) 具有焦點。您必須瞭解這項概念,才能在 Blockly 元件層級作業。舉例來說,鍵盤導覽外掛程式可讓使用者使用方向鍵,在元件之間移動,例如從區塊移到欄位。同樣地,內容選單系統會為目前的元件建構適當的選單,也就是為工作區、方塊和工作區註解建構不同的選單。
DOM 焦點會指定哪個 DOM 元素具有焦點。這是在 DOM 元素層級作業時的必要條件。舉例來說,螢幕閱讀器會呈現目前具有 DOM 焦點的元素相關資訊,而 Tab 鍵則會在 DOM 元素之間移動 (變更焦點)。
焦點管理工具會讓 Blockly 焦點和 DOM 焦點保持同步,因此當節點 (Blockly 元件) 具有 Blockly 焦點時,其基礎 DOM 元素會具有 DOM 焦點,反之亦然。
主動和被動焦點
Blockly 焦點進一步分為主動焦點和被動焦點。 「有效焦點」是指節點會接收使用者輸入內容,例如按鍵。被動焦點是指節點先前具有主動焦點,但使用者移至另一個樹狀結構中的節點 (例如從工作區移至工具箱),或完全離開 Blockly 編輯器時,節點就會失去焦點。如果樹狀結構重新取得焦點,被動聚焦的節點就會重新取得主動焦點。
每棵樹都有各自的焦點內容。也就是說,樹狀結構中最多只能有一個節點具有焦點。該焦點是主動還是被動,取決於樹狀結構是否具有焦點。整個頁面最多只能有一個節點處於焦點狀態。
焦點管理工具會針對主動和被動聚焦的節點使用不同的醒目顯示 (CSS 類別)。讓使用者瞭解自己目前的位置,以及返回的位置。
暫時模式
另一種焦點稱為「暫時性焦點」。對話方塊或欄位編輯器等個別工作流程會向焦點管理員要求暫時性焦點。焦點管理工具授予暫時性焦點時,會暫停焦點系統。從實務角度來看,這表示這類工作流程可以擷取 DOM 焦點事件並對其採取行動,而不必擔心焦點系統也可能對這些事件採取行動。
當焦點管理工具授予暫時性焦點時,會將主動聚焦的節點變更為被動焦點。當暫時性焦點傳回時,系統會還原有效焦點。
範例
以下範例說明 Blockly 如何使用焦點系統。這些範例應有助於您瞭解程式碼如何融入焦點系統,以及程式碼可能如何使用焦點系統。
使用鍵盤移動焦點
假設含有兩個欄位的區塊具有 Blockly 焦點,如區塊 DOM 元素上的醒目顯示 (CSS 類別) 所示。現在假設使用者按下向右箭頭:
- 鍵盤導覽外掛程式:
- 接收按鍵按下事件。
- 要求導覽系統 (Blockly 核心的一部分) 將焦點移至「下一個」元件。
- 導覽系統:
- 向焦點管理工具詢問哪個元件擁有 Blockly 焦點。焦點管理工具會以
IFocusableNode
形式傳回區塊。 - 判斷
IFocusableNode
是BlockSvg
,並查看其導覽區塊的規則,這些規則指出應將 Blockly 焦點從整個區塊移至區塊的第一個欄位。 - 告知焦點管理工具將 Blockly 焦點移至第一個欄位。
- 向焦點管理工具詢問哪個元件擁有 Blockly 焦點。焦點管理工具會以
- 焦點管理員:
- 更新狀態,將 Blockly 焦點設為第一個欄位。
- 將 DOM 焦點設在欄位的 DOM 元素上。
- 將醒目顯示類別從區塊的元素移至欄位的元素。
使用滑鼠移動焦點
現在假設使用者點選區塊中的第二個欄位。焦點管理員:
- 在第一個欄位的 DOM 元素上接收 DOM
focusout
事件,在第二個欄位的 DOM 元素上接收focusin
事件。 - 判斷收到焦點的 DOM 元素是否對應至第二個欄位。
- 更新其狀態,將 Blockly 焦點設在第二個欄位。(焦點管理工具不需要設定 DOM 焦點,因為瀏覽器已完成這項操作)。
- 將醒目顯示類別從第一個欄位的元素移至第二個欄位的元素。
其他範例
其他例子包括:
當使用者將積木從工具箱拖曳到工作區時,滑鼠事件處理常式會建立新積木,並呼叫焦點管理員,在該積木上設定 Blockly 焦點。
刪除區塊時,系統會呼叫其
dispose
方法,將焦點移至區塊的父項。鍵盤快速鍵會使用
IFocusableNode
識別快速鍵適用的 Blockly 元件。內容選單會使用
IFocusableNode
識別叫用選單的 Blockly 元件。
自訂項目和焦點系統
自訂 Blockly 時,請務必確保程式碼能與焦點系統正常運作。您也可以使用焦點系統,找出並設定目前聚焦的節點。
自訂積木和工具箱內容
自訂 Blockly 最常見的方式是定義自訂方塊,以及自訂工具箱的內容。這兩項操作都不會影響專注模式系統。
自訂類別
自訂類別可能需要實作一或兩個焦點介面 (IFocusableTree
和 IFocusableNode
)。但有時很難判斷是否需要這麼做。
有些類別顯然需要實作焦點介面。包括:
實作自訂工具箱的類別。這個類別需要實作
IFocusableTree
和IFocusableNode
。建立使用者可前往的顯示元件 (例如欄位或圖示) 的類別。這些類別必須實作
IFocusableNode
。
即使某些類別不會建立可見元件,或建立使用者無法前往的可見元件,也需要實作 IFocusableNode
。包括:
實作擴充
IFocusableNode
介面的類別。舉例來說,鍵盤導覽外掛程式中的移動圖示會顯示四向箭頭,表示可使用箭頭鍵移動區塊。圖示本身不會顯示 (四向箭頭是泡泡),使用者也無法前往該圖示。不過,圖示必須實作
IFocusableNode
,因為圖示會實作IIcon
,而IIcon
會擴充IFocusableNode
。需要
IFocusableNode
的 API 中使用的類別。舉例來說,
FlyoutSeparator
類別會在飛出視窗中的兩個項目之間建立間距。這不會建立任何 DOM 元素,因此沒有可見的元件,使用者也無法前往該元件。不過,由於它儲存在FlyoutItem
中,而FlyoutItem
建構函式需要IFocusableNode
,因此必須實作IFocusableNode
。擴充實作
IFocusableNode
的類別。舉例來說,
ToolboxSeparator
會擴充ToolboxItem
,而ToolboxItem
會實作IFocusableNode
。雖然工具箱分隔線有可見的元件,但使用者無法前往,因為分隔線無法執行動作,也沒有實用內容。
其他類別會建立使用者可前往的顯示元件,但不需要實作 IFocusableNode
。包括:
- 建立可見元件的類別,這類元件會管理自己的焦點,例如欄位編輯器或對話方塊。(請注意,這類別在開始時需要取得暫時性焦點,並在結束時傳回焦點。使用
WidgetDiv
或DropDownDiv
即可處理這項問題。)
最後,部分類別不會與焦點系統互動,也不需要實作 IFocusableTree
或 IFocusableNode
。包括:
建立使用者無法前往或操作的可見元件,且不含螢幕閱讀器可能使用的資訊。舉例來說,遊戲中的純裝飾性背景。
與焦點系統完全無關的類別,例如實作
IMetricsManager
或IVariableMap
的類別。
如果不確定類別是否會與焦點系統互動,請使用鍵盤導覽外掛程式進行測試。如果失敗,您可能需要實作 IFocusableTree
或 IFocusableNode
。如果成功,但您仍不確定,請閱讀使用您類別的程式碼,確認是否需要任一介面,或是否有任何其他互動。
實作焦點介面
如要實作 IFocusableTree
或 IFocusableNode
,最簡單的方法是擴充實作這些介面的類別。舉例來說,如果您要建立自訂工具箱,請擴充 Toolbox
,其中會實作 IFocusableTree
和 IFocusableNode
。如果您要建立自訂欄位,請擴充 Field
,這會實作 IFocusableNode
。請務必檢查程式碼是否會干擾基底類別中的焦點介面程式碼。
如果您擴充實作焦點介面的類別,通常不需要覆寫任何方法。最常見的例外狀況是 IFocusableNode.canBeFocused
,如果您不希望使用者前往元件,就必須覆寫這個函式。
較不常見的情況是需要覆寫焦點回呼方法 (IFocusableTree
和 onNodeFocus
中的 onTreeFocus
和 onTreeBlur
,以及 IFocusableNode
中的 onNodeBlur
)。請注意,嘗試從這些方法變更焦點 (呼叫 FocusManager.focusNode
或 FocusManager.focusTree
) 會導致例外狀況。
如果您從頭開始編寫自訂元件,就必須自行實作焦點介面。詳情請參閱 IFocusableTree
和 IFocusableNode
的參考說明文件。
實作類別後,請針對鍵盤導覽外掛程式進行測試,確認您是否可以導覽至元件。
使用焦點管理工具
部分自訂類別會使用焦點管理工具。最常見的原因是取得目前焦點節點,以及將焦點放在其他節點。如要取得焦點管理工具,請呼叫 Blockly.getFocusManager
:
const focusManager = Blockly.getFocusManager();
如要取得目前焦點所在的節點,請呼叫 getFocusedNode
:
const focusedNode = focusManager.getFocusedNode();
// Do something with the focused node.
如要將焦點移至其他節點,請呼叫 focusNode
:
// Move focus to a different block.
focusManager.focusNode(myOtherBlock);
如要將焦點移至樹狀結構,請呼叫 focusTree
。這也會將節點焦點設為樹狀結構的根節點。
// Move focus to the main workspace.
focusManager.focusTree(myMainWorkspace);
使用焦點管理工具的另一個常見原因是取得及傳回暫時性焦點。takeEphemeralFocus
函式會傳回 lambda,您必須呼叫該 lambda 才能傳回暫時性焦點。
const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();
如果您使用 WidgetDiv
或 DropDownDiv
,系統會為您處理臨時焦點。
定位點
焦點系統會在所有樹狀結構的根元素 (主工作區、工具箱和彈出式工作區) 上設定 Tab 鍵停駐點 (tabindex
的 0
)。這可讓使用者使用 Tab 鍵在 Blockly 編輯器的主要區域之間移動,然後 (使用鍵盤導覽外掛程式) 使用方向鍵在這些區域內移動。請勿變更這些定位點,否則焦點管理工具將無法管理這些定位點。
一般來說,您應避免在 Blockly 使用的其他 DOM 元素上設定定位點,因為這會干擾 Blockly 的模型,導致無法使用 Tab 鍵在編輯器區域之間導覽,也無法在這些區域內使用方向鍵。此外,這類定位點可能無法正常運作。這是因為每個可聚焦節點都會將 DOM 元素宣告為可聚焦元素。如果您在可聚焦元素的子項上設定定位點,且使用者將焦點移至該元素,焦點管理工具會將 DOM 焦點移至已宣告的可聚焦元素。
在 Blockly 編輯器外的應用程式元素上設定定位點是安全的。當使用者從編輯器分頁前往這類元素時,焦點管理員會將 Blockly 焦點從主動變更為被動。為確保無障礙功能,您應將 tabindex
屬性設為 0
或 -1
,如 MDN 的 tabindex
屬性說明中的警告訊息建議。
DOM 焦點
基於無障礙考量,應用程式應避免在 DOM 元素上呼叫 focus
方法。螢幕閱讀器使用者會突然移至應用程式中的不明位置,導致他們感到困惑。
另一個問題是,焦點管理工具會對焦點事件做出反應,在已宣告為可聚焦元素的焦點元素最近的祖先或自身上設定 DOM 焦點。這可能與呼叫 focus
的元素不同。(如果沒有最接近的可聚焦祖先或本身,例如在 Blockly 編輯器外部的元素上呼叫 focus
時,焦點管理工具只會將主動聚焦節點變更為被動聚焦)。
可放置的項目
Positionable 是位於工作區頂端的元件,並實作 IPositionable
。例如 backpack 外掛程式中的垃圾桶和背包。可定位項目尚未整合至焦點系統。