포커스 시스템은 Blockly 편집기에서 사용자의 위치 (포커스)를 추적합니다. Blockly와 맞춤 코드에서 현재 포커스가 있는 구성요소 (블록, 필드, 도구 상자 카테고리 등)를 확인하고 포커스를 다른 구성요소로 이동하는 데 사용됩니다.
맞춤 코드가 포커스 시스템과 올바르게 작동하도록 하려면 포커스 시스템을 이해하는 것이 중요합니다.
아키텍처
집중 모드 시스템은 다음 세 부분으로 구성됩니다.
FocusManager
는 Blockly 전체에서 포커스를 조정하는 싱글톤입니다. Blockly와 맞춤 코드에서 Blockly 포커스가 있는 구성요소를 확인하고 Blockly 포커스를 다른 구성요소로 이동하는 데 사용됩니다. 또한 DOM 포커스 이벤트를 수신 대기하고, Blockly 포커스와 DOM 포커스를 동기화하며, 포커스가 있는 구성요소를 나타내는 CSS 클래스를 관리합니다.포커스 관리자는 주로 Blockly에서 사용됩니다. 맞춤 코드가 포커스 시스템과 상호작용하는 데 사용되기도 합니다.
IFocusableTree
는 작업공간이나 도구 상자와 같은 Blockly 편집기의 독립적인 영역입니다. 블록, 필드와 같은 포커스 가능 노드로 구성됩니다. 트리에는 하위 트리도 있을 수 있습니다. 예를 들어 기본 작업공간의 블록에 있는 뮤테이터 작업공간은 기본 작업공간의 하위 트리입니다.IFocusableTree
는 주로 포커스 관리자가 사용합니다. 맞춤 툴박스를 작성하지 않는 한 구현할 필요가 없습니다.IFocusableNode
는 블록, 필드, 도구 상자 카테고리와 같이 포커스가 있을 수 있는 Blockly 구성요소입니다. 포커스 가능한 노드에는 노드를 표시하고 노드에 Blockly 포커스가 있을 때 DOM 포커스가 있는 DOM 요소가 있습니다. 트리도 포커스 가능한 노드입니다. 예를 들어 전체 워크스페이스에 집중할 수 있습니다.IFocusableNode
의 메서드는 주로 포커스 관리자에 의해 호출됩니다.IFocusableNode
자체는 포커스가 있는 구성요소를 나타내는 데 사용됩니다. 예를 들어 사용자가 블록의 컨텍스트 메뉴에서 항목을 선택하면 블록이 항목의 콜백 함수에IFocusableNode
로 전달됩니다.맞춤 구성요소를 작성하는 경우
IFocusableNode
을 구현해야 할 수 있습니다.
포커스 유형
집중 모드 시스템은 여러 가지 유형의 집중 모드를 정의합니다.
Blockly 포커스 및 DOM 포커스
두 가지 주요 포커스 유형은 Blockly 포커스와 DOM 포커스입니다.
Blockly 포커스는 포커스가 있는 Blockly 구성요소 (블록, 필드, 도구 상자 카테고리 등)를 지정합니다. Blockly 구성요소 수준에서 작업하는 데 필요합니다. 예를 들어 키보드 탐색 플러그인을 사용하면 사용자가 화살표 키를 사용하여 구성요소 간에 이동할 수 있습니다(예: 블록에서 필드로 이동). 마찬가지로 컨텍스트 메뉴 시스템은 현재 구성요소에 적합한 메뉴를 빌드합니다. 즉, 작업공간, 블록, 작업공간 댓글에 대해 서로 다른 메뉴를 빌드합니다.
DOM 포커스는 포커스가 있는 DOM 요소를 지정합니다. DOM 요소 수준에서 작업하는 데 필요합니다. 예를 들어 화면 리더는 현재 DOM 포커스가 있는 요소에 관한 정보를 표시하고 탭은 DOM 요소에서 DOM 요소로 이동합니다 (포커스 변경).
포커스 관리자는 Blockly 포커스와 DOM 포커스를 동기화하므로 노드(Blockly 구성요소)에 Blockly 포커스가 있으면 기본 DOM 요소에 DOM 포커스가 있고 그 반대의 경우도 마찬가지입니다.
활성 및 수동 포커스
Blockly 포커스는 활성 포커스와 수동 포커스로 더 세분화됩니다. 활성 포커스는 노드가 키 누르기와 같은 사용자 입력을 수신한다는 의미입니다. 소극적 포커스는 이전에 노드에 활성 포커스가 있었지만 사용자가 다른 트리 (예: 작업 공간에서 도구 상자로 이동)의 노드로 이동하거나 Blockly 편집기에서 완전히 벗어날 때 포커스가 손실되었음을 의미합니다. 트리가 포커스를 다시 얻으면 수동으로 포커스가 지정된 노드가 활성 포커스를 다시 얻습니다.
각 트리에는 별도의 포커스 컨텍스트가 있습니다. 즉, 트리에서 최대 하나의 노드만 포커스를 가질 수 있습니다. 포커스가 활성인지 수동인지는 트리에 포커스가 있는지에 따라 달라집니다. 전체 페이지에서 포커스가 활성 상태인 노드는 최대 하나만 있을 수 있습니다.
포커스 관리자는 활성 및 수동으로 포커스가 맞춰진 노드에 서로 다른 강조 표시 (CSS 클래스)를 사용합니다. 이를 통해 사용자는 현재 위치와 돌아갈 위치를 알 수 있습니다.
임시 집중
일시적 포커스라는 또 다른 유형의 포커스가 있습니다. 대화상자나 필드 편집기와 같은 별도의 워크플로는 포커스 관리자로부터 일시적인 포커스를 요청합니다. 포커스 관리자가 임시 포커스를 부여하면 포커스 시스템이 일시 중지됩니다. 실용적인 관점에서 이는 이러한 워크플로가 포커스 시스템이 DOM 포커스 이벤트에 대해서도 작동할 수 있다는 걱정 없이 DOM 포커스 이벤트를 캡처하고 이에 따라 작동할 수 있음을 의미합니다.
포커스 관리자가 임시 포커스를 부여하면 활성 포커스 노드가 수동 포커스로 변경됩니다. 임시 포커스가 반환되면 활성 포커스를 복원합니다.
예
다음 예에서는 Blockly가 포커스 시스템을 사용하는 방법을 보여줍니다. 이러한 가이드는 코드가 포커스 시스템에 어떻게 적합한지, 코드가 포커스 시스템을 어떻게 사용할 수 있는지 이해하는 데 도움이 됩니다.
키보드로 포커스 이동
두 필드가 있는 블록에 Blockly 포커스가 있다고 가정해 보겠습니다. 이는 블록의 DOM 요소에 있는 강조 표시(CSS 클래스)로 표시됩니다. 이제 사용자가 오른쪽 화살표를 누른다고 가정해 보겠습니다.
- 키보드 탐색 플러그인:
- 키 누름 이벤트를 수신합니다.
- 탐색 시스템 (핵심 Blockly의 일부)에 포커스를 '다음' 구성요소로 이동하도록 요청합니다.
- 내비게이션 시스템은 다음을 충족해야 합니다.
- 포커스 관리자에게 어떤 구성요소에 Blockly 포커스가 있는지 묻습니다. 포커스 관리자는 블록을
IFocusableNode
로 반환합니다. IFocusableNode
이BlockSvg
임을 확인하고 블록 탐색 규칙을 살펴봅니다. 이 규칙에 따르면 Blockly 포커스를 전체 블록에서 블록의 첫 번째 필드로 이동해야 합니다.- 포커스 관리자에게 Blockly 포커스를 첫 번째 필드로 이동하도록 지시합니다.
- 포커스 관리자에게 어떤 구성요소에 Blockly 포커스가 있는지 묻습니다. 포커스 관리자는 블록을
- 포커스 관리자:
- 첫 번째 필드에 Blockly 포커스를 설정하도록 상태를 업데이트합니다.
- 필드의 DOM 요소에 DOM 포커스를 설정합니다.
- 강조 표시 클래스를 블록의 요소에서 필드의 요소로 이동합니다.
마우스로 포커스 이동
이제 사용자가 블록의 두 번째 필드를 클릭한다고 가정해 보겠습니다. 포커스 관리자는 다음을 충족해야 합니다.
- 첫 번째 필드의 DOM 요소에서 DOM
focusout
이벤트를 수신하고 두 번째 필드의 DOM 요소에서focusin
이벤트를 수신합니다. - 포커스를 받은 DOM 요소가 두 번째 필드에 해당한다고 판단합니다.
- 두 번째 필드에 Blockly 포커스를 설정하도록 상태를 업데이트합니다. (브라우저에서 이미 DOM 포커스를 설정했으므로 포커스 관리자가 DOM 포커스를 설정할 필요가 없습니다.)
- 강조 표시 클래스를 첫 번째 필드의 요소에서 두 번째 필드의 요소로 이동합니다.
기타 예
예를 들면 다음과 같습니다.
사용자가 도구 상자에서 작업 공간으로 블록을 드래그하면 마우스 이벤트 핸들러가 새 블록을 만들고 포커스 관리자를 호출하여 해당 블록에 Blockly 포커스를 설정합니다.
블록이 삭제되면
dispose
메서드가 포커스 관리자를 호출하여 포커스를 블록의 상위 요소로 이동합니다.단축키는
IFocusableNode
를 사용하여 단축키가 적용되는 Blockly 구성요소를 식별합니다.컨텍스트 메뉴는
IFocusableNode
를 사용하여 메뉴가 호출된 Blockly 구성요소를 식별합니다.
맞춤설정 및 포커스 시스템
Blockly를 맞춤설정할 때는 코드가 포커스 시스템과 올바르게 작동하는지 확인해야 합니다. 포커스 시스템을 사용하여 현재 포커스가 있는 노드를 식별하고 설정할 수도 있습니다.
맞춤 블록 및 도구 상자 콘텐츠
Blockly를 맞춤설정하는 가장 일반적인 방법은 맞춤 블록을 정의하고 도구 상자의 콘텐츠를 맞춤설정하는 것입니다. 이러한 작업은 포커스 시스템에 영향을 미치지 않습니다.
커스텀 클래스
맞춤 클래스는 포커스 인터페이스(IFocusableTree
및 IFocusableNode
)를 하나 또는 둘 다 구현해야 할 수 있습니다. 이 경우를 항상 명확하게 알 수는 없습니다.
일부 클래스는 포커스 인터페이스를 구현해야 합니다. 예를 들면 다음과 같습니다.
맞춤 도구 상자를 구현하는 클래스입니다. 이 클래스는
IFocusableTree
및IFocusableNode
을 구현해야 합니다.사용자가 탐색할 수 있는 표시되는 구성요소 (예: 필드 또는 아이콘)를 만드는 클래스입니다. 이러한 클래스는
IFocusableNode
를 구현해야 합니다.
일부 클래스는 표시되는 구성요소를 만들지 않거나 사용자가 탐색할 수 없는 표시되는 구성요소를 만들더라도 IFocusableNode
를 구현해야 합니다. 예를 들면 다음과 같습니다.
IFocusableNode
를 확장하는 인터페이스를 구현하는 클래스예를 들어 키보드 탐색 플러그인의 이동 아이콘에는 화살표 키로 블록을 이동할 수 있음을 나타내는 4방향 화살표가 표시됩니다. 아이콘 자체가 표시되지 않으며 (4방향 화살표는 풍선임) 사용자가 아이콘으로 이동할 수 없습니다. 하지만 아이콘은
IFocusableNode
를 구현해야 합니다. 아이콘이IIcon
를 구현하고IIcon
가IFocusableNode
를 확장하기 때문입니다.IFocusableNode
가 필요한 API에서 사용되는 클래스입니다.예를 들어
FlyoutSeparator
클래스는 플라이아웃의 두 항목 사이에 간격을 만듭니다. DOM 요소를 만들지 않으므로 표시되는 구성요소가 없으며 사용자가 탐색할 수 없습니다. 하지만FlyoutItem
에 저장되고FlyoutItem
생성자에는IFocusableNode
가 필요하므로IFocusableNode
를 구현해야 합니다.IFocusableNode
를 구현하는 클래스를 확장하는 클래스예를 들어
ToolboxSeparator
는IFocusableNode
를 구현하는ToolboxItem
을 확장합니다. 툴박스 구분자에는 표시되는 구성요소가 있지만 사용자는 이를 조작할 수 없고 유용한 콘텐츠가 없으므로 탐색할 수 없습니다.
다른 클래스는 사용자가 탐색할 수 있는 표시되는 구성요소를 만들지만 IFocusableNode
를 구현할 필요는 없습니다. 예를 들면 다음과 같습니다.
- 필드 편집기나 대화상자와 같이 자체 포커스를 관리하는 표시되는 구성요소를 만드는 클래스입니다. 이러한 클래스는 시작할 때 일시적 포커스를 가져와야 하고 종료할 때 반환해야 합니다.
WidgetDiv
또는DropDownDiv
을 사용하면 이 문제가 해결됩니다.)
마지막으로 일부 클래스는 포커스 시스템과 상호작용하지 않으며 IFocusableTree
또는 IFocusableNode
를 구현할 필요가 없습니다. 예를 들면 다음과 같습니다.
사용자가 탐색하거나 조작할 수 없고 스크린 리더가 사용할 수 있는 정보가 포함되지 않은 표시되는 구성요소를 만드는 클래스입니다. 예를 들어 게임의 순전히 장식적인 배경이 있습니다.
IMetricsManager
또는IVariableMap
를 구현하는 클래스와 같이 포커스 시스템과 완전히 관련이 없는 클래스
클래스가 포커스 시스템과 상호작용하는지 확실하지 않다면 키보드 탐색 플러그인으로 테스트하세요. 이 방법이 실패하면 IFocusableTree
또는 IFocusableNode
을 구현해야 할 수도 있습니다. 성공했지만 여전히 확실하지 않은 경우 클래스를 사용하는 코드를 읽어 인터페이스가 필요한지 또는 다른 상호작용이 있는지 확인합니다.
포커스 인터페이스 구현
IFocusableTree
또는 IFocusableNode
을 구현하는 가장 쉬운 방법은 이러한 인터페이스를 구현하는 클래스를 확장하는 것입니다. 예를 들어 맞춤 도구 상자를 만드는 경우 IFocusableTree
및 IFocusableNode
을 구현하는 Toolbox
를 확장합니다. 커스텀 필드를 만드는 경우 IFocusableNode
을 구현하는 Field
를 확장합니다. 코드가 기본 클래스의 포커스 인터페이스 코드를 방해하지 않는지 확인하세요.
포커스 인터페이스를 구현하는 클래스를 확장하는 경우 일반적으로 메서드를 재정의할 필요가 없습니다. 가장 일반적인 예외는 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
함수는 임시 포커스를 반환하기 위해 호출해야 하는 람다를 반환합니다.
const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();
WidgetDiv
또는 DropDownDiv
을 사용하는 경우 임시 포커스를 처리합니다.
탭 정지
포커스 시스템은 모든 트리 (기본 작업공간, 도구 상자, 플라이아웃 작업공간)의 루트 요소에 탭 정지 (0
의 tabindex
)를 설정합니다. 이를 통해 사용자는 탭 키를 사용하여 Blockly 편집기의 기본 영역을 탐색한 다음 (키보드 탐색 플러그인을 사용하여) 화살표 키를 사용하여 해당 영역 내에서 탐색할 수 있습니다. 포커스 관리자가 탭 정지를 관리하는 기능을 방해하므로 이러한 탭 정지를 변경하지 마세요.
일반적으로 Blockly에서 사용하는 다른 DOM 요소에 탭 정지를 설정하면 안 됩니다. 이렇게 하면 탭 키를 사용하여 편집기 영역 간에 이동하고 화살표 키를 사용하여 해당 영역 내에서 이동하는 Blockly 모델이 방해되기 때문입니다. 또한 이러한 탭 정지가 의도한 대로 작동하지 않을 수 있습니다. 각 포커스 가능 노드가 DOM 요소를 포커스 가능 요소로 선언하기 때문입니다. 포커스 가능 요소의 하위 요소에 탭 정지를 설정하고 사용자가 해당 요소로 탭하면 포커스 관리자가 DOM 포커스를 선언된 포커스 가능 요소로 이동합니다.
Blockly 편집기 외부에 있는 애플리케이션의 요소에 탭 정지를 설정해도 안전합니다. 사용자가 편집기에서 이러한 요소로 탭하면 포커스 관리자가 Blockly 포커스를 활성에서 수동으로 변경합니다. 접근성을 위해 MDN의 tabindex
속성 설명에 나온 경고에 따라 tabindex
속성을 0
또는 -1
로 설정해야 합니다.
DOM 포커스
접근성상의 이유로 애플리케이션은 DOM 요소에서 focus
메서드를 호출하지 않아야 합니다. 이렇게 하면 스크린 리더 사용자가 갑자기 애플리케이션의 알 수 없는 위치로 이동하므로 혼란스러워집니다.
추가 문제는 포커스 관리자가 선언된 포커스 가능 요소인 포커스가 있는 요소의 가장 가까운 상위 요소 또는 자체에 DOM 포커스를 설정하여 포커스 이벤트에 반응한다는 것입니다. 이는 focus
가 호출된 요소와 다를 수 있습니다. (Blockly 편집기 외부의 요소에서 focus
가 호출되는 경우와 같이 가장 가까운 포커스 가능 상위 요소나 자기 자신이 없는 경우 포커스 관리자는 활성 포커스 노드를 수동 포커스로 변경합니다.)
위치 지정 가능
위치 지정 가능 요소는 작업공간 위에 배치되고 IPositionable
를 구현하는 구성요소입니다.
예로는 백팩 플러그인의 휴지통과 백팩이 있습니다.
위치 지정 가능 항목은 아직 포커스 시스템에 통합되지 않았습니다.