可聚焦元素 (Tabbable)
Loading...
Files
components/tabbable-demo.tsx
'use client';
import * as React from 'react';
import type { PlateElementProps } from 'platejs/react';
import { TabbablePlugin } from '@platejs/tabbable/react';
import {
Plate,
PlateElement,
useFocused,
usePlateEditor,
useSelected,
} from 'platejs/react';
import { cn } from '@/lib/utils';
import { EditorKit } from '@/components/editor/editor-kit';
import { tabbableValue } from '@/registry/examples/values/tabbable-value';
import { Editor, EditorContainer } from '@/components/ui/editor';
export default function TabbableDemo() {
const editor = usePlateEditor({
plugins: [
...EditorKit,
TabbablePlugin.configure({
node: { component: TabbableElement, isElement: true, isVoid: true },
}),
],
value: tabbableValue,
});
return (
<Plate editor={editor}>
<EditorContainer variant="demo">
<Editor />
</EditorContainer>
</Plate>
);
}
export function TabbableElement({ children, ...props }: PlateElementProps) {
const selected = useSelected();
const focused = useFocused();
return (
<PlateElement {...props}>
<div
className={cn(
'mb-2 p-2',
selected && focused
? 'border-2 border-blue-500'
: 'border border-gray-200'
)}
contentEditable={false}
>
<p>This is a void element.</p>
<button type="button">Button 1</button>{' '}
<button type="button">Button 2</button>
</div>
{children}
</PlateElement>
);
}
套件使用
安装
最快捷的方式是使用 TabbableKit
,它包含预配置的 TabbablePlugin
和智能查询逻辑,可避免与其他插件冲突。
'use client';
import { TabbablePlugin } from '@platejs/tabbable/react';
import { KEYS } from 'platejs';
export const TabbableKit = TabbablePlugin.configure(({ editor }) => ({
node: {
isElement: true,
},
options: {
query: () => {
if (editor.api.isAt({ start: true }) || editor.api.isAt({ end: true }))
return false;
return !editor.api.some({
match: (n) => {
return !!(
(n.type &&
[
KEYS.codeBlock,
KEYS.li,
KEYS.listTodoClassic,
KEYS.table,
].includes(n.type as any)) ||
n.listStyleType
);
},
});
},
},
override: {
enabled: {
indent: false,
},
},
}));
添加套件
import { createPlateEditor } from 'platejs/react';
import { TabbableKit } from '@/components/editor/plugins/tabbable-kit';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
...TabbableKit,
],
});
手动配置
安装
pnpm add @platejs/tabbable
添加插件
import { TabbablePlugin } from '@platejs/tabbable/react';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
TabbablePlugin,
],
});
配置插件
import { TabbablePlugin } from '@platejs/tabbable/react';
import { createPlateEditor } from 'platejs/react';
import { KEYS } from 'platejs';
const editor = createPlateEditor({
plugins: [
// ...其他插件,
TabbablePlugin.configure({
options: {
query: (event) => {
// 在列表或代码块中禁用
const inList = editor.api.some({ match: { type: KEYS.li } });
const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });
return !inList && !inCodeBlock;
},
globalEventListener: true,
isTabbable: (tabbableEntry) =>
editor.api.isVoid(tabbableEntry.slateNode),
},
}),
],
});
options.query
: 根据编辑器状态动态启用/禁用插件的函数options.globalEventListener
: 为true
时,将事件监听器添加到document而非编辑器options.isTabbable
: 判断哪些元素应包含在标签顺序中的函数
高级用法
与其他插件的冲突
Tabbable插件可能会与处理Tab
键的其他插件产生冲突,例如:
- 列表插件
- 代码块插件
- 缩进插件
使用query
选项在Tab
键应由其他插件处理时禁用Tabbable插件:
query: (event) => {
const inList = editor.api.some({ match: { type: KEYS.li } });
const inCodeBlock = editor.api.some({ match: { type: KEYS.codeBlock } });
return !inList && !inCodeBlock;
},
如果使用缩进插件,可以仅在选中特定类型节点(如void节点)时启用Tabbable插件:
query: (event) => !!editor.api.some({
match: (node) => editor.api.isVoid(node),
}),
非空Slate节点
将为编辑器中的每个可聚焦DOM元素创建一个TabbableEntry
,使用tabbable NPM包确定。然后使用isTabbable
过滤可聚焦列表。
默认情况下,isTabbable
仅对void Slate节点内的entry返回true。可以覆盖isTabbable
以支持其他类型Slate节点中包含的DOM元素:
// 启用CUSTOM_ELEMENT内的可聚焦DOM元素
isTabbable: (tabbableEntry) => (
tabbableEntry.slateNode.type === CUSTOM_ELEMENT ||
editor.api.isVoid(tabbableEntry.slateNode)
),
编辑器外部的DOM元素
某些情况下,可能需要允许用户从编辑器切换到外部渲染的DOM元素(如交互式弹出框)。
为此,覆盖insertTabbableEntries
返回TabbableEntry
对象数组,每个对象对应一个要包含在可聚焦列表中的外部DOM元素。TabbableEntry
的slateNode
和path
应引用当DOM元素可聚焦时用户光标所在的Slate节点。
将globalEventListener
选项设为true
以确保Tabbable插件能将用户焦点返回到编辑器。
例如,如果DOM元素在选中链接时出现,slateNode
和path
应为该链接的节点。
// 将.my-popover内的按钮添加到可聚焦列表
globalEventListener: true,
insertTabbableEntries: (event) => {
const [selectedNode, selectedNodePath] = editor.api.node(editor.selection);
return [
...document.querySelectorAll('.my-popover > button'),
].map((domNode) => ({
domNode,
slateNode: selectedNode,
path: selectedNodePath,
}));
},
插件
TabbablePlugin
管理可聚焦元素间标签顺序的插件。
类型
TabbableEntry
定义可聚焦entry的属性。