docs/migration/v48.mdx
PlateElement, PlateLeaf and PlateText HTML attributes are moved from top-level props to attributes prop, except className, style and as. Migration:// From
<PlateElement
{...props}
ref={ref}
contentEditable={false}
>
{children}
</PlateElement>
// To
<PlateElement
{...props}
ref={ref}
attributes={{
...props.attributes,
contentEditable: false,
}}
>
{children}
</PlateElement>
nodeProps prop from PlateElement, PlateLeaf, PlateText. It has been merged into attributes prop.node.props should return the props directly instead of inside nodeProps object. Migration:// From
node: {
props: ({ element }) => ({
nodeProps: {
colSpan: element?.attributes?.colspan,
rowSpan: element?.attributes?.rowspan,
},
});
}
// To
node: {
props: ({ element }) => ({
colSpan: element?.attributes?.colspan,
rowSpan: element?.attributes?.rowspan,
});
}
asChild prop from PlateElement, PlateLeaf, PlateText. Use as prop instead.elementToAttributes, leafToAttributes, textToAttributes props from PlateElement, PlateLeaf, PlateText.DefaultElement, DefaultLeaf, DefaultText. Use PlateElement, PlateLeaf, PlateText instead.PlateRenderElementProps, PlateRenderLeafProps, PlateRenderTextProps. Use PlateElementProps, PlateLeafProps, PlateTextProps instead.PlateElement, PlateLeaf, PlateText to @udecode/plate-core. No migration needed if you're importing from @udecode/plate.#4225 by @bbyiringiro –
hocuspocusProviderOptions with the new providers array. See examples below.Before:
YjsPlugin.configure({
options: {
cursorOptions: {
/* ... */
},
hocuspocusProviderOptions: {
url: 'wss://hocuspocus.example.com',
name: 'document-1',
// ... other Hocuspocus options
},
},
});
After (Hocuspocus only):
YjsPlugin.configure({
options: {
cursors: {
/* ... */
},
providers: [
{
type: 'hocuspocus',
options: {
url: 'wss://hocuspocus.example.com',
name: 'document-1',
// ... other Hocuspocus options
},
},
],
},
});
After (Hocuspocus + WebRTC):
YjsPlugin.configure({
options: {
cursors: {
/* ... */
},
providers: [
{
type: 'hocuspocus',
options: {
url: 'wss://hocuspocus.example.com',
name: 'document-1',
},
},
{
type: 'webrtc',
options: {
roomName: 'document-1',
// signaling: ['wss://signaling.example.com'], // Optional
},
},
],
},
});
UnifiedProvider interface that enables custom provider implementations (e.g., IndexedDB for offline persistence).cursorOptions to cursors.yjsOptions into options.
yjsOptions directly into the main options object.YjsAboveEditable. You should now call init and destroy manually:React.useEffect(() => {
if (!mounted) return;
// Initialize Yjs connection and sync
editor.getApi(YjsPlugin).yjs.init({
id: roomName, // Or your document identifier
value: INITIAL_VALUE, // Your initial editor content
});
// Destroy connection on component unmount
return () => {
editor.getApi(YjsPlugin).yjs.destroy();
};
}, [editor, mounted, roomName]); // Add relevant dependencies
#4174 by @felixfeng33 – #### New Features
<u>underline</u>slate nodes => MDAST nodes => markdown stringallowedNodes: Whitelist specific nodesdisallowedNodes: Blacklist specific nodesallowNode: Custom function to filter nodesrules option for customizing serialization and deserialization rules, including custom mdx supportremarkPlugins option to use remark pluginsPlugin Options
Removed options:
elementRules use rules insteadtextRules use rules insteadindentList now automatically detects if the IndentList plugin is usedsplitLineBreaks deserialize onlyelementRules and textRules options
rules.key.deserialize insteadExample migration:
export const markdownPlugin = MarkdownPlugin.configure({
options: {
disallowedNodes: [SuggestionPlugin.key],
rules: {
// For textRules
[BoldPlugin.key]: {
mark: true,
deserialize: (mdastNode) => ({
bold: true,
text: node.value || '',
}),
},
// For elementRules
[EquationPlugin.key]: {
deserialize: (mdastNode, options) => ({
children: [{ text: '' }],
texExpression: node.value,
type: EquationPlugin.key,
}),
},
},
remarkPlugins: [remarkMath, remarkGfm],
},
});
editor.api.markdown.deserialize
remarkPlugins insteadserializeMdNodes
editor.markdown.serialize({ value: nodes }) insteadSerializeMdOptions due to new serialization process
slate nodes => mdslate nodes => md-ast => mdnodesbreakTagcustomNodesignoreParagraphNewlinelistDepthmarkFormatsulListStyleTypesignoreSuggestionTypeMigration example for SerializeMdOptions.customNodes and SerializeMdOptions.nodes:
export const markdownPlugin = MarkdownPlugin.configure({
options: {
rules: {
// Ignore all `insert` type suggestions
[SuggestionPlugin.key]: {
mark: true,
serialize: (slateNode: TSuggestionText, options): mdast.Text => {
const suggestionData = options.editor
.getApi(SuggestionPlugin)
.suggestion.suggestionData(node);
return suggestionData?.type === 'insert'
? { type: 'text', value: '' }
: { type: 'text', value: node.text };
},
},
// For elementRules
[EquationPlugin.key]: {
serialize: (slateNode) => ({
type: 'math',
value: node.texExpression,
}),
},
},
remarkPlugins: [remarkMath, remarkGfm],
},
});
#4122 by @zbeyens – Migrated from prismjs to highlight.js + lowlight for syntax highlighting.
CodeBlockPlugin: remove prism option. Use lowlight option instead:import { all, createLowlight } from 'lowlight';
const lowlight = createLowlight(all);
CodeBlockPlugin.configure({
options: {
lowlight,
},
});
defaultLanguagesyntax option. Just omit lowlight option to disable syntax highlighting.syntaxPopularFirst option. Control this behavior in your own components.useCodeBlockCombobox, useCodeBlockElement, useCodeSyntaxLeaf, useToggleCodeBlockButton. The logic has been moved to the components.#4064 by @felixfeng33 – This is a rewrite of the comments plugin removing UI logic (headless).
Plugin Options
options.commentsoptions.myUserIdoptions.usersComponents
CommentDeleteButtonCommentEditActionsCommentEditButtonCommentEditCancelButtonCommentEditSaveButtonCommentEditTextareaCommentNewSubmitButtonCommentNewTextareaCommentResolveButtonCommentsPositionerCommentUserNameAPI
findCommentNode → api.comment.node()findCommentNodeById → api.comment.node({ id })getCommentNodeEntries → api.comment.nodes()getCommentNodesById → api.comment.nodes({ id })removeCommentMark → tf.comment.remove()unsetCommentNodesById → tf.comment.unsetMark({ id })getCommentFragmentgetCommentUrlgetElementAbsolutePositiongetCommentPositiongetCommentCount to exclude draft commentsState Management
CommentProvider - users should implement their own state management – block-discussion.tsxuseHooksComments to UI registry – comments-plugin.tsxuseActiveCommentNodeuseCommentsResolveduseCommentAddButtonuseCommentItemContentuseCommentLeafuseCommentsShowResolvedButtonuseFloatingCommentsContentStateuseFloatingCommentsStateTypes
CommentUserTComment to UI registry – comment.tsx#4064 by @felixfeng33 – Note: This plugin is currently in an experimental phase and breaking changes may be introduced without a major version bump.
findSuggestionNode use findSuggestionProps.ts insteadaddSuggestionMark.tsuseHooksSuggestion.ts as we've updated the activeId logic to no longer depend on useEditorSelectorzustand-x@6
eventEditorSelectors -> EventEditorStore.geteventEditorActions -> EventEditorStore.setuseEventEditorSelectors -> useEventEditorValue(key)jotai-x@2
usePlateEditorStore -> usePlateStoreusePlateActions -> usePlateSeteditor.setPlateState, use usePlateSet insteadusePlateSelectors -> usePlateValueusePlateStates -> usePlateStateeditor.useOption, ctx.useOption -> usePluginOption(plugin, key, ...args)editor.useOptions, ctx.useOptions -> usePluginOption(plugin, 'state')usePluginOptions(plugin, selector) to select plugin options (Zustand way).extendOptions. Those were mixed up with the options state, leading to potential conflicts and confusion.
extendSelectorsplugin.selectors instead of plugin.options, but this does not change how you access those: using editor.getOption(plugin, 'selectorName'), ctx.getOption('selectorName') or above hooks.PluginConfig, we're adding a 5th generic type for it.// Before:
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{ selectedIds?: Set<string>; } & BlockSelectionSelectors,
>;
// After:
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{ selectedIds?: Set<string>; },
{}, // API
{}, // Transforms
BlockSelectionSelectors, // Selectors
}>
#4048 by @zbeyens – Upgrade to zustand-x@2. Migration needed only if you use one of these stores:
ImagePreviewStoreFloatingMediaStore#4048 by @zbeyens – Upgrade to jotai-x@2. Migration needed only if you use usePlaceholderStore
#4048 by @zbeyens – Move store state selectedCells and selectedTables from useTableStore to TablePlugin options store. This fixes the issue to get access to those state outside a table element (e.g. the toolbar)
#4048 by @zbeyens – Upgrade to jotai-x@2. Migration needed only if you use useTableStore
No breaking changes. Upgraded all dependencies to the latest version.
#3920 by @zbeyens – This package is now deprecated and will be renamed to @udecode/plate. Migration:
@udecode/plate-common and install @udecode/plate'@udecode/plate-common' with '@udecode/plate',Plugin normalizeInitialValue now returns void instead of Value. When mutating nodes, keep their references (e.g., use Object.assign instead of spread).
Editor methods have moved to editor.tf and editor.api. They still exist at the top level for slate backward compatibility, but are no longer redundantly typed. If you truly need the top-level method types, extend your editor type with LegacyEditorMethods (e.g. editor as Editor & LegacyEditorMethods). Since these methods can be overridden by extendEditor, with..., or slate plugins, consider migrating to the following approaches:
// For overriding existing methods only:
overrideEditor(({ editor, tf: { deleteForward }, api: { isInline } }) => ({
transforms: {
deleteForward(options) {
// ...conditional override
deleteForward(options);
},
},
api: {
isInline(element) {
// ...conditional override
return isInline(element);
},
},
}));
This was previously done in extendEditor using top-level methods, which still works but now throws a type error due to the move to editor.tf/editor.api. A workaround is to extend your editor with LegacyEditorMethods.
Why? Having all methods at the top-level (next to children, marks, etc.) would clutter the editor interface. Slate splits transforms in three places (editor, Editor, and Transforms), which is also confusing. We've reorganized them into tf and api for better DX, but also to support transform-only middlewares in the future. This also lets us leverage extendEditorTransforms, extendEditorApi, and overrideEditor to modify those methods.
Migration example:
// From:
export const withInlineVoid: ExtendEditor = ({ editor }) => {
const { isInline, isSelectable, isVoid, markableVoid } = editor;
const voidTypes: string[] = [];
const inlineTypes: string[] = [];
editor.pluginList.forEach((plugin) => {
if (plugin.node.isInline) {
inlineTypes.push(plugin.node.type);
}
if (plugin.node.isVoid) {
voidTypes.push(plugin.node.type);
}
});
editor.isInline = (element) => {
return inlineTypes.includes(element.type as any)
? true
: isInline(element);
};
editor.isVoid = (element) => {
return voidTypes.includes(element.type as any) ? true : isVoid(element);
};
return editor;
};
export const InlineVoidPlugin = createSlatePlugin({
key: 'inlineVoid',
extendEditor: withInlineVoid,
});
// After (using overrideEditor since we're only overriding existing methods):
export const withInlineVoid: OverrideEditor = ({
api: { isInline, isSelectable, isVoid, markableVoid },
editor,
}) => {
const voidTypes: string[] = [];
const inlineTypes: string[] = [];
editor.pluginList.forEach((plugin) => {
if (plugin.node.isInline) {
inlineTypes.push(plugin.node.type);
}
if (plugin.node.isVoid) {
voidTypes.push(plugin.node.type);
}
});
return {
api: {
isInline(element) {
return inlineTypes.includes(element.type as any)
? true
: isInline(element);
},
isVoid(element) {
return voidTypes.includes(element.type as any)
? true
: isVoid(element);
},
},
};
};
export const InlineVoidPlugin = createSlatePlugin({
key: 'inlineVoid',
}).overrideEditor(withInlineVoid);
editor.redecorate to editor.api.redecorateTypes:
TRenderElementProps to RenderElementPropsTRenderLeafProps to RenderLeafPropsTEditableProps to EditableProps#3920 by @zbeyens – This package is now the new common package, so all plugin packages are being removed. Migration:
"@udecode/plate-alignment": "42.0.0",
"@udecode/plate-autoformat": "42.0.0",
"@udecode/plate-basic-elements": "42.0.0",
"@udecode/plate-basic-marks": "42.0.0",
"@udecode/plate-block-quote": "42.0.0",
"@udecode/plate-break": "42.0.0",
"@udecode/plate-code-block": "42.0.0",
"@udecode/plate-combobox": "42.0.0",
"@udecode/plate-comments": "42.0.0",
"@udecode/plate-csv": "42.0.0",
"@udecode/plate-diff": "42.0.0",
"@udecode/plate-docx": "42.0.0",
"@udecode/plate-find-replace": "42.0.0",
"@udecode/plate-floating": "42.0.0",
"@udecode/plate-font": "42.0.0",
"@udecode/plate-heading": "42.0.0",
"@udecode/plate-highlight": "42.0.0",
"@udecode/plate-horizontal-rule": "42.0.0",
"@udecode/plate-indent": "42.0.0",
"@udecode/plate-indent-list": "42.0.0",
"@udecode/plate-kbd": "42.0.0",
"@udecode/plate-layout": "42.0.0",
"@udecode/plate-line-height": "42.0.0",
"@udecode/plate-link": "42.0.0",
"@udecode/plate-list": "42.0.0",
"@udecode/plate-markdown": "42.0.0",
"@udecode/plate-media": "42.0.0",
"@udecode/plate-mention": "42.0.0",
"@udecode/plate-node-id": "42.0.0",
"@udecode/plate-normalizers": "42.0.0",
"@udecode/plate-reset-node": "42.0.0",
"@udecode/plate-resizable": "42.0.0",
"@udecode/plate-select": "42.0.0",
"@udecode/plate-selection": "42.0.0",
"@udecode/plate-slash-command": "42.0.0",
"@udecode/plate-suggestion": "42.0.0",
"@udecode/plate-tabbable": "42.0.0",
"@udecode/plate-table": "42.0.0",
"@udecode/plate-toggle": "42.0.0",
"@udecode/plate-trailing-block": "42.0.0"
@udecode/plate imports with the individual package imports, or export the following in a new file (e.g. src/plate.ts):export * from '@udecode/plate-alignment';
export * from '@udecode/plate-autoformat';
export * from '@udecode/plate-basic-elements';
export * from '@udecode/plate-basic-marks';
export * from '@udecode/plate-block-quote';
export * from '@udecode/plate-break';
export * from '@udecode/plate-code-block';
export * from '@udecode/plate-combobox';
export * from '@udecode/plate-comments';
export * from '@udecode/plate-diff';
export * from '@udecode/plate-find-replace';
export * from '@udecode/plate-font';
export * from '@udecode/plate-heading';
export * from '@udecode/plate-highlight';
export * from '@udecode/plate-horizontal-rule';
export * from '@udecode/plate-indent';
export * from '@udecode/plate-indent-list';
export * from '@udecode/plate-kbd';
export * from '@udecode/plate-layout';
export * from '@udecode/plate-line-height';
export * from '@udecode/plate-link';
export * from '@udecode/plate-list';
export * from '@udecode/plate-media';
export * from '@udecode/plate-mention';
export * from '@udecode/plate-node-id';
export * from '@udecode/plate-normalizers';
export * from '@udecode/plate-reset-node';
export * from '@udecode/plate-select';
export * from '@udecode/plate-csv';
export * from '@udecode/plate-docx';
export * from '@udecode/plate-markdown';
export * from '@udecode/plate-slash-command';
export * from '@udecode/plate-suggestion';
export * from '@udecode/plate-tabbable';
export * from '@udecode/plate-table';
export * from '@udecode/plate-toggle';
export * from '@udecode/plate-trailing-block';
export * from '@udecode/plate-alignment/react';
export * from '@udecode/plate-autoformat/react';
export * from '@udecode/plate-basic-elements/react';
export * from '@udecode/plate-basic-marks/react';
export * from '@udecode/plate-block-quote/react';
export * from '@udecode/plate-break/react';
export * from '@udecode/plate-code-block/react';
export * from '@udecode/plate-combobox/react';
export * from '@udecode/plate-comments/react';
export * from '@udecode/plate-floating';
export * from '@udecode/plate-font/react';
export * from '@udecode/plate-heading/react';
export * from '@udecode/plate-highlight/react';
export * from '@udecode/plate-layout/react';
export * from '@udecode/plate-slash-command/react';
export * from '@udecode/plate-indent/react';
export * from '@udecode/plate-indent-list/react';
export * from '@udecode/plate-kbd/react';
export * from '@udecode/plate-line-height/react';
export * from '@udecode/plate-link/react';
export * from '@udecode/plate-list/react';
export * from '@udecode/plate-media/react';
export * from '@udecode/plate-reset-node/react';
export * from '@udecode/plate-selection';
export * from '@udecode/plate-suggestion/react';
export * from '@udecode/plate-tabbable/react';
export * from '@udecode/plate-table/react';
export * from '@udecode/plate-toggle/react';
export * from '@udecode/plate-resizable';
'@udecode/plate' and '@udecode/plate/react' with '@/plate' in your codebase.moveSelectionByOffset, getLastBlockDOMNode, useLastBlock, useLastBlockDOMNodeslate, slate-dom, slate-react, slate-history and slate-hyperscript from your dependencies. It's now part of this package and @udecode/plate. All exports remain the same or have equivalents (see below).createTEditor to createEditor.createEditor now returns an editor (Editor) with all queries under editor.api and transforms under editor.tf. You can see or override them at a glance. For example, we now use editor.tf.setNodes instead of importing setNodes. This marks the completion of generic typing and the removal of error throws from slate, slate-dom, and slate-history queries/transforms, without forking implementations. We’ve also reduced the number of queries/transforms by merging a bunch of them.The following interfaces from slate and slate-dom are now part of Editor:
Editor, EditorInterface
Transforms
HistoryEditor (noop, unchanged), HistoryEditorInterface
DOMEditor (noop, unchanged), DOMEditorInterface
editor.findPath now returns DOMEditor.findPath (memo) and falls back to findNodePath (traversal, less performant) if not found.
Removed the first parameter (editor) from:
editor.hasEditableTargeteditor.hasSelectableTargeteditor.isTargetInsideNonReadonlyVoideditor.hasRangeeditor.hasTargeteditor.api.node(options) (previously findNode) at option is now at ?? editor.selection instead of at ?? editor.selection ?? []. That means if you want to lookup the entire document, you need to pass [] explicitly.
Removed setNode in favor of setNodes (you can now pass a TNode to at directly).
Removed setElements in favor of setNodes.
Removed unused isWordAfterTrigger, setBlockAboveNode, setBlockAboveTexts, setBlockNodes, getPointNextToVoid.
Replaced Path from slate with Path (type) and PathApi (static methods).
Replaced Operation from slate with Operation (type) and OperationApi (static methods).
Replaced Point from slate with Point (type) and PointApi (static methods).
Replaced Text from slate with TText (type) and TextApi (static methods). We also export Text type like slate but we don't recommend it as it's conflicting with the DOM type.
Replaced Range from slate with TRange (type) and RangeApi (static methods). We also export Range type like slate but we don't recommend it as it's conflicting with the DOM type.
Replaced Location from slate with TLocation (type) and LocationApi (static methods). We also export Location type like slate but we don't recommend it as it's conflicting with the DOM type.
Replaced Span from slate with Span (type) and SpanApi (static methods).
Replaced Node from slate with TNode (type) and NodeApi (static methods). We also export Node type like slate but we don't recommend it as it's conflicting with the DOM type.
Replaced Element from slate with TElement (type) and ElementApi (static methods). We also export Element type like slate but we don't recommend it as it's conflicting with the DOM type.
Signature change:
editor.tf.toggle.block({ type, ...options }) -> editor.tf.toggleBlock(type, options)editor.tf.toggle.mark({ key, clear }) -> editor.tf.toggleMark(key, { remove: clear })Moved editor functions:
addMark -> editor.tf.addMarkaddRangeMarks -> editor.tf.setNodes(props, { at, marks: true })blurEditor -> editor.tf.blurcollapseSelection -> editor.tf.collapsecreateDocumentNode -> editor.api.create.value (core)createNode -> editor.api.create.blockcreatePathRef -> editor.api.pathRefcreatePointRef -> editor.api.pointRefcreateRangeRef -> editor.api.rangeRefdeleteBackward({ unit }) -> editor.tf.deleteBackward(unit)deleteForward({ unit }) -> editor.tf.deleteForward(unit)deleteFragment -> editor.tf.deleteFragmentdeleteText -> editor.tf.deletedeselect -> editor.tf.deselectdeselectEditor -> editor.tf.deselectDOMduplicateBlocks -> editor.tf.duplicateNodes({ nodes })findDescendant -> editor.api.descendantfindEditorDocumentOrShadowRoot -> editor.api.findDocumentOrShadowRootfindEventRange -> editor.api.findEventRangefindNode(options) -> editor.api.node(options)findNodeKey -> editor.api.findKeyfindNodePath -> editor.api.findPathfindPath -> editor.api.findPathfocusEditor -> editor.tf.focus({ at })focusEditorEdge -> editor.tf.focus({ at, edge: 'startEditor' | 'endEditor' })getAboveNode -> editor.api.abovegetAncestorNode -> editor.api.block({ highest: true })getBlockAbove -> editor.api.block({ at, above: true }) or editor.api.block() if at is not a pathgetBlocks -> editor.api.blocksgetEdgeBlocksAbove -> editor.api.edgeBlocksgetEdgePoints -> editor.api.edgesgetEditorString -> editor.api.stringgetEditorWindow -> editor.api.getWindowgetEndPoint -> editor.api.endgetFirstNode -> editor.api.firstgetFragment -> editor.api.fragmentgetFragmentProp(fragment, options) -> editor.api.prop({ nodes, ...options})getLastNode -> editor.api.lastgetLastNodeByLevel(level) -> editor.api.last([], { level })getLeafNode -> editor.api.leafgetLevels -> editor.api.levelsgetMark -> editor.api.markgetMarks -> editor.api.marksgetNextNode -> editor.api.nextgetNextNodeStartPoint -> editor.api.start(at, { next: true })getNodeEntries -> editor.api.nodesgetNodeEntry -> editor.api.node(at, options)getNodesRange -> editor.api.nodesRangegetParentNode -> editor.api.parentgetPath -> editor.api.pathgetPathRefs -> editor.api.pathRefsgetPoint -> editor.api.pointgetPointAfter -> editor.api.aftergetPointBefore -> editor.api.beforegetPointBeforeLocation -> editor.api.beforegetPointRefs -> editor.api.pointRefsgetPositions -> editor.api.positionsgetPreviousBlockById -> editor.api.previous({ id, block: true })getPreviousNode -> editor.api.previousgetPreviousNodeEndPoint -> editor.api.end({ previous: true })getPreviousSiblingNode -> editor.api.previous({ at, sibling: true })getRange -> editor.api.rangegetRangeBefore -> editor.api.range('before', to, { before })getRangeFromBlockStart -> editor.api.range('start', to)getRangeRefs -> editor.api.rangeRefsgetSelectionFragment -> editor.api.fragment(editor.selection, { structuralTypes })getSelectionText -> editor.api.string()getStartPoint -> editor.api.startgetVoidNode -> editor.api.voidhasBlocks -> editor.api.hasBlockshasEditorDOMNode -> editor.api.hasDOMNodehasEditorEditableTarget -> editor.api.hasEditableTargethasEditorSelectableTarget -> editor.api.hasSelectableTargethasEditorTarget -> editor.api.hasTargethasInlines -> editor.api.hasInlineshasTexts -> editor.api.hasTextsinsertBreak -> editor.tf.insertBreakinsertData -> editor.tf.insertDatainsertElements -> editor.tf.insertNodes<TElement>insertEmptyElement -> editor.tf.insertNodes(editor.api.create.block({ type }))insertFragment -> editor.tf.insertFragmentinsertNode -> editor.tf.insertNodeinsertNodes -> editor.tf.insertNodesinsertText -> editor.tf.insertText({ at }) or editor.tf.insertText({ marks: false }) without atisAncestorEmpty -> editor.api.isEmptyisBlock -> editor.api.isBlockisBlockAboveEmpty -> editor.api.isEmpty(editor.selection, { block: true })isBlockTextEmptyAfterSelection -> editor.api.isEmpty(editor.selection, { after: true })isCollapsed(editor.selection) -> editor.api.isCollapsed()isComposing -> editor.api.isComposingisDocumentEnd -> editor.api.isEditorEndisEdgePoint -> editor.api.isEdgeisEditorEmpty -> editor.api.isEmpty()isEditorFocused -> editor.api.isFocusedisEditorNormalizing -> editor.api.isNormalizingisEditorReadOnly -> editor.api.isReadOnlyisElementEmpty -> editor.api.isEmptyisElementReadOnly -> editor.api.elementReadOnlyisEndPoint -> editor.api.isEndisExpanded(editor.selection) -> editor.api.isCollapsed()isInline -> editor.api.isInlineisMarkableVoid -> editor.api.markableVoidisMarkActive -> editor.api.hasMark(key)isPointAtWordEnd -> editor.api.isAt({ at, word: true, end: true })isRangeAcrossBlocks -> editor.api.isAt({ at, blocks: true })isRangeInSameBlock -> editor.api.isAt({ at, block: true })isRangeInSingleText -> editor.api.isAt({ at, text: true })isSelectionAtBlockEnd -> editor.api.isAt({ end: true })isSelectionAtBlockStart -> editor.api.isAt({ start: true })isSelectionCoverBlock -> editor.api.isAt({ block: true, start: true, end: true })isSelectionExpanded -> editor.api.isExpanded()isStartPoint -> editor.api.isStartisTargetinsideNonReadonlyVoidEditor -> editor.api.isTargetInsideNonReadonlyVoidisTextByPath -> editor.api.isText(at)isVoid -> editor.api.isVoidliftNodes -> editor.tf.liftNodesmergeNodes -> editor.tf.mergeNodesmoveChildren -> editor.tf.moveNodes({ at, to, children: true, fromIndex, match: (node, path) => boolean })moveNodes -> editor.tf.moveNodesmoveSelection -> editor.tf.movenormalizeEditor -> editor.tf.normalizeremoveEditorMark -> editor.tf.removeMarkremoveEditorText -> editor.tf.removeNodes({ text: true, empty: false })removeEmptyPreviousBlock -> editor.tf.removeNodes({ previousEmptyBlock: true })removeMark(options) -> editor.tf.removeMarks(keys, options)removeNodeChildren -> editor.tf.removeNodes({ at, children: true })removeNodes -> editor.tf.removeNodesremoveSelectionMark -> editor.tf.removeMarks()replaceNode(editor, { nodes, insertOptions, removeOptions }) -> editor.tf.replaceNodes(nodes, { removeNodes, ...insertOptions })select -> editor.tf.selectselectEndOfBlockAboveSelection -> editor.tf.select(editor.selection, { edge: 'end' })selectNodes -> editor.tf.select(editor.api.nodesRange(nodes))setFragmentData -> editor.tf.setFragmentDatasetMarks(marks, clear) -> editor.tf.addMarks(marks, { remove: string | string[] })setNodes -> editor.tf.setNodessetPoint -> editor.tf.setPointsetSelection -> editor.tf.setSelectionsomeNode -> editor.api.some(options)splitNodes -> editor.tf.splitNodestoDOMNode -> editor.api.toDOMNodetoDOMPoint -> editor.api.toDOMPointtoDOMRange -> editor.api.toDOMRangetoggleWrapNodes -> editor.tf.toggleBlock(type, { wrap: true })toSlateNode -> editor.api.toSlateNodetoSlatePoint -> editor.api.toSlatePointtoSlateRange -> editor.api.toSlateRangeunhangCharacterRange -> editor.api.unhangRange(range, { character: true })unhangRange -> editor.api.unhangRangeunsetNodes -> editor.tf.unsetNodesunwrapNodes -> editor.tf.unwrapNodeswithoutNormalizing -> editor.tf.withoutNormalizingwrapNodeChildren -> editor.tf.wrapNodes(element, { children: true })wrapNodes -> editor.tf.wrapNodesreplaceNodeChildren -> editor.tf.replaceNodes({ at, children: true })resetEditor -> editor.tf.resetresetEditorChildren -> editor.tf.reset({ children: true })selectEditor -> editor.tf.select([], { focus, edge })selectSiblingNodePoint -> editor.tf.select(at, { next, previous })Moved to NodeApi.:
getNextSiblingNodes(parentEntry, path) -> NodeApi.children(editor, path, { from: path.at(-1) + 1 })getFirstNodeText -> NodeApi.firstTextgetFirstChild([node, path]) -> NodeApi.firstChild(editor, path)getLastChild([node, path]) -> NodeApi.lastChild(editor, path)getLastChildPath([node, path]) -> NodeApi.lastChild(editor, path)isLastChild([node, path], childPath) -> NodeApi.isLastChild(editor, childPath)getChildren([node, path]) -> Array.from(NodeApi.children(editor, path))getCommonNode -> NodeApi.commongetNode -> NodeApi.getgetNodeAncestor -> NodeApi.ancestorgetNodeAncestors -> NodeApi.ancestorsgetNodeChild -> NodeApi.childgetNodeChildren -> NodeApi.childrengetNodeDescendant -> NodeApi.descendantgetNodeDescendants -> NodeApi.descendantsgetNodeElements -> NodeApi.elementsgetNodeFirstNode -> NodeApi.firstgetNodeFragment -> NodeApi.fragmentgetNodeLastNode -> NodeApi.lastgetNodeLeaf -> NodeApi.leafgetNodeLevels -> NodeApi.levelsgetNodeParent -> NodeApi.parentgetNodeProps -> NodeApi.extractPropsgetNodes -> NodeApi.nodesgetNodeString -> NodeApi.stringgetNodeTexts -> NodeApi.textshasNode -> NodeApi.hashasSingleChild -> NodeApi.hasSingleChildisAncestor -> NodeApi.isAncestorisDescendant -> NodeApi.isDescendantisEditor -> NodeApi.isEditorisNode -> NodeApi.isNodeisNodeList -> NodeApi.isNodeListnodeMatches -> NodeApi.matchesMoved to ElementApi.:
elementMatches -> ElementApi.matchesisElement -> ElementApi.isElementisElementList -> ElementApi.isElementListMoved to TextApi.:
isText -> TextApi.isText(at)Moved to RangeApi.:
isCollapsed -> RangeApi.isCollapsedisExpanded -> RangeApi.isExpandedMoved to PathApi.:
isFirstChild -> !PathApi.hasPreviousgetPreviousPath -> PathApi.previousMoved to PointApi.:
getPointFromLocation({ at, focus }) -> PointApi.get(at, { focus })Moved from @udecode/plate/react to @udecode/plate:
HotkeysUpgraded to zustand@5 and zustand-x@5:
createZustandStore('name')(initialState) with createZustandStore(initialState, { mutative: true, name: 'name' })immer.Types:
TEditor -> EditorTOperation -> OperationTPath -> PathTNodeProps -> NodePropsTNodeChildEntry -> NodeChildEntryTNodeEntry -> NodeEntryTDescendant -> DescendantTDescendantEntry -> DescendantEntryTAncestor -> AncestorTAncestorEntry -> AncestorEntryTElementEntry -> ElementEntryTTextEntry -> TextEntryV extends Value instead of E extends Editor.getEndPoint, getEdgePoints, getFirstNode, getFragment, getLastNode, getLeafNode, getPath, getPoint, getStartPoint can return undefined if not found (suppressing error throws).NodeApi.ancestor, NodeApi.child, NodeApi.common, NodeApi.descendant, NodeApi.first, NodeApi.get, NodeApi.last, NodeApi.leaf, NodeApi.parent, NodeApi.getIf, PathApi.previous return undefined if not found instead of throwingNodeOf type with DescendantOf in editor.tf.setNodes editor.tf.unsetNodes, editor.api.previous, editor.api.node, editor.api.nodes, editor.api.lasteditor.tf.setNodes:
marks option to handle mark-specific operationsmarks: true:
split: true and voids: trueaddRangeMarks functionality#3920 by @zbeyens – Major performance improvement: all table cells were re-rendering when a single cell changed. This is now fixed.
TablePlugin now depends on NodeIdPlugin.enableMerging to disableMerge.enableMerging: true → remove the option.TablePlugin.configure({ options: { disableMerge: true } })unmergeTableCells to splitTableCell.editor.api.create.cell to editor.api.create.tableCell.useTableMergeState, renamed canUnmerge to canSplit.insertTableRow and insertTableColumn: removed disableSelect in favor of select. Migration: replace it with the opposite boolean.getTableCellBorders: params (element, options) → (editor, options); removed isFirstCell and isFirstRow.useTableCellElementState into useTableCellElement:
hovered and hoveredLeft returns (use CSS instead).rowSize to minHeight.width.useTableCellElementResizableState into useTableCellElementResizable:
onHover and onHoverEnd props (use CSS instead).useTableElementState into useTableElement:
colSizes, minColumnWidth, and colGroupProps.#3830 by @felixfeng33 – Rename findNodePath to findPath since the addition of findNodePath in the headless lib.
We recommend using findPath mostly when subscribing to its value (e.g. in a React component) as it has O(path.length) complexity, compared to O(n) for the traversal-based findNodePath. This optimization is particularly important in:
findNodePath would increase the initial render time by O(n²)findNodePath would increase the handling time by O(n)where n is the number of nodes in the editor.
useDndBlock, useDragBlock, and useDropBlock hooks in favor of useDndNode, useDragNode, and useDropNode.DndProvider and useDraggableStore. Drop line state is now managed by DndPlugin as a single state object dropTarget containing both id and line.useDropNode: removed onChangeDropLine and dropLine optionsMigration steps:
DndProvider from your draggable component (e.g. draggable.tsx)useDraggableStore with useEditorPlugin(DndPlugin).useOptionuseDraggableState. Use const { isDragging, previewRef, handleRef } = useDraggableuseDraggableGutter. Set contentEditable={false} to your gutter elementprops from useDropLine. Set contentEditable={false} to your drop line elementwithDraggable, useWithDraggable. Use DraggableAboveNodes insteadrender.belowNodes from IndentListPlugin to BaseIndentListPlugin. Props type for listStyleTypes.liComponent and listStyleTypes.markerComponent options is now SlateRenderElementProps instead of PlateRenderElementPropssomeIndentList, someIndentTodo from @udecode/plate-indent-list/react to @udecode/plate-indent-listinsertColumnGroup: rename layout to columnssetColumnWidth, useColumnState. Use setColumns instead#3830 by @felixfeng33 – Move from @udecode/plate-table/react to @udecode/plate-table:
deleteColumndeleteColumnWhenExpandeddeleteRowdeleteRowWhenExpandedgetTableColumngetTableGridAbovegetTableGridByRangegetTableRowinsertTablemergeTableCellsmoveSelectionFromCelloverrideSelectionFromCellunmergeTableCellswithDeleteTablewithGetFragmentlablewithInsertFragmentTablewithInsertTextTablewithMarkTablewithSelectionTablewithSetFragmentDataTablewithTableslate-dom as a peer dependency.slate-react peer dependency to >=0.111.0toggleColumns in favor of toggleColumnGroupinsertEmptyColumn in favor of insertColumn#3597 by @zbeyens – The following changes were made to improve performance:
useDraggable hook to focus on core dragging functionality:
dropLine. Use useDropLine().dropLine instead.groupProps from the returned object – isHovered, and setIsHovered from the returned state. Use CSS instead.droplineProps, and gutterLeftProps from the returned object. Use useDropLine().props, useDraggableGutter().props instead.#3597 by @zbeyens – The following changes were made to improve performance:
useHooksBlockSelection in favor of BlockSelectionAfterEditableslate-selected class from BlockSelectable. You can do it on your components using useBlockSelected() instead, or by using our new block-selection.tsx component.useBlockSelectableStore for managing selectable state.plugin.options merging behavior from deep merge to shallow merge..extend(), .configure(), and other methods that modify plugin options.Before:
const plugin = createSlatePlugin({
key: 'test',
options: { nested: { a: 1 } },
}).extend({
options: { nested: { b: 1 } },
});
// Result: { nested: { a: 1, b: 1 } }
After:
const plugin = createSlatePlugin({
key: 'test',
options: { nested: { a: 1 } },
}).extend(({ getOptions }) => ({
options: {
...getOptions(),
nested: { ...getOptions().nested, b: 1 },
},
}));
// Result: { nested: { a: 1, b: 1 } }
Migration:
Rename all base plugins that have a React plugin counterpart to be prefixed with Base. This change improves clarity and distinguishes base implementations from potential React extensions. Use base plugins only for server-side environments or to extend your own DOM layer.
Import the following plugins from /react entry: AlignPlugin, CalloutPlugin, EquationPlugin, FontBackgroundColorPlugin, FontColorPlugin, FontFamilyPlugin, FontSizePlugin, FontWeightPlugin, InlineEquationPlugin, LineHeightPlugin, TextIndentPlugin, TocPlugin
Migration example: https://github.com/udecode/plate/pull/3480
We recommend to upgrade to @udecode/[email protected] in one-go.
createBasicElementPlugins -> BasicElementsPlugincreateBlockquotePlugin -> BlockquotePlugincreateCodeBlockPlugin -> CodeBlockPlugincreateHeadingPlugin -> HeadingPlugin@udecode/plate-corecreateBasicMarksPlugins -> BasicMarksPlugincreateBoldPlugin -> BoldPlugincreateCodePlugin -> CodePlugincreateItalicPlugin -> ItalicPlugincreateStrikethroughPlugin -> StrikethroughPlugincreateSubscriptPlugin -> SubscriptPlugincreateSuperscriptPlugin -> SuperscriptPlugincreateUnderlinePlugin -> UnderlinePluginhotkey option. Use plugin.shortcuts instead (see plate-core)createSoftBreakPlugin -> SoftBreakPlugincreateExitBreakPlugin -> ExitBreakPlugincreateSingleLinePlugin -> SingleLinePlugincreateCaptionPlugin -> CaptionPluginCaptionPlugin options:
pluginKeys to pluginsfocusEndCaptionPath to focusEndPathfocusStartCaptionPath to focusStartPathshowCaptionId to visibleIdisShow to isVisiblecaptionGlobalStore to CaptionPlugincreateCodeBlockPlugin -> CodeBlockPluginCodeLinePluginCodeSyntaxPlugingetCodeLineType, use editor.getType(CodeLinePlugin) insteadcreateCommentsPlugin -> CommentsPlugincommentsStore to CommentsPluginCommentsProvider and its hooksuseCommentsStates (replaced by direct option access)useCommentsSelectors (replaced by option selectors)useCommentsActions (replaced by api methods)useUpdateComment with api.comment.updateCommentuseAddRawComment with api.comment.addRawCommentuseAddComment with api.comment.addCommentuseRemoveComment with api.comment.removeCommentuseResetNewCommentValue with api.comment.resetNewCommentValueuseNewCommentText with options.newTextuseMyUser with options.myUseruseUserById with options.userByIduseCommentById with options.commentByIduseActiveComment with options.activeCommentuseAddCommentMark with insert.comment@udecode/plate-common and @udecode/plate-common/react./react exports @udecode/react-hotkeys#3420 by @zbeyens – Plugin System:
Decoupling React in all packages:
@udecode/plate-core and @udecode/plate-core/reactSlatePlugin as the foundation for all pluginsPlatePlugin extends SlatePlugin with React-specific plugin featuresPlugin Creation:
createPluginFactorycreateSlatePlugin: vanillacreateTSlatePlugin: vanilla explicitly typedcreatePlatePlugin: ReactcreateTPlatePlugin: React explicitly typedtoPlatePlugin: extend a vanilla plugin into a React plugintoTPlatePlugin: extend a vanilla plugin into a React plugin explicitly typedcreateNamePlugin() to NamePluginBefore:
const MyPluginFactory = createPluginFactory({
key: 'myPlugin',
isElement: true,
component: MyComponent,
});
const plugin = MyPluginFactory();
After:
const plugin = createSlatePlugin({
key: 'myPlugin',
node: {
isElement: true,
component: MyComponent,
},
});
const reactPlugin = toPlatePlugin(plugin);
Plugin Configuration:
NamePlugin option types, use NameConfig instead.NameConfig as the new naming convention for plugin configurations.Before:
createPluginFactory<HotkeyPlugin>({
handlers: {
onKeyDown: onKeyDownToggleElement,
},
options: {
hotkey: ['mod+opt+0', 'mod+shift+0'],
},
});
After:
export const ParagraphPlugin = createPlatePlugin({
key: 'p',
node: { isElement: true },
}).extend({ editor, type }) => ({
shortcuts: {
toggleParagraph: {
handler: () => {
editor.tf.toggle.block({ type });
},
keys: [
[Key.Mod, Key.Alt, '0'],
[Key.Mod, Key.Shift, '0'],
],
preventDefault: true,
},
},
})
toggleParagraph is now a shortcut for editor.tf.toggle.block({ type: 'p' }) for the given keysshortcuts.toggleParagraph = nullKeyPlugin Properties:
Rename SlatePlugin / PlatePlugin properties:
type -> node.typeisElement -> node.isElementisLeaf -> node.isLeafisInline -> node.isInlineisMarkableVoid -> node.isMarkableVoidisVoid -> node.isVoidcomponent -> node.component or render.nodeprops -> node.propsoverrideByKey -> override.pluginsrenderAboveEditable -> render.aboveEditablerenderAboveSlate -> render.aboveSlaterenderAfterEditable -> render.afterEditablerenderBeforeEditable -> render.beforeEditableinject.props -> inject.nodePropsinject.props.validTypes -> inject.targetPluginsinject.aboveComponent -> render.aboveNodesinject.belowComponent -> render.belowNodesinject.pluginsByKey -> inject.pluginseditor.insertData -> parser
parser.format now supports string[]parser.mimeTypes: string[]deserializeHtml -> parsers.html.deserializerdeserializeHtml.getNode -> parsers.html.deserializer.parseserializeHtml -> parsers.htmlReact.serializerwithOverride -> extendEditorSlatePluginContext<C> or PlatePluginContext<C>, in addition to the method specific options. Some of the affected methods are:
decoratehandlers, including onChange. Returns ({ event, ...ctx }) => void instead of (editor, plugin) => (event) => voidhandlers.onChange: ({ value, ...ctx }) => void instead of (editor, plugin) => (value) => voidnormalizeInitialValueeditor.insertData.preInserteditor.insertData.transformDataeditor.insertData.transformFragmentdeserializeHtml.getNodedeserializeHtml.queryinject.props.queryinject.props.transformPropsuseHookswithOverridesNEW SlatePlugin properties:
api: API methods provided by this plugindependencies: An array of plugin keys that this plugin depends onnode: Node-specific configuration for this pluginparsers: Now accept string keys to add custom parserspriority: Plugin priority for registration and execution ordershortcuts: Plugin-specific hotkeysinject.targetPluginToInject: Function to inject plugin config into other plugins specified by inject.targetPluginsBefore:
export const createAlignPlugin = createPluginFactory({
key: KEY_ALIGN,
inject: {
props: {
defaultNodeValue: 'start',
nodeKey: KEY_ALIGN,
styleKey: 'textAlign',
validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],
validTypes: ['p'],
},
},
then: (_, plugin) =>
mapInjectPropsToPlugin(editor, plugin, {
deserializeHtml: {
getNode: (el, node) => {
if (el.style.textAlign) {
node[plugin.key] = el.style.textAlign;
}
},
},
}),
});
After:
export const AlignPlugin = createSlatePlugin({
inject: {
nodeProps: {
defaultNodeValue: 'start',
nodeKey: 'align',
styleKey: 'textAlign',
validNodeValues: ['start', 'left', 'center', 'right', 'end', 'justify'],
},
targetPluginToInject: ({ editor, plugin }) => ({
parsers: {
html: {
deserializer: {
parse: ({ element, node }) => {
if (element.style.textAlign) {
node[editor.getType(plugin)] = element.style.textAlign;
}
},
},
},
},
}),
targetPlugins: [ParagraphPlugin.key],
},
key: 'align',
});
Plugin Shortcuts:
shortcuts to add custom hotkeys to a plugin.hotkey option from all pluginsBefore:
type LinkPlugin = {
hotkey?: string;
};
After:
type LinkConfig = PluginConfig<
// key
'p',
// options
{ defaultLinkAttributes?: any },
// api
{ link: { getAttributes: (editor: PlateEditor) => LinkAttributes } },
// transforms
{ floatingLink: { hide: () => void } }
>;
Shortcuts API:
handler is called with the editor, event, and event details.keys is an array of keys to trigger the shortcut.priority is the priority of the shortcut over other shortcuts....HotkeysOptions from @udecode/react-hotkeysPlugin Types:
SlatePlugin, PlatePlugin generics. P, V, E -> C extends AnyPluginConfig = PluginConfigPluginOptionsPlatePluginKeyHotkeyPlugin, ToggleMarkPlugin in favor of plugin.shortcutsWithPlatePlugin -> EditorPlugin, EditorPlatePluginPlatePluginComponent -> NodeComponentInjectComponent* -> NodeWrapperComponent*PlatePluginInsertData -> ParserPlatePluginProps -> NodePropsRenderAfterEditable -> EditableSiblingComponentWithOverride -> ExtendEditorSerializeHtml -> HtmlReactSerializerPlugin Store:
plugin.optionsStore and plugin.useOptionsStoreeditor has many methods to get, set and subscribe to plugin optionsPlugin Methods:
then, use extend insteadextend method to deep merge a plugin configuration
configure method to configure the properties of existing plugins. The difference with extend is that configure with not add new properties to the plugin, it will only modify existing ones.extendPlugin method to extend a nested plugin configuration.configurePlugin method to configure the properties of a nested plugin.extendApi method to extend the plugin API. The API is then merged into editor.api[plugin.key].extendTransforms method to extend the plugin transforms. The transforms is then merged into editor.transforms[plugin.key].extendEditorApi method to extend the editor API. The API is then merged into editor.api. Use this to add or override top-level methods to the editor.extendEditorTransforms method to extend the editor transforms. The transforms is then merged into editor.transforms.extendOptions method to extend the plugin options with selectors. Use editor.useOption(plugin, 'optionKey') to subscribe to an (extended) option.withComponent to replace plugin.node.componentPlugin Context
Each plugin method now receive the plugin context created with getEditorPlugin(editor, plugin) as parameter:
apieditorgetOptiongetOptionspluginsetOptionsetOptionstftypeuseOptionCore Plugins:
ParagraphPlugin is now part of coreDebugPlugin is now part of core
api.debug.log, api.debug.info, api.debug.warn, api.debug.error methodsoptions.isProduction to control logging in production environmentsoptions.logLevel to set the minimum log leveloptions.logger to customize logging behavioroptions.throwErrors to control error throwing behavior, by default a PlateError will be thrown on api.debug.erroreditor.plugins. Last plugin wins.createDeserializeHtmlPlugin -> HtmlPlugin
api.html.deserializecreateEventEditorPlugin -> EventEditorPlugin
eventEditorStore -> EventEditorStorecreateDeserializeAstPlugin -> AstPlugincreateEditorProtocolPlugin -> SlateNextPlugin
editor.tf.toggle.blockeditor.tf.toggle.markcreateNodeFactoryPlugin, included in SlateNextPlugin.createPrevSelectionPlugin, included in SlateNextPlugin.createHistoryPlugin -> HistoryPlugincreateInlineVoidPlugin -> InlineVoidPlugincreateInsertDataPlugin -> ParserPlugincreateLengthPlugin -> LengthPlugincreateReactPlugin -> ReactPluginEditor Creation:
NEW withSlate:
rootPlugin option for configuring the root pluginNEW withPlate:
withSlate with React-specific enhancementsuseOptions and useOption methods to the editorNEW createSlateEditor:
createPlateEditor:
Plugin replacement mechanism: using plugins, any plugin with the same key that a previous plugin will replace it. That means you can now override core plugins that way, like ReactPlugin
root plugin is now created from createPlateEditor option as a quicker way to configure the editor than passing plugins. Since plugins can have nested plugins (think as a recursive tree), plugins option will be passed to root plugin plugins option.
Centralized editor resolution. Before, both createPlateEditor and Plate component were resolving the editor. Now, only createPlateEditor takes care of that. That means id, value, and other options are now controlled by createPlateEditor.
Remove createPlugins, pass plugins directly:
components -> override.componentsoverrideByKey -> override.pluginscreatePlateEditor options:
normalizeInitialValue option to shouldNormalizeEditorcomponents to override.components to override components by keyoverrideByKey to override.plugins to override plugins by keydisableCorePlugins, use override.enabled insteadvalue to set the initial value of the editor.autoSelect?: 'end' | 'start' | boolean to auto select the start of end of the editor. This is decoupled from autoFocus.selection to control the initial selection.override.enabled to disable plugins by keyrootPlugin?: (plugin: AnyPlatePlugin) => AnyPlatePlugin to configure the root plugin. From here, you can for example call configurePlugin to configure any plugin.api, decorate, extendEditor, handlers, inject, normalizeInitialValue, options, override, priority, render, shortcuts, transforms, useHooks. These options will be passed to the very first rootPlugin.NEW usePlateEditor() hook to create a PlateEditor in a React component:
createPlateEditor and useMemo to avoid re-creating the editor on every render.id option is always used as dependency.Editor Methods:
editor: PlateEditor:
redecorate to editor.api.redecoratereset to editor.tf.resetplate.set to editor.setPlateStateblockFactory to editor.api.create.blockchildrenFactory to editor.api.create.valueplugins to pluginListpluginsByKey to pluginsgetApi() to get the editor APIgetTransforms() to get the editor transformsgetPlugin(editor, key), use editor.getPlugin(plugin) or editor.getPlugin({ key })getPluginType, use editor.getType(plugin) to get node typegetPluginInjectProps(editor, key), use editor.getPlugin(plugin).inject.propsgetOptionsStore() to get a plugin options storegetPluginOptions, use getOptions()getOption() to get a plugin optionsetOption() to set a plugin optionsetOptions() to set multiple plugin options. Pass a function to use Immer. Pass an object to merge the options.useOption to subscribe to a plugin option in a React componentuseOptions to subscribe to a plugin options store in a React componentgetPlugins, use editor.pluginListgetPluginsByKey, use editor.pluginsmapInjectPropsToPluginEditor Types:
The new generic types are:
V extends Value = Value, P extends AnyPluginConfig = PlateCorePluginkeyoptionsapitransformscreateTPlateEditor for explicit typing.const editor = createPlateEditor({ plugins: [TablePlugin] });
editor.api.htmlReact.serialize(); // core plugin is automatically inferred
editor.tf.insert.tableRow(); // table plugin is automatically inferred
Plate Component
PlateProps:
editor is now required. If null, Plate will not render anything. As before, Plate remounts on id change.id, plugins, maxLength, pass these to createPlateEditor insteadinitialValue, value, pass value to createPlateEditor insteadeditorRefdisableCorePlugins, override plugins in createPlateEditor insteadUtils:
useReplaceEditor since editor is now always controlleduseEditorPlugin to get the editor and the plugin context.Types:
PlateRenderElementProps, PlateRenderLeafProps generics: V, N -> N, CPlate Store:
plugins and rawPlugins, use useEditorRef().plugins instead, or listen to plugin changes with editor.useOption(plugin, <optionKey>)value, use useEditorValue() insteadeditorRef, use useEditorRef() insteadMiscellaneous Changes
slate >=0.103.0 peer dependencyslate-react >=0.108.0 peer dependency@udecode/react-hotkeysELEMENT_, MARK_ and KEY_ constants. Use NamePlugin.key instead.ELEMENT_DEFAULT with ParagraphPlugin.key.getTEditorwithTReact to withPlateReactwithTHistory to withPlateHistoryusePlateId to useEditorIdusePlateSelectors().id(), usePlateSelectors().value(), usePlateSelectors().plugins(), use instead useEditorRef().<key>toggleNodeType to toggleBlocktoggleBlock options:
activeType to typeinactiveType to defaultTypereact-hotkeys-hook re-exports. Use @udecode/react-hotkeys instead.Types:
TEditableProps, TRenderElementProps to @udecode/slate-react<V extends Value> generic in all functions where not usedPlatePluginKeyOverrideByKeyPlateIdcreateDndPlugin -> DndPlugineditor.isDragging, use editor.getOptions(DndPlugin).isDragging insteaddndStore to DndPlugincreateFontBackgroundColorPlugin -> FontBackgroundColorPlugincreateFontColorPlugin -> FontColorPlugincreateFontSizePlugin -> FontSizePlugincreateFontFamilyPlugin -> FontFamilyPlugincreateFontWeightPlugin -> FontWeightPlugincreateHeadingPlugin -> HeadingPluginELEMENT_H1 with HEADING_KEYS.H1KEYS_HEADING with HEADING_LEVELScreateDeserializeHtmlPlugin -> HtmlPlugindeserializeHtml plugin to htmldeserializeHtml.getNode to parsecreateIndentListPlugin -> IndentListPlugininjectIndentListComponent to renderIndentListBelowNodesnormalizeIndentList with withNormalizeIndentListdeleteBackwardIndentList with withDeleteBackwardIndentListinsertBreakIndentList with withInsertBreakIndentListLiFC (use PlateRenderElementProps), MarkerFC (use Omit<PlateRenderElementProps, 'children'>)createListPlugin -> ListPluginBulletedListPluginNumberedListPluginListItemPluginListItemContentPlugintoggle.list, toggle.bulletedList, toggle.numberedListgetListItemType, getUnorderedListType, getOrderedListType, getListItemContentTypeinsertBreakList(editor) with withInsertBreakList(ctx)insertFragmentList(editor) with withInsertFragmentList(ctx)insertBreakTodoList(editor) with withInsertBreakTodoList(ctx)deleteForwardList(editor) with withdeleteForwardList(ctx)deleteBackwardList(editor) with withdeleteBackwardList(ctx)ul and ol to list plugintoggleList options are now { type: string }createMediaPlugin -> MediaPluginFloatingMediaUrlInput, submitFloatingMedia rename option pluginKey -> plugininsertMediaEmbed remove key optioncreateMentionPlugin -> MentionPluginMentionInputPlugincreateMentionNode option, override api.insert.mention instead@udecode/plate-layout/react exports @udecode/react-hotkeys@udecode/plate and @udecode/plate/react.@udecode/plate-paragraphonKeyDownToggleElement, use shortcuts instead.onKeyDownToggleMark, use shortcuts instead.createSelectOnBackspacePlugin -> SelectOnBackspacePlugincreateDeletePlugin -> DeletePlugincreateSelectionPlugin to BlockSelectionPluginisNodeBlockSelected, isBlockSelected, hasBlockSelected, useBlockSelected functions
editor.getOptions(BlockSelectionPlugin) or editor.useOptions(BlockSelectionPlugin) insteadaddSelectedRow function
editor.api.blockSelection.addSelectedRow insteadwithSelection HOConCloseBlockSelection to onChangeBlockSelectionblockSelectionStore to BlockSelectionPluginblockContextMenuStore to BlockContextMenuPluginBlockStartArea and BlockSelectionArea components
areaOptions in BlockSelectionPlugin for configuration instead@viselect/vanilla package
BlockContextMenuPlugin, which is now used by BlockSelectionPlugin
#3420 by @zbeyens – createTEditor:
slate-react and slate-history in createTEditornoop function to provide default implementations for unimplemented editor methodsTypes:
ReactEditor and HistoryEditor interfaces into TEditor, removing TReactEditor and THistoryEditorValue generic type parameter from function signatures and type definitionsV extends Value with E extends TEditor for improved type inferenceTEditor<V> to TEditor in many placesE extends TEditor in all cases:
EElement<V> to ElementOf<E>EText<V> to TextOf<E>ENode<V> to NodeOf<E>EDescendant<V> to DescendantOf<E>EAncestor<V> to AncestorOf<E>EElementOrText<V> to ElementOrTextOf<E>TNodeEntry related types:
ENodeEntry<V> to NodeEntryOf<E>EElementEntry<V> to ElementEntryOf<E>ETextEntry<V> to TextEntryOf<E>EAncestorEntry<V> to AncestorEntryOf<E>EDescendantEntry<V> to DescendantEntryOf<E>EElementEntry<V>ETextEntry<V>EDescendantEntry<V>TReactEditor type, as it's now integrated into the main TEditor type in @udecode/slate. Use TEditor instead.V extends Value with E extends TEditor for improved type inferenceTEditableProps, TRenderElementPropsV extends Value with E extends TEditor for improved type inferencecreateSuggestionPlugin -> SuggestionPluginsuggestionStore to SuggestionPluginSuggestionProvider and its hooksuseSuggestionStates (replaced by direct option access)useSuggestionSelectors (replaced by option selectors)useSuggestionActions (replaced by api methods)useUpdateSuggestion with api.suggestion.updateSuggestionuseAddSuggestion with api.suggestion.addSuggestionuseRemoveSuggestion with api.suggestion.removeSuggestionuseSuggestionById with options.suggestionByIduseSuggestionUserById with options.suggestionUserByIduseCurrentSuggestionUser with options.currentSuggestionUsereditor.activeSuggestionId, use plugin optionuseSetIsSuggesting, use editor.setOptionuseSetActiveSuggestionId, use editor.setOptioneditor.isSuggesting, use plugin optionSuggestionEditorProps typecreateTabbablePlugin -> TabbablePluginTabbablePlugin option isTabbable: remove first editor parametercreateTablePlugin -> TablePluginTableRowPlugin, TableCellPlugin, TableCellHeaderPlugininsertTableColumn with editor.insert.tableColumninsertTableRow with editor.insert.tableRowcellFactory option to create.cell apigetCellChildren option to table.getCellChildren apicreateTogglePlugin -> TogglePlugintoggleControllerStore to TogglePluginsetOpenIds optionisToggleOpen with option isOpeninjectToggle to renderToggleAboveNodescreateYjsPlugin -> YjsPluginyjsStore to YjsPlugineditor.yjs.provider to options.providerRenderAboveEditableYjs to YjsAboveEditableNo breaking changes
#3282 by @12joan – Make the dependency on prismjs optional
New usage:
// Import Prism with your supported languages
import Prism from 'prismjs';
import 'prismjs/components/prism-antlr4.js';
import 'prismjs/components/prism-bash.js';
import 'prismjs/components/prism-c.js';
// ...
const plugins = createPlugins([
createCodeBlockPlugin({
options: {
prism: Prism,
},
}),
]);
Breaking Change: The selectedColor option for BlockSelectable has been deprecated. Please use useBlockSelected to customize the style of each node component.
#3241 by @felixfeng33 – Add logic for the block-context-menu and improved the user experience for block-selection, such as interactions related to keyboard shortcuts, bug fixes.
Starting from this version, a single Cmd+A will no longer select the entire document but will select the entire block instead. Double Cmd+A will use the blockSelection plugin to select all blocks. To disable this behavior, pass handlers: { onKeyDown: null }.
None (CI release issue)
None (CI release issue)
TableProvider was incorrectly shared by all tables in the editor. TableProvider must now be rendered as part of TableElement.withProps to @udecode/cnPortalBody, Text, Box, createPrimitiveComponent, createSlotComponent, withProviders to @udecode/react-utilsgetRootProps (unused)822f6f56b by @12joan –
{ fn: ... } workaround for jotai stores that contain functionsusePlateSelectors, usePlateActions and usePlateStates no longer accept generic type arguments. If custom types are required, cast the resulting values at the point of use, or use hooks like useEditorRef that still provide generics.jotai@2@udecode/plate-comments made in 26.0.0jotai@1 to jotai@2
jotai-x. See https://github.com/udecode/jotai-xTried to access jotai store '${storeName}' outside of a matching provider.zustand@3 to zustand@4
zustand-x exports
StateActions -> ZustandStateActionsStoreApi -> ZustandStoreApicreateStore -> createZustandStorejotai@2ResizableProvidercomments prop on CommentsProvider to initialComments to reflect the fact that updating its value after the initial render has no effectactiveCommentIdaddingCommentIdnewValuefocusTextareamyUserIdusersonCommentAddonCommentUpdateonCommentDeletedeserializeHtml, replace stripWhitespace with collapseWhiteSpace, defaulting to true. The collapseWhiteSpace option aims to parse white space in HTML according to the HTML specification, ensuring greater accuracy when pasting HTML from browsers.useCommentValue, which was redundant with the hooks applied automatically in CommentEditTextarea.tsx.Plate to PlateContent.PlateProvider to Plate.PlateContent is now required in Plate. This allows you to choose where to render the editor next to other components like toolbar. Example:// Before
<Plate />
// or
<PlateProvider>
<Plate />
</PlateProvider>
// After
<Plate>
<PlateContent />
</Plate>
plugins from PlateContent. These props should be passed to Plate.editableProps prop from PlateContent. Move these asPlateContent props.children prop from PlateContent. Render instead these components after PlateContent.firstChildren prop from PlateContent. Render instead these components before PlateContent.editableRef prop from PlateContent. Use ref instead.withPlateProvider.usePlateEditorRef to useEditorRef.usePlateEditorState to useEditorState.usePlateReadOnly to useEditorReadOnly. This hook can be used below Plate while useReadOnly can only be used in node components.usePlateSelection to useEditorSelection.keyDecorate, keyEditor and keySelection to versionDecorate, versionEditor and versionSelection. These are now numbers incremented on each change.isRendered to isMounted.#2537 by @haydencarlson – MediaEmbedElement is now more headless with a smaller bundle size.
Update the following components:
npx shadcn@canary add media-embed-element
react-lite-youtube-embed for YouTube videos.react-tweet for Twitter tweets.npx shadcn@canary add image-elementBreaking changes:
Resizable to @udecode/plate-resizableCaption, CaptionTextarea to @udecode/plate-captionuseMediaEmbed, MediaEmbedVideo, MediaEmbedTweet, Tweet, parseMediaUrl, mediaStore@udecode/resizable, scriptjs, react-textarea-autosize dependenciesMediaPlugin
rules. Use parsers option instead.disableCaption. Use createCaptionPlugin instead.@udecode/plate-caption and add it to your plugins:import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED } from '@udecode/plate-media';
createCaptionPlugin({
options: { pluginKeys: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED] },
});
@udecode/plate-resizable.ResizeHandle is now fully headless: no style is applied by default. Add your own Resizable, ResizeHandle components:
npx shadcn@canary add resizableTableCellElementResizable. Use useTableCellElementResizableState and useTableCellElementResizable instead.Headless UI.
Migration:
@udecode/plate-ui-x), generate the component using the CLI.AccountCircleIconCheckIconMoreVertIconRefreshIconAvatarImageCommentLinkButtonCommentLinkDialogCommentLinkDialogCloseButtonCommentLinkDialogCopyLinkCommentLinkDialogInputPlateCommentLeaf for useCommentLeafStateDraggableDraggableBlockDraggableBlockToolbarDraggableBlockToolbarWrapperDraggableDroplineDraggableGutterLeftPropsDraggableRootDragHandleFloatingLinkFloatingLinkEditButtonFloatingLinkTextInputUnlinkButtonLaunchIconLinkLinkIconLinkOffIconShortTextIcon#2471 by @zbeyens – Plate 2.0 – Headless UI. Read the docs about the new UI pattern: https://platejs.org/docs/components.
@udecode/plate-ui dependency.@udecode/plate-emoji dependency. You can install it separately.styled-components peerDependency.Replaced @udecode/plate-headless dependency (deprecated) by:
@udecode/plate-alignment@udecode/plate-autoformat@udecode/plate-basic-elements@udecode/plate-basic-marks@udecode/plate-block-quote@udecode/plate-break@udecode/plate-code-block@udecode/plate-combobox@udecode/plate-comments@udecode/plate-common@udecode/plate-find-replace@udecode/plate-floating@udecode/plate-font@udecode/plate-heading@udecode/plate-highlight@udecode/plate-horizontal-rule@udecode/plate-indent@udecode/plate-indent-list@udecode/plate-kbd@udecode/plate-line-height@udecode/plate-link@udecode/plate-list@udecode/plate-media@udecode/plate-mention@udecode/plate-node-id@udecode/plate-normalizers@udecode/plate-paragraph@udecode/plate-reset-node@udecode/plate-select@udecode/plate-serializer-csv@udecode/plate-serializer-docx@udecode/plate-serializer-html@udecode/plate-serializer-md@udecode/plate-suggestion@udecode/plate-tabbable@udecode/plate-table@udecode/plate-trailing-block@udecode/resizableslate-react: >=0.95.0
Removed:useElementPrposuseWrapElementcreateComponentAscreateElementAsTableCellElementTableCellElementResizableWrapperTableCellElementRootTableElementTableElementColTableElementColGroupTableElementRootTableElementTBodyTableRowElementArrowDropDownCircleIconBorderAllIconBorderBottomIconBorderLeftIconBorderNoneIconBorderOuterIconBorderRightIconBorderTopIcon[email protected], [email protected] and [email protected] by upgrading the peer dependencies.0077402 by @zbeyens –
@udecode/utils is a collection of miscellaneous utilities. Can be used by any project.@udecode/slate is a collection of slate experimental features and bug fixes that may be moved into slate one day. It's essentially composed of the generic types. Can be used by vanilla slate consumers without plate.@udecode/slate-react is a collection of slate-react experimental features and bug fixes that that may be moved into slate-react one day. It's essentially composed of the generic types. Can be used by vanilla slate-react consumers without plate.@udecode/plate-core is the minimalistic core of plate. It essentially includes Plate, PlateProvider and their dependencies. Note this package is not dependent on the *-utils packages.@udecode/slate-utils is a collection of utils depending on @udecode/slate. Can be used by vanilla slate consumers without plate.@udecode/plate-utils is a collection of utils depending on @udecode/slate-react and @udecode/plate-core@udecode/plate-common re-exports the 6 previous packages and is a dependency of all the other packages. It's basically replacing @udecore/plate-core as a bundle.getPreventDefaultHandler since it is no longer needed.
Migration:
@udecode/plate or @udecode/plate-headless: none@udecode/plate-core by @udecode/plate-commonallowedSchemes plugin option
http(s), mailto and tel must be added to allowedSchemes, otherwise they will not be included in linksTablePlugin option disableUnsetSingleColSize has been renamed and inverted into enableUnsetSingleColSize. New default is disabled. Migration:
disableUnsetSingleColSize: true, the option can be removeddisableUnsetSingleColSize: false, use enableUnsetSingleColSize: truegetTableColumnIndex second parameter type is now: cellNode: TElement#2237 by @tmorane – Unstyled logic has been moved to @udecode/plate-dnd
// before
import { createDndPlugin } from '@udecode/plate-ui-dnd';
// after
import { createDndPlugin } from '@udecode/plate-dnd';
Only withPlateDraggable, withPlateDraggables and PlateDraggable are left in @udecode/plate-ui-dnd.
Renamed:
withDraggables -> withPlateDraggables. In the second parameter, draggable props options have been moved under draggableProps:// before
{
onRenderDragHandle: () => {}
styles,
}
// after
{
draggableProps: {
onRenderDragHandle: () => {}
styles,
},
}
@udecode/plate-table, so the following components have been renamed:
TableElement -> PlateTableElement
margin-left: 1px to support cell bordersTableRowElement -> PlateTableRowElementTableCellElement -> PlateTableCellElement
selectedCell div in favor of ::beforeTablePopover -> PlateTablePopover
Styled props have been removed.// from
"slate": "0.78.0",
"slate-history": "0.66.0",
"slate-react": "0.79.0"
// to
"slate": "0.87.0",
"slate-history": "0.86.0",
"slate-react": "0.88.0"
@udecode/plate-ui-dnd// before
import { createDndPlugin } from '@udecode/plate';
// after
import { createDndPlugin } from '@udecode/plate-ui-dnd';
// from
"slate": ">=0.78.0",
"slate-history": ">=0.66.0",
"slate-react": ">=0.79.0"
// to
"slate": ">=0.87.0",
"slate-history": ">=0.86.0",
"slate-react": ">=0.88.0"
@udecode/plate-selection package moved out from @udecode/plate because of https://github.com/Simonwep/selection/issues/124createBlockSelectionPlugin, no migration is needed.@udecode/plate-selection and import createBlockSelectionPlugin from that package.usePlateStore:
PlateProvider or Plate (provider-less mode). It means it's no longer accessible outside of a Plate React tree. If you have such use-case, you'll need to build your own layer to share the state between your components.PlateProvider with different scopes (id prop). Default scope is PLATE_SCOPEconst value = usePlateSelectors(id).value()const setValue = usePlateActions(id).value()const [value, setValue] = usePlateStates(id).value()editableProps, use the props insteadenabled, use conditional rendering insteadisReady, no point anymore as it's now directly readyuseEventPlateId is still used to get the last focused editor id.{ fn: <here> }
const setOnChange = usePlateActions(id).onChange()setOnChange({ fn: newOnChange })Plate
PlateProvider, it will render PlateSlate > PlateEditablePlateProvider, it will render PlateProvider > PlateSlate > PlateEditableid is no longer main, it's now PLATE_SCOPEPlateProvider
scope, so you can have multiple providers in the same React tree and use the plate hooks with the corresponding scope.PlateProvider
initialValue, value, editor, normalizeInitialValue, normalizeEditor are no longer defined in an effect (SSR support)Plate propsPlateProvider, set the provider props on it instead of Plate. Plate would only need editableProps and PlateEditableExtendedPropsPlate// Before
<PlateProvider>
<Toolbar>
<AlignToolbarButtons />
</Toolbar>
<Plate<MyValue> editableProps={editableProps} <MyValue> initialValue={alignValue} plugins={plugins} />
</PlateProvider>
// After
<PlateProvider<MyValue> initialValue={alignValue} plugins={plugins}>
<Toolbar>
<AlignToolbarButtons />
</Toolbar>
<Plate<MyValue> editableProps={editableProps} />
</PlateProvider>
// After (provider-less mode)
<Plate<MyValue> editableProps={editableProps} initialValue={alignValue} plugins={plugins} />
editor is no longer nullablevalue is no longer nullableid type is now PlateIdSCOPE_PLATE to PLATE_SCOPEgetEventEditorId to getEventPlateIdgetPlateActions().resetEditor to useResetPlateEditor()plateIdAtomusePlateId for usePlateSelectors().id()EditablePlugins for PlateEditableSlateChildrenPlateEventProvider for PlateProviderwithPlateEventProvider for withPlateProviderusePlateusePlatesStoreEffectuseEventEditorId for useEventPlateIdplatesStore, platesActions, platesSelectors, usePlatesSelectorsgetPlateActions for usePlateActionsgetPlateSelectors for usePlateSelectorsgetPlateEditorRef for usePlateEditorRefgetPlateStore, usePlateStoreEditorId for PlateIdprismjs/components/prism-djangoprismjs/components/prism-ejsprismjs/components/prism-php[ELEMENT_CODE_BLOCK]: CodeBlockElement from Plate UI. You can define it in your app.@udecode/plate-image and @udecode/plate-media-embed, those got merged into @udecode/plate-mediauseImageElement for useElementMediaEmbedUrlInput for FloatingMediaUrlInputparseEmbedUrl for parseMediaUrlEmbedProvidersImageImg to ImageImageCaptionTextarea to CaptionTextareauseImageCaptionString to useCaptionStringImageResizable to ResizableTableElementBase props:
onRenderContainer by floatingOptions or by replacing ELEMENT_TABLE in the createPlateUI function.TablePopover is now a floating instead of tippyplate-ui-popover by plate-floating@udecode/plate-ui-popper by @udecode/plate-floatingcomboboxStore:
popperContainer, use floatingOptions insteadpopperOptions, use floatingOptions insteadcreateLinkPlugin
onKeyDownLink for floating linkhotkey for triggerFloatingLinkHotkeysgetAndUpsertLink for upsertLinkupsertLinkAtSelection for upsertLinkLinkToolbarButton:
onClick now calls triggerFloatingLink@udecode/plate-ui-popper and react-popper deps for @udecode/plate-floatingBalloonToolbarProps:
popperOptions for floatingOptionsuseBalloonToolbarPopper for useFloatingToolbarserializeHtml and its utils to @udecode/plate-serializer-html as it has a new dependency: html-entities.
@udecode/plate, no migration is needed@udecode/plate-serializer-htmlPlate children are now rendered as last children of Slate (previously first children). To reproduce the previous behavior, move children to firstChildren@udecode/plate-juice from @udecode/plate. Install it if using @udecode/plate-serializer-docx:
pnpm install @udecode/plate-juice
react-dnd react-dnd-html5-backend deps to peer-dependencies. Install these if using @udecode/plate-ui-dnd:
pnpm install react-dnd react-dnd-html5-backend
useDndBlock options:
blockRef -> nodeRefremovePreview -> preview.disableuseDropBlockOnEditor -> useDropBlockuseDropBlock options:
blockRef -> nodeRefsetDropLine -> onChangeDropLine
signature change:getHoverDirection:// before
(
dragItem: DragItemBlock,
monitor: DropTargetMonitor,
ref: any,
hoverId: string
)
// after
{
dragItem,
id,
monitor,
nodeRef,
}: GetHoverDirectionOptions
#1500 by @zbeyens – Thanks @ianstormtaylor for the initial work on https://github.com/ianstormtaylor/slate/pull/4177.
This release includes major changes to plate and slate types:
TEditor type to be TEditor<V> where V represents the "value" being edited by Slate. In the most generic editor, V would be equivalent to TElement[] (since that is what is accepted as children of the editor). But in a custom editor, you might have TEditor<Array<Paragraph | Quote>>.TEditor-and-TNode-related methods have been also made generic, so for example if you use getLeafNode(editor, path) it knows that the return value is a TText node. But more specifically, it knows that it is the text node of the type you've defined in your custom elements (with any marks you've defined).TEditor still.)Define your custom types
Slate types
Those Slate types should be replaced by the new types:
Editor -> TEditor<V extends Value = Value>
TEditor methods are not typed based on Value as it would introduce a circular dependency. You can use getTEditor(editor) to get the editor with typed methods.ReactEditor -> TReactEditor<V extends Value = Value>HistoryEditor -> THistoryEditor<V extends Value = Value>EditableProps -> TEditableProps<V extends Value = Value>Node -> TNodeElement -> TElementText -> TTextNodeEntry -> TNodeEntryNodeProps -> TNodePropsSlate functions
Those Slate functions should be replaced by the new typed ones:
Transforms, Editor, Node, Element, Text, HistoryEditor, ReactEditor functions should be replaced: The whole API has been typed into Plate core. See https://github.com/udecode/plate/packages/core/src/slatecreateEditor -> createTEditorwithReact -> withTReactwithHistory -> withTHistoryGeneric types
<T = {}> could be used to extend the editor type. It is now replaced by <E extends PlateEditor<V> = PlateEditor<V>> to customize the whole editor type.
When the plugin type is customizable, these generics are used: <P = PluginOptions, V extends Value = Value, E extends PlateEditor<V> = PlateEditor<V>>, where P is the plugin options type.
Editor functions are using <V extends Value> generic, where V can be a custom editor value type used in PlateEditor<V>.
Editor functions returning a node are using <N extends ENode<V>, V extends Value = Value> generics, where N can be a custom returned node type.
Editor callbacks (e.g. a plugin option) are using <V extends Value = Value, E extends PlateEditor<V> = PlateEditor<V>> generics, where E can be a custom editor type.
Node functions returning a node are using <N extends Node, R extends TNode = TNode> generics.
These generics are used by <V extends Value, K extends keyof EMarks<V>>: getMarks, isMarkActive, removeMark, setMarks, ToggleMarkPlugin, addMark, removeEditorMark
WithOverride is a special type case as it can return a new editor type:
// before
export type WithOverride<T = {}, P = {}> = (
editor: PlateEditor<T>,
plugin: WithPlatePlugin<T, P>
) => PlateEditor<T>;
// after - where E is the Editor type (input), and EE is the Extended Editor type (output)
export type WithOverride<
P = PluginOptions,
V extends Value = Value,
E extends PlateEditor<V> = PlateEditor<V>,
EE extends E = E,
> = (editor: E, plugin: WithPlatePlugin<P, V, E>) => EE;
type TEditor<V extends Value>
type PlateEditor<V extends Value>
Renamed functions
getAbove -> getAboveNodegetParent -> getParentNodegetText -> getEditorStringgetLastNode -> getLastNodeByLevelgetPointBefore -> getPointBeforeLocationgetNodes -> getNodeEntriesisStart -> isStartPointisEnd -> isEndPointReplaced types
Removing node props types in favor of element types (same props + extends TElement). You can use TNodeProps to get the node data (props).
LinkNodeData -> TLinkElementImageNodeData -> TImageElementTableNodeData -> TTableElementMentionNodeData -> TMentionElementMentionNode -> TMentionElementMentionInputNodeData -> TMentionInputElementMentionInputNode -> TMentionInputElementCodeBlockNodeData -> TCodeBlockElementMediaEmbedNodeData -> TMediaEmbedElementTodoListItemNodeData -> TTodoListItemElementExcalidrawNodeData -> TExcalidrawElementUtils
match signature change:<T extends TNode>(
obj: T,
path: TPath,
predicate?: Predicate<T>
)
deleteFragment is now using Editor.deleteFragmentgetEmptyTableNode default options changed. Migration:// From (0 row count and col count, previously it was 2)
getEmptyTableNode(editor);
// To
getEmptyTableNode(editor, { rowCount: 2, colCount: 2 });
Generic types
type StyledElementProps<V extends Value, N extends TElement = EElement<V>, TStyles = {}>BalloonToolbar could be outside Plate. Now, BallonToolbar should be a child of Plate to support multiple editors.Plate
editor prop can now be fully controlled: Plate is not applying withPlate on it anymorePlatePlugin.deserializeHtml
validAttribute, validClassName, validNodeName, validStyle to deserializeHtml.rules propertyplateStore to platesStoreplatesStore is now a zustood storeeventEditorStore is now a zustood storegetPlateId now gets the last editor id if not focused or blurred
usePlateEditorRef and usePlateEditorStateusePlateEnabled for usePlateSelectors(id).enabled()usePlateValue for usePlateSelectors(id).value()usePlateActions:
resetEditor for getPlateActions(id).resetEditor()clearState for platesActions.unset()setInitialState for platesActions.set(id)setEditor for getPlateActions(id).editor(value)setEnabled for getPlateActions(id).enabled(value)setValue for getPlateActions(id).value(value)getPlateStateusePlateStateusePlateKeyplate-x-ui to plate-ui-x: all packages depending on styled-components has plate-ui prefixplate-x-serializer to plate-serializer-x@udecode/plate-headless: all unstyled packages@udecode/plate-ui: all styled packagesPlateState to PlateStoreStateIndentListPluginOptions for PlatePluginRename:
getIndentListInjectComponent to injectIndentListComponent#1234 by @zbeyens – Breaking changes:
Platecomponents prop:// Before
<Plate plugins={plugins} components={components} />;
// After
// option 1: use the plugin factory
let plugins = [
createParagraphPlugin({
component: ParagraphElement,
}),
];
// option 2: use createPlugins
plugins = createPlugins(plugins, {
components: {
[ELEMENT_PARAGRAPH]: ParagraphElement,
},
});
<Plate plugins={plugins} />;
options prop:// Before
<Plate plugins={plugins} options={options} />;
// After
// option 1: use the plugin factory
let plugins = [
createParagraphPlugin({
type: 'paragraph',
}),
];
// option 2: use createPlugins
plugins = createPlugins(plugins, {
overrideByKey: {
[ELEMENT_PARAGRAPH]: {
type: 'paragraph',
},
},
});
<Plate plugins={plugins} />;
PlatePluginkey
pluginKeyplugin as a second parameter:// Before
export type X<T = {}> = (editor: PlateEditor<T>) => Y;
// After
export type X<T = {}, P = {}> = (
editor: PlateEditor<T>,
plugin: WithPlatePlugin<T, P>
) => Y;
serialize no longer has element and leaf properties:type SerializeHtml = RenderFunction<
PlateRenderElementProps | PlateRenderLeafProps
>;
Renamed:
injectParentComponent to inject.aboveComponentinjectChildComponent to inject.belowComponentoverrideProps to inject.props
transformClassName, transformNodeValue, transformStyle first parameter is no longer editor as it's provided by then if needed.getOverrideProps is now the core behavior if inject.props is defined.serialize to serializeHtmldeserialize to deserializeHtml
deserializeHtmltype DeserializeHtml = {
/** List of HTML attribute names to store their values in `node.attributes`. */
attributeNames?: string[];
/**
* Deserialize an element. Use this instead of plugin.isElement if you don't
* want the plugin to renderElement.
*
* @default plugin.isElement
*/
isElement?: boolean;
/**
* Deserialize a leaf. Use this instead of plugin.isLeaf if you don't want the
* plugin to renderLeaf.
*
* @default plugin.isLeaf
*/
isLeaf?: boolean;
/** Deserialize html element to slate node. */
getNode?: (element: HTMLElement) => AnyObject | undefined;
query?: (element: HTMLElement) => boolean;
/**
* Deserialize an element:
*
* - If this option (string) is in the element attribute names.
* - If this option (object) values match the element attributes.
*/
validAttribute?: string | { [key: string]: string | string[] };
/** Valid element `className`. */
validClassName?: string;
/** Valid element `nodeName`. Set '*' to allow any node name. */
validNodeName?: string | string[];
/**
* Valid element style values. Can be a list of string (only one match is
* needed).
*/
validStyle?: Partial<
Record<keyof CSSStyleDeclaration, string | string[] | undefined>
>;
/** Whether or not to include deserialized children on this node */
withoutChildren?: boolean;
};
on... are moved to handlers property.// Before
onDrop: handler;
// After
handlers: {
onDrop: handler;
}
Removed:
renderElement is favor of:
isElement is a boolean that enables element rendering.getRenderElement is now the core behavior.renderLeaf is favor of:
isLeaf is a boolean that enables leaf rendering.getRenderLeaf is now the core behavior.inlineTypes and voidTypes for:
isInline is a boolean that enables inline rendering.isVoid is a boolean that enables void rendering.plugins prop:const corePlugins = [
createReactPlugin(),
createHistoryPlugin(),
createEventEditorPlugin(),
createInlineVoidPlugin(),
createInsertDataPlugin(),
createDeserializeAstPlugin(),
createDeserializeHtmlPlugin(),
];
plugins is not a parameter anymore as it can be retrieved in editor.pluginswithInlineVoid is now using plugins isInline and isVoid plugin properties.Renamed:
getPlatePluginType to getPluginTypegetEditorOptions to getPluginsgetPlatePluginOptions to getPluginpipeOverrideProps to pipeInjectPropsgetOverrideProps to pluginInjectPropsserializeHTMLFromNodes to serializeHtml
getLeaf to leafToHtmlgetNode to elementToHtmlxDeserializerId to KEY_DESERIALIZE_XdeserializeHTMLToText to htmlTextNodeToStringdeserializeHTMLToMarks to htmlElementToLeaf and pipeDeserializeHtmlLeafdeserializeHTMLToElement to htmlElementToElement and pipeDeserializeHtmlElementdeserializeHTMLToFragment to htmlBodyToFragmentdeserializeHTMLToDocumentFragment to deserializeHtmldeserializeHTMLToBreak to htmlBrToNewLinedeserializeHTMLNode to deserializeHtmlNodedeserializeHTMLElement to deserializeHtmlElementRemoved:
usePlateKeys, getPlateKeysusePlateOptions for getPlugingetPlateSelection for getPlateEditorRef().selectionflatMapByKeygetEditableRenderElement and getRenderElement for pipeRenderElement and pluginRenderElementgetEditableRenderLeaf and getRenderLeaf for pipeRenderLeaf and pluginRenderLeafgetInlineTypesgetVoidTypesgetPlatePluginTypesgetPlatePluginWithOverridesmapPlatePluginKeysToOptionswithDeserializeX for PlatePlugin.editor.insertDataChanged types:
PlateEditor:
options for pluginsByKeyWithOverride is not returning an extended editor anymore (input and output editors are assumed to be the same types for simplicity).PlateState
keyChange to keyEditorplugins for editor.pluginspluginKeysselection for editor.selectionsetSelection, setPlugins, setPluginKeysincrementKeyChange forRenamed types:
XHTMLY to XHtmlYDeserialize to DeseralizeHtmlRemoved types:
PlatePluginOptions:
type to PlatePlugin.typecomponent to PlatePlugin.componentdeserialize to PlatePlugin.deserializeHtmlgetNodeProps to PlatePlugin.props.nodePropshotkey to HotkeyPluginclear to ToggleMarkPlugindefaultType is hardcoded to p.typeOverrideProps for PlatePlugin.inject.propsSerialize for PlatePlugin.serializeHtmlNodeProps for AnyObjectOnKeyDownElementOptions for HotkeyPluginOnKeyDownMarkOptions for ToggleMarkPluginWithInlineVoidOptionsGetNodeProps for PlatePluginPropsDeserializeOptions, GetLeafDeserializerOptions, GetElementDeserializerOptions, GetNodeDeserializerOptions, GetNodeDeserializerRule, DeserializeNode for PlatePlugin.deserializeHtmlPlateOptionsRenderNodeOptionsDeserializedHTMLElementgetCodeBlockPluginOptions for getPlugingetCodeLinePluginOptions for getPlugingetMentionInputPluginOptions for getPlugingetMentionInputType for getPluginTypeCOMBOBOX_TRIGGER_MENTION#1234 by @zbeyens – Breaking changes:
createBasicElementsPlugincreateCodeBlockPlugincreateHeadingPlugincreateListPlugincreateTablePlugincreateBasicMarksPluginRemoved:
createEditorPlugins for createPlateEditor (without components) and createPlateEditorUI (with Plate components)createPlateOptions for createPluginsDEFAULTS_X: these are defined in the pluginsgetXDeserialize: these are defined in the pluginsWithXOptions for extended pluginsgetXRenderElementPlatePluginRenamed:
createPlateComponents to createPlateUIgetXY handlers to yX (e.g. getXOnKeyDown to onKeyDownX)XPluginOptions to XPluginpluginKey parameter to key except in componentsRenamed types:
DecorateSearchHighlightOptions to FindReplacePluginUpdated deps:
"slate": "0.70.0""slate-react": "0.70.1"Removed deps (merged to core):
plate-commonplate-ast-serializerplate-html-serializerplate-serializercreateDeserializeCSVPlugin to createDeserializeCsvPlugindeserializeCSV to deserializeCsvcreateDeserializeMdPlugin:
Renamed:
createDeserializeMDPlugin to createDeserializeMdPlugindeserializeMD to deserializeMd@udecode/plate-coreSPEditor to PEditor (note that PlateEditor is the new default)SPRenderNodeProps to PlateRenderNodePropsSPRenderElementProps to PlateRenderElementPropsSPRenderLeafProps to PlateRenderLeafPropsuseEventEditorId to usePlateEventIduseStoreEditorOptions to usePlateOptionsuseStoreEditorRef to usePlateEditorRefuseStoreEditorSelection to usePlateSelectionuseStoreEditorState to usePlateEditorStateuseStoreEditorValue to usePlateValueuseStoreEnabled to usePlateEnableduseStorePlate to usePlatePluginsuseStorePlatePluginKeys to usePlateKeysuseStoreState to usePlateStategetPlateId: Get the last focused editor id, else get the last blurred editor id, else get the first editor id, else nullgetPlateState:
stategetPlateId(). It means useEventEditorId('focus') is no longer needed for
usePlateEditorRefusePlateEditorStateusePlateX...@udecode/plate-alignmentsetAlign: option align renamed to valuegetAlignOverrideProps() in favor of getOverrideProps(KEY_ALIGN)@udecode/plate-indentgetIndentOverrideProps() in favor of getOverrideProps(KEY_INDENT)onKeyDownHandler to getIndentOnKeyDown()IndentPluginOptions
types to validTypescssPropName to styleKeytransformCssValue to transformNodeValue@udecode/plate-line-heightsetLineHeight: option lineHeight renamed to valuegetLineHeightOverrideProps in favor of getOverrideProps(KEY_LINE_HEIGHT)@udecode/plate-mentiongetMentionOnSelectItem:
createMentionNode in favor of plugin optionsinsertSpaceAfterMention in favor of plugin options@udecode/plate-mention-uiMentionCombobox props:
trigger in favor of plugin optionsinsertSpaceAfterMention in favor of plugin optionscreateMentionNode in favor of plugin options@udecode/plate-x-uiToolbarAlign to AlignToolbarButtonToolbarCodeBlock to CodeBlockToolbarButtonToolbarElement to BlockToolbarButtonToolbarImage to ImageToolbarButtonToolbarLink to LinkToolbarButtonToolbarList to ListToolbarButtonToolbarLineHeight to LineHeightToolbarDropdownToolbarMark to MarkToolbarButtonToolbarMediaEmbed to MediaEmbedToolbarButtonToolbarSearchHighlight to SearchHighlightToolbarToolbarTable to TableToolbarButton@udecode/plate-alignmentThe align plugin is no longer wrapping a block, but instead setting an align property to an existing block.
createAlignPlugin:
pluginKeys, renderElement and deserializeELEMENT_ALIGN_LEFTELEMENT_ALIGN_CENTERELEMENT_ALIGN_RIGHTELEMENT_ALIGN_JUSTIFYKEYS_ALIGN in favor of KEY_ALIGNgetAlignDeserializeupsertAlign in favor of setAlignMigration (normalizer):
parent = getParent(editor, path), if parent[0].type is one of the alignment values:
setAlign(editor, { align }, { at: path })unwrapNodes(editor, { at: path })@udecode/plate-alignment-uiToolbarAlignProps:
type in favor of alignunwrapTypesalign@udecode/plate-mentionThe mention plugin is now using the combobox.
useMentionPlugin in favor of createMentionPlugin
useMentionPlugin().plugin by createMentionPlugin()mentionableSearchPatterninsertSpaceAfterMentionmaxSuggestions: moved to comboboxStoretrigger: moved to comboboxStorementionables: moved to items in comboboxStorementionableFilter: moved to filter in comboboxStorematchesTriggerAndPattern in favor of getTextFromTriggerMentionNodeData in favor of ComboboxItemDataexport interface ComboboxItemData {
/** Unique key. */
key: string;
/** Item text. */
text: any;
/**
* Whether the item is disabled.
*
* @default false
*/
disabled?: boolean;
/** Data available to `onRenderItem`. */
data?: unknown;
}
@udecode/plate-mention-uiMentionSelect in favor of MentionCombobox@udecode/plate-toolbarsetPositionAtSelection in favor of useBalloonToolbarPopperuseBalloonMove in favor of useBalloonToolbarPopperusePopupPosition in favor of useBalloonToolbarPopperuseBalloonShow in favor of useBalloonToolbarPopper
BalloonToolbar props:direction in favor of popperOptions.placementscrollContainer to popperContainer@udecode/plate-toolbarBalloonToolbar: removed hiddenDelay prop.There was multiple instances of styled-components across all the packages.
So we moved styled-components from dependencies to peer dependencies.
styled-components was not listed in your dependencies
Add styled-components to your dependencies
@udecode/plate-autoformatautoformatBlock:
// Before
(
editor: TEditor,
type: string,
at: Location,
options: Pick<AutoformatRule, 'preFormat' | 'format'>
)
// After
(editor: TEditor, options: AutoformatBlockOptions)
withAutoformatautoformatInline:
autoformatMark// Before
(
editor: TEditor,
options: Pick<AutoformatRule, 'type' | 'between' | 'markup' | 'ignoreTrim'>
)
// After
(
editor: TEditor,
options: AutoformatMarkOptions
)
AutoformatRule is now AutoformatBlockRule | AutoformatMarkRule | AutoformatTextRule;
mode: 'inline' renamed to mode: 'mark'markup and between have been replaced by match: string | string[] | MatchRange | MatchRange[]: The rule applies when the trigger and the text just before the cursor matches. For mode: 'block': lookup for the end match(es) before the cursor. For mode: 'text': lookup for the end match(es) before the cursor. If format is an array, also lookup for the start match(es). For mode: 'mark': lookup for the start and end matches. Note: '_*', ['_*'] and { start: '_*', end: '*_' } are equivalent.trigger now defaults to the last character of match or match.end (previously ' ')a*text*.