Contribute to this guide

guide深入探究焦点跟踪

焦点是所有键盘交互(事件)发生的地方。 对于任何使用键盘操作的网页部分来说,这都是一个重要的概念,无论是笔记本电脑的物理键盘还是智能手机的软件键盘。而 CKEditor 5 作为富文本编辑器,也不例外。

# 什么是焦点以及它对 CKEditor 意义何在?

# 什么是焦点?

每次单击文本字段或编辑器时,它会自动准备接收您的文本。它不再只是一个用于存放字母的静态容器,而是您可以使用键盘进行交互的东西。您可以通过看到熟悉的闪烁光标来识别它。这种称为焦点改变的微妙动作每天会发生数百次,因为您在浏览网页、键入搜索查询、与朋友聊天以及在网上购物时填写结账表单。

The animation shows the focus moving from one field to another when filling in the form.

聚焦文本字段感觉非常自然,我们通常不会过多考虑它。但是如果没有这个简单的操作,就无法键入文本。如果没有任何聚焦的字段,文本该去哪里呢?焦点向软件通知您的意图,它等同于上下文

# CKEditor 中的焦点

CKEditor 不仅仅是一个简单的文本字段。是的,它有用于键入文本的主要空间,但其他地方也允许您键入文本,例如链接 URL 字段或包含大量输入的表单,这些输入允许您配置表格的外观。

The animation showing the focused link URL input in CKEditor 5 with a blinking caret.

当多个地方可以接收焦点时,一定存在一些系统来发现和管理它。这些系统运行良好,例如,您不会发现自己处于 CKEditor 5 失去焦点(或模糊)而无法键入文本的情况。或者它们确保只要您继续编辑,就可以看到并使用编辑器工具栏。这些只是焦点管理系统必要的几个例子。现在应该也清楚了,它们对于编辑体验至关重要。

在本指南的后续章节中,我们将解释这些系统如何工作、什么使 CKEditor“可聚焦”以及如何开发新的编辑器功能以使其适应现有的焦点管理系统和模式。

  • 第一部分解释了编辑器 引擎 如何管理焦点以及在开发新功能时有哪些工具可以帮助您。
  • 第二部分中,我们将向您展示编辑器的 用户界面 如何跟踪焦点以及各种 UI 组件如何利用这一点,例如,提供可访问性。
  • 最后一部分中,我们将使用前面各节中的知识在一个现实场景中分析所有这些工具和系统如何协同工作。

# 编辑器引擎中的焦点

请记住,本章中的信息仅涉及编辑器引擎层。要了解用户界面中的焦点,请查看本指南的下一节

CKEditor 5 WYSIWYG 编辑器的主要可编辑区域可以通过 contenteditable DOM 属性进行聚焦。此属性告诉 Web 浏览器网页元素可以像任何其他文本字段一样编辑,这也意味着它必须能够接收焦点。

每个编辑视图的根节点都具有 contenteditable 属性。编辑视图使用 FocusObserver(了解有关视图观察器的更多信息)通过监听来自它们的本机 DOM focusblur 事件来跟踪可编辑元素中的焦点。

已经感到困惑了吗?请查看编辑器引擎指南,其中解释了编辑视图、可编辑元素以及 CKEditor 5 的其他构建块。

# 检查视图文档是否处于焦点状态

您可以使用视图文档的可观察对象 isFocused 属性来检查视图文档是否处于焦点状态。创建编辑器实例并执行以下代码

console.log( editor.editing.view.document.isFocused );

如果您从 Web 浏览器的开发者控制台运行此代码段,它应该返回 false,除非您设法保持编辑器处于焦点状态(例如,通过运行调试器并冻结 DOM)。这是因为当您切换到开发者工具以执行代码段时,编辑器会失去焦点。

您如何知道 isFocused 确实有效?由于它是可观察对象,因此您可以实时查看它的变化

editor.editing.view.document.on( 'change:isFocused', ( evt, data, isFocused ) => {
    console.log( `View document is focused: ${ isFocused }.` );
} );

单击编辑器的可编辑区域,然后单击其他位置 - 当您这样做时,isFocused 属性将改变其值。如果您运行带有 多个编辑根节点 的编辑器并在它们之间导航,也会发生同样的事情。

更重要的是,您还应该知道,当您将焦点集中到内容中的任何嵌套可编辑元素(例如,图像标题)时,isFocused 也会改变。听起来很奇怪,对吧?这是因为内容中的每个嵌套可编辑元素都具有 contenteditable 属性,对于 Web 浏览器来说,将您的光标移动到它里面意味着主可编辑元素已失去焦点,而嵌套可编辑元素已获得焦点。

# 如何聚焦编辑器?

聚焦编辑器最简单的方法是调用 editor.focus() 方法。

但是,您可能希望在执行特定操作(例如,单击按钮)时显式地聚焦 CKEditor 5 的可编辑区域。为此,请使用编辑视图的 focus() 方法

editor.editing.view.focus();

此代码段将焦点集中到具有选区的可编辑元素上。如果编辑器尚未获得焦点,这将聚焦第一个可编辑元素。如果编辑器有多个编辑根节点,而用户正在编辑内容,焦点将被带回到用户停止的地方。

聚焦编辑器不会更改其选区。如果您想聚焦编辑器并将光标移动到特定位置,您应该首先调用 editor.editing.view.focus(),然后使用 setSelection() 方法(模型写入器)来更改选区。

# 编辑器 UI 中的焦点

如果您阅读了本指南的上一节,您应该知道在编辑器引擎中已经有一个负责跟踪焦点的层。但是,由于编辑器框架是模块化的,因此该层只关心编辑器可编辑根节点中的焦点,并且对用户界面一无所知。这种粒度使得能够创建没有 UI 的功能齐全的编辑器。

CKEditor 5 的用户界面由多个组件组成,以树状结构组织。这棵树不仅决定了 UI 的逻辑结构(工具栏有一个下拉菜单,下拉菜单有一个按钮,按钮有一个图标等),还决定了它的行为,包括跟踪和维护焦点,因为用户会在界面中导航和与不同的部分进行交互。

感觉不知所措?请查看 UI 库指南,了解有关 CKEditor 5 UI 的工作原理及其主要构建块的一些基础知识。

总之,在 UI 级别单独跟踪焦点有两个主要原因:

  • 确保编辑器(作为一个整体)永远不会失去焦点,除非用户希望它失去焦点。
    例如,请查看 内联编辑器。只要用户在主可编辑区域编辑文本或在任何弹出窗口、下拉菜单或面板中配置其属性,主编辑器工具栏必须保持可见(因为焦点位于 UI 中的某个位置)。只有当用户完成编辑并移到网页上的其他位置时,工具栏才会消失。
  • 使 UI 可供使用屏幕阅读器和其他辅助技术的用户访问。
    这些用户不仅使用键盘编写文本,还使用键盘在工具栏按钮、面板、下拉菜单等之间导航。编辑器的 UI 必须不断跟踪当前哪个组件处于焦点状态,例如,允许使用 TabEsc 和箭头键进行导航。

# 工具和架构

焦点管理位于 用户界面元素树 旁边,其架构也基于对边界内及其子级内的用户操作做出响应的组件。请查看 经典编辑器 实例中的常见键盘导航场景。

The animation showing the focus moving as the user navigates to the heading drop–down in the toolbar.

以下是参与导航的焦点层以及每一层的简要概述:

The image showing the focus layers used during navigation.

  1. 焦点树的根是 ClassicEditorUI 类。它为整个编辑器创建一个 全局焦点跟踪器(您可以通过 editor.ui.focusTracker 访问它)。
  2. 当编辑文本时,您可以按 Alt+F10 键来聚焦主编辑器工具栏,这是第二个焦点层。
    • ToolbarView 组件带有一个焦点跟踪器,它会监视其子级,以便当用户使用键盘箭头在工具栏中导航时,可以清楚地知道哪个项目处于焦点状态。
    • 工具栏还使用 焦点循环器 提供连续导航。例如,当最后一个项目处于焦点状态时,导航到下一个项目会将焦点重新带回到工具栏的开头。
  3. 当用户选择工具栏项目时,将执行其 focus() 方法。这将我们带到第三层。
    • 工具栏的一些组件很简单(例如 ButtonView),其 DOM 元素是焦点树的叶子。但有些组件(例如,DropdownView)是容器,允许焦点更深入。每个下拉菜单都有一个可聚焦的按钮,并且可以在其 DropdownPanelView 中托管一个子视图。它不需要焦点跟踪器,因为用户只有两种方法可以导航它:向下导航到其面板或远离它。
  4. 当用户进入下拉菜单的面板时,他们会访问另一个(第四个)焦点层。这次是 ListView,它与 ToolbarView 一样,也需要焦点跟踪器和焦点循环器,以允许在项目之间进行平滑的键盘导航。
  5. 本示例中焦点树的最外层叶子是 ListItemView。它具有 focus() 方法,该方法在 DOM 中聚焦其元素。

以下是每个焦点层(UI 组件)使用的工具汇总:

焦点层 需要 焦点跟踪器 吗? 需要 按键处理程序 吗? 需要 焦点循环器 吗?
1 ClassicEditorUI
2 ToolbarView
3 DropdownView
4 ListView
5 ListItemView

大多数组件都有 焦点跟踪器,以便跟踪其内部的焦点。托管多个子级的某些组件还使用 焦点循环器按键处理程序 来帮助用户在它们之间导航。您可以在本指南的后续部分学习如何使用它们。

# 实现可聚焦的 UI 组件

任何 UI 视图 都可以是可聚焦的。要成为可聚焦的,视图必须实现 focus() 方法,该方法会聚焦 DOM 元素 以及在元素上设置的 tabindex="-1" 属性,该属性会阻止使用键盘进行本地导航(应由父级上的 焦点循环器 处理)。

tabindex="-1" 是一个本地 DOM 属性,它控制 DOM 元素是否可以聚焦(以及以何种顺序)。将其值设置为 "-1" 会告诉 Web 浏览器应将其从本地键盘导航中排除,并且只允许使用 JavaScript 聚焦它。由于您的组件将属于编辑器焦点管理系统,因此如果您想避免与 Web 浏览器的冲突,则应执行此操作。

import { View } from 'ckeditor5';

class MyListItemView extends View {
    constructor( locale, text ) {
        super( locale );

        // More initializations.
        // ...

        this.setTemplate( {
            tag: 'li',
            attributes: {
                tabindex: -1
            },
            children: [ text ]
        } );
    }

    // More methods.
    // ...

    focus() {
        this.element.focus();
    }
}

如果视图具有多个可聚焦的子级(例如列表),则 focus() 方法应聚焦第一个子级。

import { View } from 'ckeditor5';

class MyListView extends View {
    constructor( locale ) {
        super( locale );

        // More initializations.
        // ...

        this.items = this.createCollection();

        this.setTemplate( {
            tag: 'ul',
            children: this.items
        } );
    }

    // More methods.
    // ...

    focus() {
        if ( this.items.length ) {
            // This will call MyListItemView#focus().
            this.items.first.focus();
        }
    }
}

可聚焦的视图使得能够使用键盘导航 CKEditor 的界面成为可能。在 下一部分 中,您将学习父视图如何使用 FocusTracker 类跟踪其子级之间的焦点。

# 使用 FocusTracker

CKEditor 5 UI 中的关键焦点管理帮助器之一是 FocusTracker 类及其实例。它们在 DOM 级别工作,并提供了一个相当简单的 API:

  • 用于 add()remove() 跟踪的 DOM 元素的方法。
  • 一个可观察的 isFocused 属性,它告诉世界,跟踪的元素之一在 DOM 中具有焦点。
  • 一个可观察的 focusedElement 属性,它准确地说明了哪个 DOM 元素处于焦点状态。

焦点跟踪器会监听来自其跟踪的元素的 DOM focusblur 事件,并确定是否有任何元素当前处于焦点状态。作为经验法则,拥有多个可聚焦元素的 父级 应该有一个焦点跟踪器。

请记住,没有可聚焦子级或只有一个可聚焦子级的简单组件可能不需要焦点跟踪器。

# 关于全局焦点跟踪器的说明

每个编辑器实例都有一个全局焦点跟踪器,可以通过 editor.ui.focusTracker 访问。它是一个特殊的实例,将用户界面的所有部分(包括编辑根)粘合在一起,并存储整个编辑器实例的焦点状态。

您始终可以监听全局焦点跟踪器,并确定用户是否正在使用 UI。

editor.ui.focusTracker.on( 'change:isFocused', ( evt, data, isFocused ) => {
    console.log( `The editor is focused: ${ isFocused }.` );
} );

负责编辑器 UI 的类(ClassicEditorUIInlineEditorUI 等)必须将界面中的可聚焦构建块(例如,编辑器的可编辑根(编辑发生的地方)或工具栏)告知全局焦点跟踪器。

同样适用于引入任何可聚焦 UI 的插件。例如,上下文气球 插件提供了由项目中的许多功能共享的浮动气球(链接编辑、图像工具、小部件工具栏等),它也会在全局焦点跟踪器中注册气球。在开发自定义编辑器插件时,请牢记这一点。

仅当全局焦点跟踪器了解 UI 中所有可聚焦块时,才会维护编辑器焦点的连续性。它们在 DOM 中的位置无关紧要:在可编辑根旁边,在 “主体”集合 中,甚至在其他地方。对于全局焦点跟踪器来说,它们都是同一个编辑器的一部分。

查看 焦点状态分析部分,了解全局焦点跟踪器在实际场景中的工作原理。

# 实际示例

查看以下具有多个项目的列表示例,这是焦点跟踪器的典型用例。

import { View, FocusTracker } from 'ckeditor5';

class MyListView extends View {
    constructor( locale ) {
        super( locale );

        // More initializations.
        // ...

        // A view collection containing items of the list.
        this.items = this.createCollection();

        // Setting the template.
        // ...

        // The instance of the focus tracker that tracks focus in #items.
        this.focusTracker = new FocusTracker();
    }

    // More methods.
    // ...
}

为了确保焦点跟踪器实例和 items 视图集合保持同步,请创建侦听器,以便在添加新子视图或删除一些子视图时更新跟踪器(视图集合会触发事件)。最好的方法是在 render() 方法中完成。

// Previously imported packages.
// ...

class MyListView extends View {
    constructor( locale ) {
        // More initializations.
        // ...
    }

    // More methods.
    // ...

    render() {
        super.render();

        // Children added before rendering should be known to the #focusTracker.
        for ( const item of this.items ) {
            this.focusTracker.add( item.element );
        }

        // Make sure items added to the collection are recognized by the #focusTracker.
        this.items.on( 'add', ( evt, item ) => {
            this.focusTracker.add( item.element );
        } );

        // Make sure items removed from the collection are ignored by the #focusTracker.
        this.items.on( 'remove', ( evt, item ) => {
            this.focusTracker.remove( item.element );
        } );
    }

    // More methods.
    // ...
}

现在,MyListView 可以跟踪聚焦的子级,现在是时候使用键盘帮助用户导航它们了。在下一部分中,您将创建一个 按键处理程序,这将使您更接近目标。

# 使用 KeystrokeHandler

KeystrokeHandler 帮助器类允许注册按键回调。它在编辑器 UI 中的许多视图中被用于许多目的。例如,它负责在 Alt+F10 键按下时聚焦工具栏,或者当您在选定文本上按 Ctrl+L 时打开链接弹出窗口表单。

但是,在焦点管理的上下文中,它被 焦点循环器 使用,您将在下一部分中了解它。您可以在 API 文档中了解更多关于 KeystrokeHandler 类的信息,但现在,您只需要知道如何在继续之前创建和初始化它即可。

import { FocusCycler, View, FocusTracker, KeystrokeHandler } from 'ckeditor5';

export default class MyListView extends View {
    constructor( locale ) {
        super( locale );

        // More initializations.
        // ...

        // The keystroke handler that will help the focus cycler respond to the keystrokes.
        this.keystrokes = new KeystrokeHandler();
    }

    render() {
        super.render();

        // Setting the focus tracker.
        // ...

        // Start listening for the keystrokes coming from #element, which will allow
        // the focus cycler to handle the keyboard navigation.
        this.keystrokes.listenTo( this.element );
    }

    destroy() {
        // Stop listening to all keystrokes when the view is destroyed.
        this.keystrokes.destroy();
    }

    // More methods.
    // ...
}

# 使用 FocusCycler

FocusCycler 帮助用户使用按键(箭头键、TabReturnEsc 等)导航用户界面。此帮助器类是专为托管多个子级的组件而创建的,例如列表、工具栏或表单。它会在其子级之间循环,因此,例如,如果最后一个子级处于焦点状态,并且用户想要向前移动,则焦点将移回第一个子级。它还支持具有可变(动态)数量的可聚焦子级的组件。

每个焦点循环器实例与一个焦点跟踪器和一个按键处理程序协同工作。前者提供焦点的当前状态,而后者帮助循环器与按键集成,例如,在按下向下箭头键时将焦点移至列表中的下一个项目。

查看使用焦点循环器、按键处理程序和焦点跟踪器实例的示例列表类,以启用键盘导航。首先,必须创建所有助手

import { FocusCycler, View, FocusTracker, KeystrokeHandler } from 'ckeditor5';

class MyListView extends View {
    constructor( locale ) {
        super( locale );

        // More initializations.
        // ...

        // The view collection containing items of the list.
        this.items = this.createCollection();

        // The instance of the focus tracker that tracks focus in #items.
        this.focusTracker = new FocusTracker();

        // The keystroke handler that will help the focus cycler respond to the keystrokes.
        this.keystrokes = new KeystrokeHandler();

        // The focus cycler that glues it all together.
        this.focusCycler = new FocusCycler( {
            focusables: this.items,
            focusTracker: this.focusTracker,
            keystrokeHandler: this.keystrokes,
            actions: {
                // Navigate list items backward using the arrow up key.
                focusPrevious: 'arrowup',

                // Navigate toolbar items forward using the arrow down key.
                focusNext: 'arrowdown'
            }
        } );
    }

    // More methods.
    // ...
}

与本指南的先前部分类似,在render()方法中馈送焦点跟踪器并将其与列表项集合同步。由于MyListView#element在此阶段已经渲染,因此这也是开始监听键盘事件的最佳时机

// Previously imported packages.
// ...

class MyListView extends View {
    constructor( locale ) {
        // More initializations.
        // ...
    }

    render() {
        super.render();

        // Items added before rendering should be known to the #focusTracker.
        for ( const item of this.items ) {
            this.focusTracker.add( item.element );
        }

        // Make sure items added to the collection are recognized by the #focusTracker.
        this.items.on( 'add', ( evt, item ) => {
            this.focusTracker.add( item.element );
        } );

        // Make sure items removed from the collection are ignored by the #focusTracker.
        this.items.on( 'remove', ( evt, item ) => {
            this.focusTracker.remove( item.element );
        } );

        // Start listening for the keystrokes coming from #element, which will allow
        // the #focusCycler to handle the keyboard navigation.
        this.keystrokes.listenTo( this.element );
    }

    focus() {
        if ( this.items.length ) {
            // This will call MyListItemView#focus().
            this.items.first.focus();
        }
    }

    destroy() {
        // Stop listening to all keystrokes when the view is destroyed.
        this.keystrokes.destroy();
    }

    // More methods.
    // ...
}

# 共同使用所有焦点助手

包含多个项目视图并支持跨项目键盘导航(当获得焦点时)的列表类的完整代码如下

import { FocusCycler, View, FocusTracker, KeystrokeHandler } from 'ckeditor5';

class MyListView extends View {
    constructor( locale ) {
        super( locale );

        // The view collection containing items of the list.
        this.items = this.createCollection();

        // The instance of the focus tracker that tracks focus in #items.
        this.focusTracker = new FocusTracker();

        // The keystroke handler that will help the focus cycler respond to the keystrokes.
        this.keystrokes = new KeystrokeHandler();

        // The focus cycler that glues it all together.
        this.focusCycler = new FocusCycler( {
            focusables: this.items,
            focusTracker: this.focusTracker,
            keystrokeHandler: this.keystrokes,
            actions: {
                // Navigate list items backward using the arrow up key.
                focusPrevious: 'arrowup',

                // Navigate toolbar items forward using the arrow down key.
                focusNext: 'arrowdown'
            }
        } );

        // More intializations.
        // ...

        this.setTemplate( {
                tag: 'ul',
                children: this.items
        } );
    }

    render() {
        super.render();

        // Items added before rendering should be known to the #focusTracker.
        for ( const item of this.items ) {
            this.focusTracker.add( item.element );
        }

        // Make sure items added to the collection are recognized by the #focusTracker.
        this.items.on( 'add', ( evt, item ) => {
            this.focusTracker.add( item.element );
        } );

        // Make sure items removed from the collection are ignored by the #focusTracker.
        this.items.on( 'remove', ( evt, item ) => {
            this.focusTracker.remove( item.element );
        } );

        // Start listening for the keystrokes coming from #element, which will allow
        // the #focusCycler to handle the keyboard navigation.
        this.keystrokes.listenTo( this.element );
    }

    focus() {
        if ( this.items.length ) {
            // This will call MyListItemView#focus().
            this.items.first.focus();
        }
    }

    destroy() {
        // Stop listening to all keystrokes when the view is destroyed.
        this.keystrokes.destroy();
    }
}

class MyListItemView extends View {
    constructor( locale, text ) {
        super( locale );

        // More initializations.
        // ...

        this.setTemplate( {
            tag: 'li',
            attributes: {
                tabindex: -1
            },
            children: [ text ]
        } );
    }

    // More methods.
    // ...

    focus() {
        this.element.focus();
    }
}

您可以在现有编辑器的上下文中快速运行它,方法如下

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        // The editor's configuration.
        // ...
    } )
    .then( editor => {
        const list = new MyListView( editor.locale );

        // Create two example children.
        const firstChild = new MyListItemView( editor.locale, 'First child' );
        const secondChild = new MyListItemView( editor.locale, 'Second child' );

        // Add children to the list.
        list.items.add( firstChild );
        list.items.add( secondChild );

        // Render the list and put it in the DOM.
        list.render();
        editor.ui.view.body.add( list );

        // Focus the list. This should focus the first child.
        // Use up and down keyboard arrows to cycle across the list items.
        list.focus();
    } )
    .catch( err => {
        console.error( err.stack );
    } );

现在您可以使用键盘箭头循环焦点列表项

The animation showing the focus cycling across the list items.

# 焦点状态分析

本节包含对内联编辑器中常见焦点导航场景的分析。虽然上一节侧重于构成焦点管理系统的工具,但这一次我们将重点关注它们的状态。这将有助于您更好地理解所有小部件在大图中的工作方式。

# 场景

查看以下场景,其中鼠标和键盘都用于导航编辑器的界面

The animation showing the focus navigation across the inline editor UI.

以下是场景的步骤

  1. 编辑器未获得焦点(焦点位于网页上的其他地方)。
  2. 使用鼠标将焦点移至可编辑区域。主工具栏显示出来,因为单击了链接,所以链接操作视图也弹出来。
  3. 使用Tab键将焦点移至气泡中的链接预览LinkActionsView的子项)。
  4. 使用Tab键将焦点移至“编辑链接”按钮
  5. 使用Space键执行“编辑链接”按钮。焦点移动到LinkFormView中的输入框
  6. 使用Tab键从链接 URL 字段移至“保存”按钮
  7. 使用Tab键从“保存”按钮移至“取消”按钮
  8. 使用Space键执行“取消”按钮并关闭编辑表单。
  9. 使用Esc键关闭链接气泡并返回到可编辑区域。

在此场景中共有 3 个焦点跟踪器实例起作用

  1. EditorUI#focusTracker“全局”焦点跟踪器),
  2. LinkActionsView#focusTracker
  3. LinkFormView#focusTracker

让我们看看它们如何对用户操作做出反应(状态在每一步之后都记录下来)

步骤 EditorUI#focusTracker LinkActionsView#focusTracker LinkFormView#focusTracker
isFocused focusedElement isFocused focusedElement isFocused focusedElement
1 null null null
2 editable null null
3 气泡面板

URL 预览 null
4 气泡面板 “编辑链接”按钮 null
5 气泡面板 null URL 输入框
6 气泡面板 null “保存”按钮
7 气泡面板 null “取消”按钮
8 editable null null
9 editable null null

# 结论

  • 全局焦点跟踪器(您可以通过editor.ui.focusTracker访问的跟踪器)始终了解焦点状态,即使焦点位于 UI 的最远区域。
    • 它不知道更深层的哪些元素获得了焦点(例如“编辑链接”按钮)。它只知道焦点转移到了哪里(例如,从可编辑区域到气泡面板)。
    • 它缺乏关于链接 UI 中焦点的精确信息,因为这是链接 UI 层的焦点跟踪器的职责。
    • 所有编辑器功能始终可以在需要时依赖全局焦点跟踪器。例如,只要全局焦点跟踪器知道焦点位于编辑器中的某个位置,主编辑器工具栏就会显示。
  • 您可以看到,焦点管理是模块化的:LinkActionsViewLinkFormView只知道焦点,只要它们其中一个子项拥有焦点。
  • 属于LinkActionsViewLinkFormView的焦点跟踪器精确地知道哪个元素拥有焦点。这是它们感兴趣的区域,与编辑器的全局焦点跟踪器不同,它们需要该信息才能允许使用键盘进行导航。