diff --git a/changelog.md b/changelog.md index fb2ab09f68..f60951a443 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** @@ -29,6 +30,7 @@ * Fixed Property Explorer by providing values for properties that are not automatically resolved ([#2832](https://github.com/epam/UUI/issues/2832)) * Fixed tree table indentation when child rows have no checkbox: child rows now reserve consistent checkbox space for alignment ([#2844](https://github.com/epam/UUI/issues/2844)) * [Tooltip]: fixed tooltip not showing on keyboard focus for complex elements with focusable children (e.g. Switch) ([#2959](https://github.com/epam/UUI/issues/2959)) +* [RTE]: Todo list items migrate legacy `element.data.checked` to `element.checked`. Iframe nodes normalize `url` from `url` or legacy `data.src`. # 6.4.3 - 04.02.2026 diff --git a/uui-docs/src/demoData/slateInitialValue.ts b/uui-docs/src/demoData/slateInitialValue.ts index f58f77ca4b..c372b8312d 100644 --- a/uui-docs/src/demoData/slateInitialValue.ts +++ b/uui-docs/src/demoData/slateInitialValue.ts @@ -79,9 +79,7 @@ export const slateInitialValue = [ }, { type: 'toDoItem', - data: { - checked: false, - }, + checked: true, children: [ { text: ' An item', diff --git a/uui-editor/src/__tests__/__snapshots__/normalizers.test.ts.snap b/uui-editor/src/__tests__/__snapshots__/normalizers.test.ts.snap index 1c7eb3f356..67a6e50793 100644 --- a/uui-editor/src/__tests__/__snapshots__/normalizers.test.ts.snap +++ b/uui-editor/src/__tests__/__snapshots__/normalizers.test.ts.snap @@ -73,15 +73,14 @@ Array [ "type": "paragraph", }, Object { + "checked": false, "children": Array [ Object { "text": " An item", "uui-richTextEditor-span-mark": true, }, ], - "data": Object { - "checked": false, - }, + "data": Object {}, "type": "toDoItem", }, Object { @@ -417,6 +416,17 @@ Array [ "type": "iframe", "url": "https://www.youtube.com/embed/5qap5aO4i9A", }, + Object { + "children": Array [ + Object { + "text": "", + "uui-richTextEditor-span-mark": true, + }, + ], + "data": Object {}, + "type": "iframe", + "url": "https://www.youtube.com/embed/5qap5aO4i9A", + }, Object { "children": Array [ Object { diff --git a/uui-editor/src/__tests__/data/plate-migration.ts b/uui-editor/src/__tests__/data/plate-migration.ts index 848fb39af4..bd071abbf1 100644 --- a/uui-editor/src/__tests__/data/plate-migration.ts +++ b/uui-editor/src/__tests__/data/plate-migration.ts @@ -424,6 +424,18 @@ export const initialValue = [ ], url: 'https://www.youtube.com/embed/5qap5aO4i9A', }, + { + data: { + src: 'https://www.youtube.com/embed/5qap5aO4i9A', + }, + type: 'iframe', + children: [ + { + text: '', + 'uui-richTextEditor-span-mark': true, + }, + ], + }, { data: { checked: false, 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/migrations/normalizers.ts b/uui-editor/src/migrations/normalizers.ts index 2290d691d1..769942c0f4 100644 --- a/uui-editor/src/migrations/normalizers.ts +++ b/uui-editor/src/migrations/normalizers.ts @@ -1,4 +1,5 @@ import { Value, setNodes, PlateEditor, TNodeEntry } from '@udecode/plate-common'; +import { TTodoListItemElement } from '@udecode/plate-list'; import { TLinkElement } from '@udecode/plate-link'; import { TTableCellElement, TTableElement } from '@udecode/plate-table'; import { TAttachmentElement } from '../plugins/attachmentPlugin/types'; @@ -6,6 +7,7 @@ import { TIframeElement } from '../plugins/iframePlugin/types'; import { TImageElement } from '../plugins/imagePlugin/types'; import { toNewAlign } from './legacy_migrations'; import { DepreactedTTableElement, DeprecatedImageElement, DeprecatedTAttachmentElement, DeprecatedTIframeElement, DeprecatedTLinkElement, DeprecatedTTableCellElement } from './types'; +import { isLegacyTodoListItemElement } from './utils'; /** * Migration property functions @@ -116,14 +118,20 @@ export const normalizeIframeElement = (editor: PlateEditor, entry: TNodeE const iframeNode = node as DeprecatedTIframeElement; if (iframeNode.data) { - const { src, ...otherData } = iframeNode.data; + const { data: { src, ...otherData }, url } = iframeNode; // removing props if (!src) { return; } - const iframe: TIframeElement = { ...iframeNode, data: { ...otherData } }; + const newUrl = url || src; + + const iframe: TIframeElement = { + ...iframeNode, + url: newUrl, + data: { ...otherData }, + }; setNodes( editor, @@ -175,3 +183,28 @@ export const normaizeColoredText = (editor: PlateEditor, entry: TNodeEntr ); } }; + +/** migrate checked property if needed */ +export const migrateCheckedPropertyIfNeeded = ( + editor: PlateEditor, + entry: TNodeEntry, +) => { + const [node, path] = entry; + + if (isLegacyTodoListItemElement(node)) { + const { data: { checked, ...otherData }, ...otherNodeData } = node; + const updatedNode: TTodoListItemElement = { + ...otherNodeData, + data: { + ...otherData, + }, + checked: checked, + }; + + setNodes( + editor, + updatedNode, + { at: path }, + ); + } +}; diff --git a/uui-editor/src/migrations/types.ts b/uui-editor/src/migrations/types.ts index 994a6cabcd..89d3cce3ac 100644 --- a/uui-editor/src/migrations/types.ts +++ b/uui-editor/src/migrations/types.ts @@ -1,5 +1,6 @@ import { TLinkElement } from '@udecode/plate-link'; import { TTableCellElement, TTableElement } from '@udecode/plate-table'; +import { TTodoListItemElement } from '@udecode/plate-list'; import { TAttachmentElement } from '../plugins/attachmentPlugin/types'; import { TIframeElement } from '../plugins/iframePlugin/types'; import { TImageElement } from '../plugins/imagePlugin/types'; @@ -51,6 +52,12 @@ export type DeprecatedTAttachmentElement = TAttachmentElement & { } }; +export type DeprecatedTTodoListItemElement = TTodoListItemElement & { + data?: TTodoListItemElement['data'] & { + checked?: boolean; // removed + } +}; + /** * Legacy slate schema types */ diff --git a/uui-editor/src/migrations/utils.ts b/uui-editor/src/migrations/utils.ts index e64ccc4e9f..f40d7e59ca 100644 --- a/uui-editor/src/migrations/utils.ts +++ b/uui-editor/src/migrations/utils.ts @@ -1,7 +1,7 @@ -import { Value } from '@udecode/plate-common'; +import { TElement, Value } from '@udecode/plate-common'; import { EditorValue } from '../types'; import { migrateLegacySchema } from './legacy_migrations'; -import { SlateSchema } from './types'; +import { DeprecatedTTodoListItemElement, SlateSchema } from './types'; /** type guard to distinct slate format */ export const isSlateSchema = (value: EditorValue): value is SlateSchema => { @@ -21,3 +21,7 @@ export const getMigratedPlateValue = (value: EditorValue): Value | undefined => export const isPlateValue = (value: EditorValue): value is Value => { return Array.isArray(value); }; + +export const isLegacyTodoListItemElement = (element: TElement): element is DeprecatedTTodoListItemElement => + element.data !== undefined + && 'checked' in (element.data as Record); 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..a47ab989b7 100644 --- a/uui-editor/src/plugins/iframePlugin/IframeBlock.tsx +++ b/uui-editor/src/plugins/iframePlugin/IframeBlock.tsx @@ -4,28 +4,23 @@ 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'; const style = element.data?.style; - const url: string = element.url || element.src as string; // element.src it's previous editor format structure + const url: string = element.url; return ( - // style attr needed for serialization -
+