From a94fead29ef50dcea7ffa45088f5563a786d8166 Mon Sep 17 00:00:00 2001 From: Aleh Makaranka <29537473+cpoftea@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:03:16 +0400 Subject: [PATCH 1/2] [RTE] Render default and plugin nodes through PlateElement / PlateLeaf * Switched headings and default paragraph to `PlateElement` with `as`, and bold/code/italic/superscript/underline to `PlateLeaf` with `as` * Refactored `AttachmentBlock`, `IframeBlock`, and `PlaceholderBlock` to `PlateElement` with `PlateElementProps` / `useElement` instead of manual `attributes` / `element` typing * Updated `quotePlugin` quote renderer, `linkPlugin` anchor override, `listPlugin` `LI` component, `paragraphPlugin` default paragraph override, `NotePluginBlock`, and `Separator` to use `PlateElement` (merged `className` / styles where needed; `Separator` no longer uses `forwardRef` and applies `contentEditable={ false }` on the element) * Replaced nested span wrappers in `baseMarksPlugin` (bold/italic/underline) with `PlateLeaf` * Replaced inline code span in `inlineCodePlugin` with `PlateLeaf` and `PlateLeafProps` * Wrapped `ToDoItem` in `PlateElement` with `asChild` around `FlexRow` so Slate attributes compose with the layout row * Added `types.ts` with `TPlaceholderElement` and used `satisfies` when inserting placeholder nodes --- changelog.md | 1 + uui-editor/src/components.tsx | 50 +++++++++++----- .../attachmentPlugin/AttachmentBlock.tsx | 17 ++---- .../baseMarksPlugin/baseMarksPlugin.tsx | 31 ++-------- .../src/plugins/iframePlugin/IframeBlock.tsx | 15 ++--- .../inlineCodePlugin/inlineCodePlugin.tsx | 11 ++-- .../src/plugins/linkPlugin/linkPlugin.tsx | 11 ++-- .../src/plugins/listPlugin/listPlugin.tsx | 8 +-- .../plugins/notePlugin/NotePluginBlock.tsx | 28 +++++---- .../paragraphPlugin/paragraphPlugin.tsx | 10 ++-- .../placeholderPlugin/PlaceholderBlock.tsx | 15 +++-- .../placeholderPlugin/placeholderPlugin.tsx | 3 +- .../src/plugins/placeholderPlugin/types.ts | 8 +++ .../src/plugins/quotePlugin/quotePlugin.tsx | 18 +++--- .../src/plugins/separatorPlugin/Separator.tsx | 20 +++---- .../src/plugins/toDoListPlugin/ToDoItem.tsx | 57 ++++++++++--------- 16 files changed, 150 insertions(+), 153 deletions(-) create mode 100644 uui-editor/src/plugins/placeholderPlugin/types.ts diff --git a/changelog.md b/changelog.md index fb2ab09f68..438ac63c7b 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ * [DataRowOptions]: Added `reserveSpace` property to checkbox configuration - When set and `checkbox.isVisible` is `false`, reserves space for the checkbox in the row - Automatically calculated for tree-like data structures to maintain consistent alignment +* [RTE]: default block and mark components render through `PlateElement` / `PlateLeaf` (with `asChild` where a semantic tag is used) so custom Plate plugins that wrap nodes (e.g. `inject.aboveComponent`) compose correctly ([#3062](https://github.com/epam/UUI/issues/3062)). **What's Fixed** diff --git a/uui-editor/src/components.tsx b/uui-editor/src/components.tsx index e6d718c0af..349b681986 100644 --- a/uui-editor/src/components.tsx +++ b/uui-editor/src/components.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { MARK_BOLD, MARK_CODE, MARK_ITALIC, MARK_SUPERSCRIPT, MARK_UNDERLINE } from '@udecode/plate-basic-marks'; -import { PlatePluginComponent } from '@udecode/plate-common'; +import { PlateElement, PlateLeaf, PlatePluginComponent } from '@udecode/plate-common'; import { ELEMENT_H1, ELEMENT_H2, ELEMENT_H3, ELEMENT_H4, ELEMENT_H5, ELEMENT_H6 } from '@udecode/plate-heading'; import { ELEMENT_PARAGRAPH } from '@udecode/plate-paragraph'; @@ -24,18 +24,42 @@ export const createPlateUI = ( >, ) => { const components: { [key: string]: PlatePluginComponent } = { - [ELEMENT_H1]: (props) =>

{ props.children }

, - [ELEMENT_H2]: (props) =>

{ props.children }

, - [ELEMENT_H3]: (props) =>

{ props.children }

, - [ELEMENT_H4]: (props) =>

{ props.children }

, - [ELEMENT_H5]: (props) =>
{ props.children }
, - [ELEMENT_H6]: (props) =>
{ props.children }
, - [ELEMENT_PARAGRAPH]: (props) =>

{ props.children }

, - [MARK_BOLD]: (props) => { props.children }, - [MARK_CODE]: (props) => { props.children }, - [MARK_ITALIC]: (props) => { props.children }, - [MARK_SUPERSCRIPT]: (props) => { props.children }, - [MARK_UNDERLINE]: (props) => { props.children }, + [ELEMENT_H1]: (props) => ( + + ), + [ELEMENT_H2]: (props) => ( + + ), + [ELEMENT_H3]: (props) => ( + + ), + [ELEMENT_H4]: (props) => ( + + ), + [ELEMENT_H5]: (props) => ( + + ), + [ELEMENT_H6]: (props) => ( + + ), + [ELEMENT_PARAGRAPH]: (props) => ( + + ), + [MARK_BOLD]: (props) => ( + + ), + [MARK_CODE]: (props) => ( + + ), + [MARK_ITALIC]: (props) => ( + + ), + [MARK_SUPERSCRIPT]: (props) => ( + + ), + [MARK_UNDERLINE]: (props) => ( + + ), }; if (overrideByKey) { diff --git a/uui-editor/src/plugins/attachmentPlugin/AttachmentBlock.tsx b/uui-editor/src/plugins/attachmentPlugin/AttachmentBlock.tsx index 142bfe91a6..e299540a3d 100644 --- a/uui-editor/src/plugins/attachmentPlugin/AttachmentBlock.tsx +++ b/uui-editor/src/plugins/attachmentPlugin/AttachmentBlock.tsx @@ -15,25 +15,20 @@ import { ReactComponent as TextIcon } from '../../icons/file-file_text-24.svg'; import { ReactComponent as MailIcon } from '../../icons/file-file_eml-24.svg'; import css from './AttachmentBlock.module.scss'; -import { AnyObject, PlateEditor, PlatePluginComponent, setElements } from '@udecode/plate-common'; +import { PlateElement, PlateElementProps, setElements, useElement } from '@udecode/plate-common'; import { useFocused, useReadOnly, useSelected } from 'slate-react'; import { TAttachmentElement } from './types'; -export const AttachmentBlock: PlatePluginComponent<{ - editor: PlateEditor, - attributes: AnyObject, - children: React.ReactNode, - element: TAttachmentElement -}> = function AttachmentComp(props) { +export const AttachmentBlock = function AttachmentComp({ children, ...props }: PlateElementProps) { + const element = useElement(); const isFocused = useFocused(); const isSelected = useSelected() && isFocused; const isReadonly = useReadOnly(); - const { element, editor, children } = props; const [fileName, setFileName] = useState(element.data.fileName || ''); const changeName = (name: string) => { - setElements(editor, { + setElements(props.editor, { ...element, data: { ...element.data, @@ -100,7 +95,7 @@ export const AttachmentBlock: PlatePluginComponent<{ }; return ( -
+ { children } -
+
); }; diff --git a/uui-editor/src/plugins/baseMarksPlugin/baseMarksPlugin.tsx b/uui-editor/src/plugins/baseMarksPlugin/baseMarksPlugin.tsx index ba7e599940..f0306e7cb6 100644 --- a/uui-editor/src/plugins/baseMarksPlugin/baseMarksPlugin.tsx +++ b/uui-editor/src/plugins/baseMarksPlugin/baseMarksPlugin.tsx @@ -1,5 +1,5 @@ import { - PlateEditor, PlatePluginComponent, isMarkActive, PlatePlugin, + PlateEditor, PlateLeaf, PlateLeafProps, isMarkActive, PlatePlugin, } from '@udecode/plate-common'; import React from 'react'; @@ -17,32 +17,9 @@ import { ReactComponent as UnderlineIcon } from '../../icons/underline.svg'; import { handleMarkButtonClick } from '../../utils/handleMarkButtonClick'; import { BOLD_KEY, ITALIC_KEY, UNDERLINE_KEY } from './constants'; -// eslint-disable-next-line react/function-component-definition -const Bold: PlatePluginComponent = (props) => { - const { attributes, children } = props; - - return ( - { children } - ); -}; - -// eslint-disable-next-line react/function-component-definition -const Italic: PlatePluginComponent = (props) => { - const { attributes, children } = props; - - return ( - { children } - ); -}; - -// eslint-disable-next-line react/function-component-definition -const Underline: PlatePluginComponent = (props) => { - const { attributes, children } = props; - - return ( - { children } - ); -}; +const Bold = (props: PlateLeafProps) => ; +const Italic = (props: PlateLeafProps) => ; +const Underline = (props: PlateLeafProps) => ; export const boldPlugin = (): PlatePlugin => createBoldPlugin({ type: BOLD_KEY, diff --git a/uui-editor/src/plugins/iframePlugin/IframeBlock.tsx b/uui-editor/src/plugins/iframePlugin/IframeBlock.tsx index ed10c8796b..0e391293b1 100644 --- a/uui-editor/src/plugins/iframePlugin/IframeBlock.tsx +++ b/uui-editor/src/plugins/iframePlugin/IframeBlock.tsx @@ -4,18 +4,14 @@ import { uuiMod } from '@epam/uui-core'; import cx from 'classnames'; import { sanitizeUrl } from '@braintree/sanitize-url'; import { useSelected } from 'slate-react'; -import { AnyObject, PlatePluginComponent } from '@udecode/plate-common'; +import { PlateElement, PlateElementProps, useElement } from '@udecode/plate-common'; import { TIframeElement } from './types'; const IFRAME_GLOBAL_CLASS = 'uui-rte-iframe'; const PDF_GLOBAL_CLASS = 'uui-rte-iframe-pdf'; -export const IframeBlock: PlatePluginComponent<{ - attributes: AnyObject, - children: React.ReactNode, - element: TIframeElement -}> = function IframeComp(props) { - const { attributes, children, element } = props; +export const IframeBlock = function IframeComp({ children, ...props }: PlateElementProps) { + const element = useElement(); const isSelected = useSelected(); const isPdf = element.data?.extension === 'pdf'; @@ -24,8 +20,7 @@ export const IframeBlock: PlatePluginComponent<{ const url: string = element.url || element.src as string; // element.src it's previous editor format structure return ( - // style attr needed for serialization -
+