Tabbable
'use client';
import React from 'react';
import type { PlateElementProps } from '@udecode/plate/react';
import { cn } from '@udecode/cn';
import { Plate, useFocused, useSelected } from '@udecode/plate/react';
import { TabbablePlugin } from '@udecode/plate-tabbable/react';
import { editorPlugins } from '@/components/editor/plugins/editor-plugins';
import { useCreateEditor } from '@/components/editor/use-create-editor';
import { tabbableValue } from '@/components/values/tabbable-value';
import { Editor, EditorContainer } from '@/components/plate-ui/editor';
import { PlateElement } from '@/components/plate-ui/plate-element';
export default function TabbableDemo() {
const editor = useCreateEditor({
plugins: [
...editorPlugins,
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>
);
}
Installation
npm install @udecode/plate-tabbable
Usage
import { TabbablePlugin } from '@udecode/plate-tabbable/react';
const plugins = [
// ...otherPlugins,
TabbablePlugin,
];
Conflicts with other plugins
The Tabbable plugin may cause issues with other plugins that handle the Tab
key, such as:
- Lists
- Code blocks
- Indent plugin
Use the query
option to disable the Tabbable plugin when the Tab
key should be handled by another plugin:
query: (editor) => {
const inList = findNode(editor, { match: { type: ListItemPlugin.key } });
const inCodeBlock = findNode(editor, { match: { type: CodeBlockPlugin.key } });
return !inList && !inCodeBlock;
},
Alternatively, if you're using the Indent plugin, you can enable the Tabbable plugin only when a specific type of node is selected, such as voids:
query: (editor) => !!findNode(editor, {
match: (node) => isVoid(editor, node),
}),
Non-void Slate nodes
One TabbableEntry
will be created for each tabbable DOM element in the editor, as determined using the tabbable NPM package. The list of tabbables is then filtered using isTabbable
.
By default, isTabbable
only returns true for entries inside void Slate nodes. You can override isTabbable
to add support for DOM elements contained in other types of Slate node:
// Enable tabbable DOM elements inside CUSTOM_ELEMENT
isTabbable: (tabbableEntry) => (
tabbableEntry.slateNode.type === CUSTOM_ELEMENT ||
isVoid(editor, tabbableEntry.slateNode)
),
DOM elements outside the editor
In some circumstances, you may want to allow users to tab from the editor to a DOM element rendered outside the editor, such as an interactive popover.
To do this, override insertTabbableEntries
to return an array of TabbableEntry
objects, one for each DOM element outside the editor that you want to include in the tabbable list. The slateNode
and path
of the TabbableEntry
should refer to the Slate node the user's cursor will be inside when the DOM element should be tabbable to.
Set the globalEventListener
option to true
to make sure the Tabbable plugin is able to return the user's focus to the editor.
// Add buttons inside .my-popover to the list of tabbables
globalEventListener: true,
insertTabbableEntries: (editor) => {
const [selectedNode, selectedNodePath] = getNodeEntry(editor, editor.selection);
return [
...document.querySelectorAll('.my-popover > button'),
].map((domNode) => ({
domNode,
slateNode: selectedNode,
path: selectedNodePath,
}));
},
Plugins
TabbablePlugin
Plugin for managing tab order between tabbable elements.
- Default:
() => true
- Default:
false
- Default:
() => []
- Default:
(tabbableEntry) => isVoid(editor, tabbableEntry.slateNode)
Enable/disable plugin dynamically.
Add event listener to document instead of editor.
Add additional tabbable entries outside editor.
Determine if element should be tabbable.