This is the compatibility archive for apps still on @udecode/* packages
through Plate v48. Use it to identify the breaking-change family, then follow
the current docs for the target API.
Use Releases for v49 and later. Use From Plite to Plate when the source editor is plain Plite rather than an older Plate app.
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>// 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.#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
},
},
});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
},
},
],
},
});YjsPlugin.configure({
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.getPluginApi(YjsPlugin).yjs.init({
id: roomName, // Or your document identifier
value: INITIAL_VALUE, // Your initial editor content
});
// Destroy connection on component unmount
return () => {
editor.getPluginApi(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,
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
.getPluginApi(SuggestionPlugin)
.suggestion.suggestionData(node);
return suggestionData?.type === 'insert'
? { type: 'text', value: '' }
: { type: 'text'
#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,
},
});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.plugin(plugin).getOption('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
{}, // Tx
BlockSelectionSelectors, // Selectors
>;#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.
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. Plite splits transforms in three places (editor, Editor, and Transforms), which is also confusing. Plate keeps editor reads/services under api and replayable document mutations under transaction groups.
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.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-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"
@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';
'@udecode/plate' and '@udecode/plate/react' with '@/plate' in your codebase.slate, plite-dom, plite-react, plite-history and plite-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, plite-dom, and plite-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 plite-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.hasRangeTypes:
Editor -> EditorTOperation -> OperationPath -> PathTNodeProps -> NodePropsTNodeChildEntry -> NodeChildEntryNodeEntry -> NodeEntryDescendant -> DescendantTDescendantEntry -> DescendantEntryTAncestor -> AncestorTAncestorEntry -> AncestorEntry#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-list#3830 by @felixfeng33 – Move from @udecode/plate-table/react to @udecode/plate-table:
deleteColumndeleteColumnWhenExpandeddeleteRowdeleteRowWhenExpandedgetTableColumngetTableGridAbovegetTableGridByRangegetTableRowinsertTablemergeTableCellsmoveSelectionFromCelloverrideSelectionFromCellunmergeTableCellswithDeleteTablewithGetFragmentlablewithInsertFragmentTablewithInsertTextTablewithMarkTablewithSelectionTablewithSetFragmentDataTablewithTable#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 BlockSelectionAfterEditableplite-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 = createBasePlugin({
key: 'test',
options: { nested: { a: 1 } },
}).extend({
options: { nested: { b: 1 } },
});
// Result: { nested: { a: 1, b: 1 } }const plugin = createBasePlugin({
key: 'test',
options: { nested: { a: 1 } },
}).extend({
options: { nested: { b: 1 } },
});
// Result: { nested: { a: 1, b: 1 } }After:
const plugin = createBasePlugin({
key: 'test',
options: { nested: { a: 1 } },
}).extend(({ getOptions }) => ({
options: {
...getOptions(),
nested: { ...getOptions().nested, b: 1 },
},
}));
// Result: { nested: { a: 1, b: 1 } }const plugin = createBasePlugin({
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/plate-core@38.1.0 in one-go.
createBasicMarksPlugins -> BasicMarksPlugincreateBoldPlugin -> BoldPlugincreateCodePlugin -> CodePlugincreateItalicPlugin -> ItalicPlugincreateStrikethroughPlugin -> StrikethroughPlugincreateSubscriptPlugin -> SubscriptPlugincreateSuperscriptPlugin -> SuperscriptPlugincreateUnderlinePlugin -> UnderlinePluginhotkey option. Use plugin.shortcuts instead (see plate-core)createCaptionPlugin -> CaptionPluginCaptionPlugin options:
pluginNames to pluginsfocusEndCaptionPath to focusEndPathfocusStartCaptionPath to focusStartPathshowCaptionId to visibleIdisShow to isVisiblecaptionGlobalStore to CaptionPlugincreateCommentsPlugin -> 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#3420 by @zbeyens – Plugin System:
Decoupling React in all packages:
@udecode/plate-core and @udecode/plate-core/reactBasePlugin as the foundation for all pluginsPlatePlugin extends BasePlugin with React-specific plugin featuresPlugin Creation:
createPluginFactorycreateBasePlugin: vanillacreateBasePlugin: vanilla explicitly typedcreatePlatePlugin: ReactcreatePlatePlugin: React explicitly typedtoPlatePlugin: extend a vanilla plugin into a React plugintoPlatePlugin: extend a vanilla plugin into a React plugin explicitly typedcreateNamePlugin() to NamePluginBefore:
const MyPluginFactory = createPluginFactory({
key: 'myPlugin',
isElement: true,
component: MyComponent,
});
const plugin = MyPluginFactory();const MyPluginFactory = createPluginFactory({
key: 'myPlugin',
isElement: true,
component: MyComponent,
});
const plugin = MyPluginFactory();After:
const plugin = createBasePlugin({
key: 'myPlugin',
node: {
isElement: true,
component: MyComponent,
},
});
const reactPlugin = toPlatePlugin(plugin);const plugin = createBasePlugin({
key: 'myPlugin',
node: {
isElement: true,
component: MyComponent,
},
});
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'],
},
});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 BasePlugin / PlatePlugin properties:
type -> node.typeisElement -> node.isElementisLeaf -> node.isLeafisInline -> node.isInlineisMarkableVoid -> node.isMarkableVoidisVoid -> node.isVoidcomponent -> render.nodeprops -> node.propsoverrideByKey -> override.pluginsrenderAboveEditable -> render.aboveEditablerenderAboveSlate -> NEW BasePlugin 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
After:
export const AlignPlugin = createBasePlugin({
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) {
Plugin Shortcuts:
shortcuts to add custom hotkeys to a plugin.hotkey option from all pluginsBefore:
type LinkPlugin = {
hotkey?: string;
};type LinkPlugin = {
hotkey?: string;
};After:
type LinkConfig = PluginConfig<
// key
'p',
// options
{ defaultLinkAttributes?: any },
// api
{ link: { getAttributes: (editor: PlateEditor) => LinkAttributes } },
// tx
{ 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:
BasePlugin, PlatePlugin generics. P, V, E -> C extends AnyPluginConfig = PluginConfigPluginOptionsPlatePluginNameHotkeyPlugin, ToggleMarkPlugin in favor of plugin.shortcutsWithPlatePlugin -> BasePlugin, EditorPlatePluginPlatePluginComponent -> NodeComponentInjectComponent* -> NodeWrapperComponent*PlatePluginInsertData -> ParserPlatePluginProps -> NodePropsPlugin 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 extends the plugin API. The API is merged into the plugin's editor.api namespace.extendOptions method to extend the plugin options with selectors. Use to subscribe to an (extended) option.Plugin Context
Each plugin method receives its plugin context as the callback 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
Editor Creation:
NEW withPlite:
rootPlugin option for configuring the root pluginNEW withPlate:
withPlite with React-specific enhancementsuseOptions and useOption methods to the editorNEW createPliteEditor:
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 -> createPlateEditor 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, , , , , , , , , , . These options will be passed to the very first .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 pluginseditor.api.editor.plugin(plugin).plugin to read the compiled descriptoreditor.plugin(pluginName).type to resolve a dynamic plugin nameeditor.plugin(plugin).plugin.inject.nodePropsEditor Types:
The new generic types are:
V extends Value = Value, P extends AnyPluginConfig = PlateCorePluginkeyoptionsapitransformscreatePlateEditor 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 inferredconst editor = createPlateEditor({ plugins: [TablePlugin] });
editor.api.htmlReact.serialize(); // core plugin is automatically inferred
editor.tf.insert.tableRow(); // table plugin is automatically inferredPlate 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 dependencyplite-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>Types:
TEditableProps, TRenderElementProps to @udecode/slate-react<V extends Value> generic in all functions where not usedPlatePluginNameOverrideByKeyPlateIdcreateIndentListPlugin -> 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 }createSelectionPlugin to BlockSelectionPluginisNodeBlockSelected, isBlockSelected, hasBlockSelected, useBlockSelected functions
editor.plugin(BlockSelectionPlugin).getOptions() 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:
plite-react and plite-history in createTEditornoop function to provide default implementations for unimplemented editor methodsTypes:
ReactEditor and HistoryEditor interfaces into Editor, removing TReactEditor and THistoryEditorValue generic type parameter from function signatures and type definitionsV extends Value with E extends Editor for improved type inferenceEditor<V> to Editor in many placesE extends Editor 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>NodeEntry 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>createSuggestionPlugin -> 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 typecreateTablePlugin -> TablePluginTableRowPlugin, TableCellPlugin, TableCellHeaderPlugininsertTableColumn with editor.insert.tableColumninsertTableRow with editor.insert.tableRowcellFactory option to create.cell apigetCellChildren option to table.getCellChildren apiNo 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,
},
}),
]);// 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)
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@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 -> createZustandStorecomments 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>// 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.#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: { pluginNames: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED] },
});import { ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED } from '@udecode/plate-media';
createCaptionPlugin({
options: { pluginNames: [ELEMENT_IMAGE, ELEMENT_MEDIA_EMBED] },
});TableCellElementResizable. Use useTableCellElementResizableState and useTableCellElementResizable instead.Headless UI.
Migration:
@udecode/plate-ui-x), generate the component using the CLI.#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-indentTableCellElementTableCellElementResizableWrapperTableCellElementRootTableElementTableElementColTableElementColGroupTableElementRootTableElementTBodyTableRowElementArrowDropDownCircleIconBorderAllIconBorderBottomIconBorderLeftIconBorderNoneIconBorderOuterIconBorderRightIconBorderTopIcon0077402 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 plite-react experimental features and bug fixes that that may be moved into plite-react one day. It's essentially composed of the generic types. Can be used by vanilla plite-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: Element#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';// 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,
},
}// before
{
onRenderDragHandle: () => {}
styles,
}
// after
{
draggableProps
@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",
"plite-history": "0.66.0",
"plite-react": "0.79.0"
// to
"slate": "0.87.0",
"plite-history": "0.86.0",
"plite-react": "0.88.0"// from
"slate": "0.78.0",
"plite-history": "0.66.0",
"plite-react": "0.79.0"
// to
"slate": "0.87.0",
"plite-history": "0.86.0",
"plite-react": "0.88.0"@udecode/plate-ui-dnd// before
import { createDndPlugin } from '@udecode/plate';
// after
import { createDndPlugin } from '@udecode/plate-ui-dnd';// before
import { createDndPlugin } from '@udecode/plate';
// after
import { createDndPlugin } from '@udecode/plate-ui-dnd';// from
"slate": ">=0.78.0",
"plite-history": ">=0.66.0",
"plite-react": ">=0.79.0"
// to
"slate": ">=0.87.0",
"plite-history": ">=0.86.0",
"plite-react": ">=0.88.0"// from
"slate": ">=0.78.0",
"plite-history": ">=0.66.0",
"plite-react": ">=0.79.0"
// to
"slate"
@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 Plite > PlateEditablePlateProvider, it will render PlateProvider > Plite > 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
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 serializeHtml 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 Plite (previously first children). To reproduce the previous behavior, move children to firstChildren// before
(
dragItem: DragItemBlock,
monitor: DropTargetMonitor,
ref: any,
hoverId: string
)
// after
{
dragItem,
id,
monitor,
nodeRef,
}: GetHoverDirectionOptions// 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:
Editor type to be Editor<V> where V represents the "value" being edited by Plite. In the most generic editor, V would be equivalent to Element[] (since that is what is accepted as children of the editor). But in a custom editor, you might have Editor<Array<Paragraph | Quote>>.Editor-and-Node-related methods have been also made generic, so for example if you use getLeafNode(editor, path) it knows that the return value is a Text 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).Editor still.)Define your custom types
Plite types
Those Plite types should be replaced by the new types:
Editor -> Editor<V extends Value = Value>
Editor 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 -> NodeElement -> ElementText -> TextNodeEntry -> NodeEntryNodeProps -> TNodePropsPlite functions
Those Plite 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 Node = Node> 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<
type Editor<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 Element). 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 Node>(
obj: T,
path: Path,
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 });// 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 Element = EElement<V>, TStyles = {}>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)getPlateStateusePlateStateplate-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 PlateStoreState#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} />;// 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
pluginNameplugin 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
>;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
on... are moved to handlers property.// Before
onDrop: handler;
// After
handlers: {
onDrop: handler;
}// 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(),
];const corePlugins = [
createReactPlugin(),
createHistoryPlugin(),
createEventEditorPlugin(),
createInlineVoidPlugin(),
createInsertDataPlugin
plugins is not a parameter anymore as it can be retrieved in editor.pluginswithInlineVoid is now using plugins isInline and isVoid plugin properties.Renamed:
getEditorOptions to getPluginsgetPlatePluginOptions to getPluginpipeOverrideProps to pipeInjectPropsgetOverrideProps to pluginInjectPropsserializeHTMLFromNodes to serializeHtml
getLeaf to leafToHtmlgetNode to elementToHtmlxDeserializerId to KEY_DESERIALIZE_XdeserializeHTMLToText to htmlTextNodeToStringdeserializeHTMLToMarks to htmlElementToLeaf and Removed:
usePlateKeys, getPlateKeysusePlateOptions for getPlugingetPlateSelection for getPlateEditorRef().selectionflatMapByKeygetEditableRenderElement and getRenderElement for pipeRenderElement and pluginRenderElementgetEditableRenderLeaf and getRenderLeaf for pipeRenderLeaf and pluginRenderLeafgetInlineTypesgetVoidTypesgetPlatePluginTypesgetPlatePluginWithOverridesmapPlatePluginNamesToOptionsChanged 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.pluginspluginNamesselection for editor.selectionsetSelection, setPlugins, setPluginNamesincrementKeyChange 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 #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 XPluginpluginName parameter to key except in componentsRenamed types:
DecorateSearchHighlightOptions to FindReplacePluginUpdated deps:
"slate": "0.70.0""plite-react": "0.70.1"Removed deps (merged to core):
plate-commonplate-ast-serializerplate-html-serializerplate-serializerSPEditor 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 usePlatePluginsuseStorePlatePluginNames 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...setAlign: option align renamed to valuegetAlignOverrideProps() in favor of getOverrideProps(KEY_ALIGN)getIndentOverrideProps() in favor of getOverrideProps(KEY_INDENT)onKeyDownHandler to getIndentOnKeyDown()IndentPluginState
types to validTypescssPropName to styleKeytransformCssValue to transformNodeValuesetLineHeight: option lineHeight renamed to valuegetLineHeightOverrideProps in favor of getOverrideProps(KEY_LINE_HEIGHT)getMentionOnSelectItem:
createMentionNode in favor of plugin optionsinsertSpaceAfterMention in favor of plugin optionsMentionCombobox props:
trigger in favor of plugin optionsinsertSpaceAfterMention in favor of plugin optionscreateMentionNode in favor of plugin optionsToolbarAlign to AlignToolbarButtonToolbarCodeBlock to CodeBlockToolbarButtonToolbarElement to BlockToolbarButtonToolbarImage to ImageToolbarButtonToolbarLink to LinkToolbarButtonToolbarList to ListToolbarButtonToolbarLineHeight to LineHeightToolbarDropdownToolbarMark to MarkToolbarButtonToolbarMediaEmbed to MediaEmbedToolbarButtonToolbarSearchHighlight to SearchHighlightToolbarToolbarTable to TableToolbarButtonThe align plugin is no longer wrapping a block, but instead setting an align property to an existing block.
createAlignPlugin:
pluginNames, 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 })ToolbarAlignProps:
type in favor of alignunwrapTypesalignThe 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;
}export 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;
}MentionSelect in favor of MentionComboboxsetPositionAtSelection 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 popperContainerBalloonToolbar: 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
autoformatBlock:
// Before
(
editor: Editor,
type: string,
at: Location,
options: Pick<AutoformatRule, 'preFormat' | 'format'>
)// Before
(
editor: Editor,
type: string,
at: Location,
options: Pick<AutoformatRule, 'preFormat' | 'format'>
)// After
(editor: Editor, options: AutoformatBlockOptions)// After
(editor: Editor, options: AutoformatBlockOptions)withAutoformatautoformatInline:
autoformatMark// Before
(
editor: Editor,
options: Pick<AutoformatRule, 'type' | 'between' | 'markup' | 'ignoreTrim'>
)// Before
(
editor: Editor,
options: Pick<AutoformatRule, 'type' | 'between' | 'markup' | 'ignoreTrim'>
)// After
(
editor: Editor,
options: AutoformatMarkOptions
)// After
(
editor: Editor,
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*.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
},
},
],
},
});React.useEffect(() => {
if (!mounted) return;
// Initialize Yjs connection and sync
editor.getPluginApi(YjsPlugin).yjs.init({
id: roomName, // Or your document identifier
value: INITIAL_VALUE, // Your initial editor content
});
// Destroy connection on component unmount
return () => {
editor.getPluginApi(YjsPlugin).yjs.destroy();
};
}, [editor, mounted, roomName]); // Add relevant dependenciesexport 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],
},
});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
.getPluginApi(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],
},
});// Before:
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{ selectedIds?: Set<string>; } & BlockSelectionSelectors,
>;
// After:
export type BlockSelectionConfig = PluginConfig<
'blockSelection',
{ selectedIds?: Set<string>; },
{}, // API
{}, // Tx
BlockSelectionSelectors, // Selectors
>;// 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);
},
},
}));// 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 = createBasePlugin({
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 = createBasePlugin({
key: 'inlineVoid',
}).overrideEditor(withInlineVoid);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';editor.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 Node 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 Text (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 Range (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 Location (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 Node (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 Element (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.insertNodesinsertEmptyElement -> 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 })toPliteNode -> editor.api.toPliteNodetoPlitePoint -> editor.api.toPlitePointtoPliteRange -> editor.api.toPliteRangeunhangCharacterRange -> editor.api.unhangRange(range, { character: true })unhangRange -> editor.api.unhangRangeunsetNodes -> editor.tf.unsetNodesunwrapNodes -> editor.tf.unwrapNodeswithoutNormalizing(editor, fn) -> editor.update((tx) => { /* use tx.* */ })wrapNodeChildren -> 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.TElementEntry -> 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 functionalityexport 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,
},
},
})render.abovePliterenderAfterEditable -> 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 -> extendEditorBasePluginContext<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.transformPropsuseHookswithOverridesexport 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;
}
},
},
}),
});export const AlignPlugin = createBasePlugin({
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.plugin(plugin).type] = element.style.textAlign;
}
},
},
},
},
}),
targetPlugins: [ParagraphPlugin.key],
},
key: 'align',
});RenderAfterEditableEditableSiblingComponentWithOverride -> ExtendEditorSerializeHtml -> HtmlReactSerializereditor.useOption(plugin, 'optionKey')component to replace the prior plugin component fieldapi.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 -> ReactPluginoverride.componentsoverrideByKey -> override.pluginshandlersinjectnormalizeInitialValueoptionsoverridepriorityrendershortcutstransformsuseHooksrootPlugingetOptionsStore() 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.pluginsmapInjectPropsToPlugintoggleNodeType to toggleBlocktoggleBlock options:
activeType to typeinactiveType to defaultTypereact-hotkeys-hook re-exports. Use @udecode/react-hotkeys instead.usePlateEditorStateuseEditorStateusePlateReadOnly 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.@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/resizable// 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} />PlateProviderwithPlateEventProvider for withPlateProviderusePlateusePlatesStoreEffectuseEventEditorId for useEventPlateIdplatesStore, platesActions, platesSelectors, usePlatesSelectorsgetPlateActions for usePlateActionsgetPlateSelectors for usePlateSelectorsgetPlateEditorRef for usePlateEditorRefgetPlateStore, usePlateStoreEditorId for PlateId// 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;usePlateKey// 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} />;type 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;
};pipeDeserializeHtmlLeafdeserializeHTMLToElement to htmlElementToElement and pipeDeserializeHtmlElementdeserializeHTMLToFragment to htmlBodyToFragmentdeserializeHTMLToDocumentFragment to deserializeHtmldeserializeHTMLToBreak to htmlBrToNewLinedeserializeHTMLNode to deserializeHtmlNodedeserializeHTMLElement to deserializeHtmlElementwithDeserializeX for PlatePlugin.editor.insertDataAnyObjectOnKeyDownElementOptions for HotkeyPluginOnKeyDownMarkOptions for ToggleMarkPluginWithInlineVoidOptionsGetNodeProps for PlatePluginPropsDeserializeOptions, GetLeafDeserializerOptions, GetElementDeserializerOptions, GetNodeDeserializerOptions, GetNodeDeserializerRule, DeserializeNode for PlatePlugin.deserializeHtmlPlateOptionsRenderNodeOptionsDeserializedHTMLElement