如何在触发特殊字符后切换焦点至下拉面板并实现插件导航
问题:斜杠命令下拉面板焦点切换失败,无法键盘导航
需求与现状
- 需求:在编辑器输入特殊字符(
.)触发斜杠命令下拉面板后,将焦点从编辑器切换至面板,并实现插件间的键盘导航 - 当前问题:下拉面板能正常打开,但焦点未切换到面板,无法通过键盘上下键导航插件
相关实现代码
_getPluginListItemsDefinitons() { const editor = this.editor; const locale = editor.locale; this._dropdown = createDropdown(locale); this._commandItems = []; const commandNames = ['bold','italic','underline','horizontalLine','bulletedList','numberedList','insertTable','codeBlock','paragraph','table','subscript','superscript','widgetBox']; commandNames.forEach((commandName) => { const command = editor.commands.get(commandName); if (command) { const buttonModel = { type: 'button', model: { label: commandName.charAt(0).toUpperCase() + commandName.slice(1), withText: true, icon: icons[commandName] || icons.cog, commandName: commandName, }, }; this._commandItems.push(buttonModel); } }); this._commandItems.sort((a, b) => { return a.model.label.localeCompare(b.model.label); }); this._addItemsToDropdown(this._commandItems); this._createCustomToClass(); this._dropdown.isOpen = true; console.log("dropdown",this._dropdown) if(this._dropdown.isOpen === true){ this._dropdown.render(); this.focusTracker = new FocusTracker(); console.log("focusTracker", this.focusTracker) const elementFocus = this._dropdown.panelView; this.focusTracker.isFocused = true this.focusTracker.focusedElement = elementFocus this.focusables = new ViewCollection(); console.log("focusables", this.focusables) const focusableElements = this._dropdown.element.querySelectorAll('button, input, [tabindex]:not([tabindex="-1"])'); focusableElements.forEach(element => { const view = new View(element); this.focusables.add(view); }); const firstButton = focusableElements[1]; if (firstButton) { firstButton.focus(); firstButton.style.setProperty('background-color', '#007bff', 'important'); firstButton.style.setProperty('color', '#ffff', 'important'); } this.keystrokes = new KeystrokeHandler(); this.focusCycler = new FocusCycler({ focusables: focusableElements, focusTracker: this._dropdown.panelView, keystrokeHandler: this.keystrokes, actions: { focusPrevious: ['arrowup'], focusNext: ['arrowdown'] } }); console.log("focus cycler", this.focusCycler); this._dropdown.panelView.on('keydown', (evt, data) => { const keyCode = data.keyCode; if (keyCode === 38) { this.focusCycler.focusPrevious(); data.preventDefault(); } else if (keyCode === 40) { this.focusCycler.focusNext(); data.preventDefault(); } }); } this.listenTo(this._dropdown, 'execute', (evt, data) => { const commandName = evt.source.commandName; if (commandName) { this.customExecution(commandName); if (!this._dropdown.isOpen) { editor.editing.view.focus(); } } this._dropdown.isOpen = false; this.hideDropdown(); }); }
问题分析与修复方案
原代码的核心问题在于FocusTracker、FocusCycler的使用不符合CKEditor API规范,以及焦点设置时机错误,以下是针对性修复:
关键修复点
修正FocusTracker的使用:
不能直接赋值isFocused和focusedElement,需通过add()方法注册要跟踪的元素,FocusTracker会自动监听焦点状态变化。修正FocusCycler参数:
focusables必须传入ViewCollection实例而非原生DOM数组;focusTracker需传入实例化的FocusTracker对象,而非面板视图本身。确保DOM挂载后再设置焦点:
面板render()后需等待元素插入DOM树再调用focus(),可通过setTimeout或编辑器的change方法确保DOM就绪,同时主动让编辑器失焦。简化键盘事件处理:
利用KeystrokeHandler的API绑定快捷键,而非直接监听keydown事件,更符合CKEditor的生态逻辑。
修复后的代码
_getPluginListItemsDefinitons() { const editor = this.editor; const locale = editor.locale; this._dropdown = createDropdown(locale); this._commandItems = []; const commandNames = ['bold','italic','underline','horizontalLine','bulletedList','numberedList','insertTable','codeBlock','paragraph','table','subscript','superscript','widgetBox']; commandNames.forEach((commandName) => { const command = editor.commands.get(commandName); if (command) { const buttonModel = { type: 'button', model: { label: commandName.charAt(0).toUpperCase() + commandName.slice(1), withText: true, icon: icons[commandName] || icons.cog, commandName: commandName, }, }; this._commandItems.push(buttonModel); } }); this._commandItems.sort((a, b) => { return a.model.label.localeCompare(b.model.label); }); this._addItemsToDropdown(this._commandItems); this._createCustomToClass(); this._dropdown.isOpen = true; if(this._dropdown.isOpen === true){ this._dropdown.render(); // 1. 正确初始化FocusTracker this.focusTracker = new FocusTracker(); this.focusables = new ViewCollection(); // 2. 收集可聚焦元素并注册到跟踪器 const focusableElements = this._dropdown.element.querySelectorAll('button, input, [tabindex]:not([tabindex="-1"])'); focusableElements.forEach(element => { const view = new View(element); this.focusables.add(view); this.focusTracker.add(element); }); // 3. 正确初始化FocusCycler this.keystrokes = new KeystrokeHandler(); this.keystrokes.listenTo(this._dropdown.panelView); this.focusCycler = new FocusCycler({ focusables: this.focusables, focusTracker: this.focusTracker, keystrokeHandler: this.keystrokes, actions: { focusPrevious: 'arrowup', focusNext: 'arrowdown' } }); // 4. DOM就绪后设置焦点到第一个元素 editor.editing.view.change(writer => { setTimeout(() => { const firstFocusable = this.focusables.first; if (firstFocusable) { firstFocusable.element.focus(); firstFocusable.element.style.setProperty('background-color', '#007bff', 'important'); firstFocusable.element.style.setProperty('color', '#fff', 'important'); } // 让编辑器失焦,确保焦点切换到面板 editor.editing.view.document.blur(); }, 0); }); } this.listenTo(this._dropdown, 'execute', (evt, data) => { const commandName = evt.source.commandName; if (commandName) { this.customExecution(commandName); if (!this._dropdown.isOpen) { editor.editing.view.focus(); } } this._dropdown.isOpen = false; this.hideDropdown(); }); }
额外排查点
- 若焦点仍无法切换,检查编辑器是否有焦点锁定机制,需临时允许焦点切换到面板
- 确认
_addItemsToDropdown方法正确添加按钮,且按钮元素默认tabindex=0(可聚焦) - 可在
focus()后打印document.activeElement,确认焦点是否真的落在目标元素上
内容的提问来源于stack exchange,提问作者Rubasri
相关产品推荐
相关产品推荐

