diff --git a/.changeset/sixty-bats-explode.md b/.changeset/sixty-bats-explode.md new file mode 100644 index 0000000000..c7231d2dd6 --- /dev/null +++ b/.changeset/sixty-bats-explode.md @@ -0,0 +1,6 @@ +--- +'@udecode/plate-serializer-html': major +--- + +- [Breaking] `serializeHtml`: replaced option `slateProps` by `plateProps`. +- Fix errors when the components were using Plate hooks. diff --git a/.changeset/table.md b/.changeset/table.md new file mode 100644 index 0000000000..bf18831c1b --- /dev/null +++ b/.changeset/table.md @@ -0,0 +1,5 @@ +--- +'@udecode/plate-table': minor +--- + +- Table plugin has now merging support. To enable it, use option `enableMerging: true` diff --git a/apps/www/src/components/icons.tsx b/apps/www/src/components/icons.tsx index 793de4ff5d..bc3fb4ef83 100644 --- a/apps/www/src/components/icons.tsx +++ b/apps/www/src/components/icons.tsx @@ -15,6 +15,7 @@ import { ChevronsUpDown, ClipboardCheck, Code2, + Combine, Copy, DownloadCloud, ExternalLink, @@ -73,6 +74,7 @@ import { Trash, Twitter, Underline, + Ungroup, Unlink, WrapText, X, @@ -281,6 +283,8 @@ export const Icons = { codeblock: FileCode, color: Baseline, column: RectangleVertical, + combine: Combine, + ungroup: Ungroup, comment: MessageSquare, commentAdd: MessageSquarePlus, conflict: Unlink, diff --git a/apps/www/src/lib/plate/demo/values/tableValue.tsx b/apps/www/src/lib/plate/demo/values/tableValue.tsx index e124c81caf..ecf8252c97 100644 --- a/apps/www/src/lib/plate/demo/values/tableValue.tsx +++ b/apps/www/src/lib/plate/demo/values/tableValue.tsx @@ -118,4 +118,4 @@ export const tableValue: any = ( {createSpanningTable()} -); +); \ No newline at end of file diff --git a/apps/www/src/registry/default/plate-ui/table-cell-element.tsx b/apps/www/src/registry/default/plate-ui/table-cell-element.tsx index 0effb3e381..818f19b8b8 100644 --- a/apps/www/src/registry/default/plate-ui/table-cell-element.tsx +++ b/apps/www/src/registry/default/plate-ui/table-cell-element.tsx @@ -34,12 +34,15 @@ const TableCellElement = React.forwardRef< rowSize, borders, isSelectingCell, + colSpan, } = useTableCellElementState(); const { props: cellProps } = useTableCellElement({ element: props.element }); const resizableState = useTableCellElementResizableState({ colIndex, rowIndex, + colSpan, }); + const { rightProps, bottomProps, leftProps, hiddenLeft } = useTableCellElementResizable(resizableState); @@ -50,7 +53,7 @@ const TableCellElement = React.forwardRef< asChild ref={ref} className={cn( - 'relative overflow-visible border-none bg-background p-0', + 'relative h-full overflow-visible border-none bg-background p-0', hideBorder && 'before:border-none', element.background ? 'bg-[--cellBackground]' : 'bg-background', !hideBorder && diff --git a/apps/www/src/registry/default/plate-ui/table-element.tsx b/apps/www/src/registry/default/plate-ui/table-element.tsx index 713679f69b..b016d57db9 100644 --- a/apps/www/src/registry/default/plate-ui/table-element.tsx +++ b/apps/www/src/registry/default/plate-ui/table-element.tsx @@ -10,10 +10,13 @@ import { useRemoveNodeButton, } from '@udecode/plate-common'; import { + mergeTableCells, TTableElement, + unmergeTableCells, useTableBordersDropdownMenuContentState, useTableElement, useTableElementState, + useTableMergeState, } from '@udecode/plate-table'; import { useReadOnly, useSelected } from 'slate-react'; @@ -114,35 +117,73 @@ const TableFloatingToolbar = React.forwardRef< const readOnly = useReadOnly(); const selected = useSelected(); const editor = useEditorState(); - const open = !readOnly && selected && isCollapsed(editor.selection); + + const collapsed = !readOnly && selected && isCollapsed(editor.selection); + const open = !readOnly && selected; + + const { canMerge, canUnmerge } = useTableMergeState(); + + const mergeContent = canMerge && ( + + ); + + const unmergeButton = canUnmerge && ( + + ); + + const bordersContent = collapsed && ( + <> + + + + + + + + + + + + + ); return ( {children} - e.preventDefault()} - {...props} - > - - - - - - - - - - - - + {(canMerge || canUnmerge || collapsed) && ( + e.preventDefault()} + {...props} + > + {unmergeButton} + {mergeContent} + {bordersContent} + + )} ); }); diff --git a/packages/resizable/src/components/ResizeHandle.tsx b/packages/resizable/src/components/ResizeHandle.tsx index 4d71263674..74088de05d 100644 --- a/packages/resizable/src/components/ResizeHandle.tsx +++ b/packages/resizable/src/components/ResizeHandle.tsx @@ -51,6 +51,7 @@ export const ResizeHandleProvider = ({ export type ResizeHandleOptions = { direction?: ResizeDirection; + initialSize?: number; onResize?: (event: ResizeEvent) => void; onMouseDown?: MouseEventHandler; onTouchStart?: TouchEventHandler; @@ -60,6 +61,7 @@ export type ResizeHandleOptions = { export const useResizeHandleState = ({ direction = 'left', + initialSize: _initialSize, onResize, onMouseDown, onTouchStart, @@ -88,7 +90,12 @@ export const useResizeHandleState = ({ const currentPosition = isHorizontal ? clientX : clientY; const delta = currentPosition - initialPosition; - onResize?.({ initialSize, delta, finished, direction }); + onResize?.({ + initialSize: _initialSize || initialSize, + delta, + finished, + direction, + }); }; const handleMouseMove = (event: MouseEvent | TouchEvent) => diff --git a/packages/serializer-html/src/elementToHtml.ts b/packages/serializer-html/src/elementToHtml.ts index 979ccba3fc..10de567a5f 100644 --- a/packages/serializer-html/src/elementToHtml.ts +++ b/packages/serializer-html/src/elementToHtml.ts @@ -2,9 +2,9 @@ import { ComponentClass, FunctionComponent } from 'react'; import { pipeInjectProps, PlateEditor, + PlateProps, PlateRenderElementProps, pluginRenderElement, - SlateProps, Value, } from '@udecode/plate-common'; import { decode } from 'html-entities'; @@ -17,12 +17,12 @@ export const elementToHtml = ( editor: PlateEditor, { props, - slateProps, + plateProps, preserveClassNames, dndWrapper, }: { props: PlateRenderElementProps; - slateProps?: Partial; + plateProps?: Partial; preserveClassNames?: string[]; dndWrapper?: string | FunctionComponent | ComponentClass; } @@ -50,8 +50,8 @@ export const elementToHtml = ( renderToStaticMarkup( createElementWithSlate( { - ...slateProps, - + editor: editor as any, + ...plateProps, children: plugin.serializeHtml?.(props as any) ?? pluginRenderElement(editor, plugin)(props), diff --git a/packages/serializer-html/src/leafToHtml.ts b/packages/serializer-html/src/leafToHtml.ts index 38d0deaf96..6d2cdf6af3 100644 --- a/packages/serializer-html/src/leafToHtml.ts +++ b/packages/serializer-html/src/leafToHtml.ts @@ -1,9 +1,9 @@ import { pipeInjectProps, PlateEditor, + PlateProps, PlateRenderLeafProps, pluginRenderLeaf, - SlateProps, Value, } from '@udecode/plate-common'; import { decode } from 'html-entities'; @@ -16,11 +16,11 @@ export const leafToHtml = ( editor: PlateEditor, { props, - slateProps, + plateProps, preserveClassNames, }: { props: PlateRenderLeafProps; - slateProps?: Partial; + plateProps?: Partial; preserveClassNames?: string[]; } ) => { @@ -43,7 +43,7 @@ export const leafToHtml = ( let html = decode( renderToStaticMarkup( createElementWithSlate({ - ...slateProps, + ...plateProps, children: serialized, }) ) diff --git a/packages/serializer-html/src/serializeHtml.ts b/packages/serializer-html/src/serializeHtml.ts index 8bd95d5464..5978c17557 100644 --- a/packages/serializer-html/src/serializeHtml.ts +++ b/packages/serializer-html/src/serializeHtml.ts @@ -4,7 +4,7 @@ import { EElement, isText, PlateEditor, - SlateProps, + PlateProps, Value, } from '@udecode/plate-common'; import { encode } from 'html-entities'; @@ -22,7 +22,7 @@ export const serializeHtml = ( editor: PlateEditor, { nodes, - slateProps, + plateProps, stripDataAttributes = true, preserveClassNames, stripWhitespace = true, @@ -45,9 +45,9 @@ export const serializeHtml = ( preserveClassNames?: string[]; /** - * Slate props to provide if the rendering depends on slate hooks + * Slate props to provide if the rendering depends on plate/slate hooks */ - slateProps?: Partial; + plateProps?: Partial; /** * Whether stripping whitespaces from serialized HTML @@ -82,7 +82,7 @@ export const serializeHtml = ( attributes: { 'data-slate-leaf': true }, editor, }, - slateProps, + plateProps, preserveClassNames, }); } @@ -99,7 +99,7 @@ export const serializeHtml = ( attributes: { 'data-slate-node': 'element', ref: null }, editor, }, - slateProps, + plateProps, preserveClassNames, dndWrapper, }); diff --git a/packages/serializer-html/src/utils/createElementWithSlate.ts b/packages/serializer-html/src/utils/createElementWithSlate.ts index b6aff3354c..b18b3925a5 100644 --- a/packages/serializer-html/src/utils/createElementWithSlate.ts +++ b/packages/serializer-html/src/utils/createElementWithSlate.ts @@ -1,49 +1,35 @@ import React, { ComponentClass, FunctionComponent } from 'react'; -import { createTEditor, SlateProps, withTReact } from '@udecode/plate-common'; -import { Slate } from 'slate-react'; +import { Plate, PlateProps } from '@udecode/plate-common'; /** - * Create a React element wrapped in a Slate provider. - * By default, it will use an empty editor. - * TODO: allow other providers + * Create a React element wrapped in a Plate provider. */ export const createElementWithSlate = ( - slateProps?: Partial, + plateProps?: Partial, dndWrapper?: string | FunctionComponent | ComponentClass ) => { const { - editor = withTReact(createTEditor()), + editor, value = [], onChange = () => {}, children, ...props - } = slateProps || {}; + } = plateProps || {}; - if (dndWrapper) { - return React.createElement( - dndWrapper, - null, - React.createElement( - Slate, - { - editor, - initialValue: value, - onChange, - ...props, - } as any, - children - ) - ); - } - - return React.createElement( - Slate, + const plate = React.createElement( + Plate, { editor, initialValue: value, onChange, ...props, - } as any, + } as PlateProps, children ); + + if (dndWrapper) { + return React.createElement(dndWrapper, null, plate); + } + + return plate; }; diff --git a/packages/table/src/components/TableCellElement/useTableCellElementResizable.ts b/packages/table/src/components/TableCellElement/useTableCellElementResizable.ts index 7a08cdcb61..a4158dbed4 100644 --- a/packages/table/src/components/TableCellElement/useTableCellElementResizable.ts +++ b/packages/table/src/components/TableCellElement/useTableCellElementResizable.ts @@ -30,7 +30,7 @@ import { TableCellElementState } from './useTableCellElementState'; export type TableCellElementResizableOptions = Pick< TableCellElementState, - 'colIndex' | 'rowIndex' + 'colIndex' | 'rowIndex' | 'colSpan' > & { /** * Resize by step instead of by pixel. @@ -50,6 +50,7 @@ export const useTableCellElementResizableState = ({ step, stepX = step, stepY = step, + colSpan, }: TableCellElementResizableOptions) => { const editor = useEditorRef(); const { disableMarginLeft } = getPluginOptions( @@ -63,6 +64,7 @@ export const useTableCellElementResizableState = ({ rowIndex, stepX, stepY, + colSpan, }; }; @@ -72,6 +74,7 @@ export const useTableCellElementResizable = ({ rowIndex, stepX, stepY, + colSpan, }: ReturnType): { rightProps: ComponentPropsWithoutRef; bottomProps: ComponentPropsWithoutRef; @@ -86,6 +89,11 @@ export const useTableCellElementResizable = ({ ELEMENT_TABLE ); + let initialWidth: number | undefined; + if (colSpan > 1) { + initialWidth = tableElement.colSizes?.[colIndex]; + } + const [hoveredColIndex, setHoveredColIndex] = useTableStore().use.hoveredColIndex(); @@ -246,6 +254,7 @@ export const useTableCellElementResizable = ({ rightProps: { options: { direction: 'right', + initialSize: initialWidth, onResize: handleResizeRight, ...getHandleHoverProps(colIndex), }, diff --git a/packages/table/src/components/TableCellElement/useTableCellElementState.ts b/packages/table/src/components/TableCellElement/useTableCellElementState.ts index e14c7eb809..3edf24fee1 100644 --- a/packages/table/src/components/TableCellElement/useTableCellElementState.ts +++ b/packages/table/src/components/TableCellElement/useTableCellElementState.ts @@ -1,11 +1,20 @@ import { useEffect } from 'react'; -import { useEditorRef, useElement } from '@udecode/plate-common'; +import { + getPluginOptions, + useEditorRef, + useElement, +} from '@udecode/plate-common'; import { useReadOnly } from 'slate-react'; import { ELEMENT_TABLE, ELEMENT_TR } from '../../createTablePlugin'; +import { computeCellIndices } from '../../merge/computeCellIndices'; +import { getCellIndices } from '../../merge/getCellIndices'; +import { getColSpan } from '../../queries/getColSpan'; +import { getRowSpan } from '../../queries/getRowSpan'; import { getTableColumnIndex, getTableRowIndex } from '../../queries/index'; import { useTableStore } from '../../stores/tableStore'; import { + TablePlugin, TTableCellElement, TTableElement, TTableRowElement, @@ -26,6 +35,7 @@ export type TableCellElementState = { rowSize: number | undefined; borders: BorderStylesDefault; isSelectingCell: boolean; + colSpan: number; }; export const useTableCellElementState = ({ @@ -39,8 +49,8 @@ export const useTableCellElementState = ({ const editor = useEditorRef(); const cellElement = useElement(); - const colIndex = getTableColumnIndex(editor, cellElement); - const rowIndex = getTableRowIndex(editor, cellElement); + const colSpan = getColSpan(cellElement); + const rowSpan = getRowSpan(cellElement); const readOnly = useReadOnly(); @@ -51,8 +61,61 @@ export const useTableCellElementState = ({ const tableElement = useElement(ELEMENT_TABLE); const rowElement = useElement(ELEMENT_TR); const rowSizeOverrides = useTableStore().get.rowSizeOverrides(); + + const { enableMerging, _cellIndices } = getPluginOptions( + editor as any, + ELEMENT_TABLE + ); + if (!enableMerging) { + const colIndex = getTableColumnIndex(editor, cellElement); + const rowIndex = getTableRowIndex(editor, cellElement); + + const rowSize = + rowSizeOverrides.get(rowIndex) ?? rowElement?.size ?? undefined; + + const isFirstCell = colIndex === 0; + const isFirstRow = tableElement.children?.[0] === rowElement; + + const borders = getTableCellBorders(cellElement, { + isFirstCell, + isFirstRow, + }); + + return { + colIndex, + rowIndex, + readOnly: !ignoreReadOnly && readOnly, + selected: isCellSelected, + hovered: hoveredColIndex === colIndex, + hoveredLeft: isFirstCell && hoveredColIndex === -1, + rowSize, + borders, + isSelectingCell: !!selectedCells, + colSpan, + }; + } + + let result: { col: number; row: number }; + + const calculated = + getCellIndices(_cellIndices!, cellElement) || + computeCellIndices(editor, tableElement, cellElement); + + if (calculated) { + result = calculated; + } else { + const defaultColIndex = getTableColumnIndex(editor, cellElement); + const defaultRowIndex = getTableRowIndex(editor, cellElement); + result = { col: defaultColIndex, row: defaultRowIndex }; + } + const colIndex = result.col; + const rowIndex = result.row; + + const endingRowIndex = rowIndex + rowSpan - 1; + const endingColIndex = colIndex + colSpan - 1; + const rowSize = - rowSizeOverrides.get(rowIndex) ?? rowElement?.size ?? undefined; + rowSizeOverrides.get(endingRowIndex) ?? rowElement?.size ?? undefined; const isFirstCell = colIndex === 0; const isFirstRow = tableElement.children?.[0] === rowElement; @@ -63,15 +126,16 @@ export const useTableCellElementState = ({ }); return { - colIndex, - rowIndex, + colIndex: endingColIndex, + rowIndex: endingRowIndex, readOnly: !ignoreReadOnly && readOnly, selected: isCellSelected, - hovered: hoveredColIndex === colIndex, + hovered: hoveredColIndex === endingColIndex, hoveredLeft: isFirstCell && hoveredColIndex === -1, rowSize, borders, isSelectingCell: !!selectedCells, + colSpan, }; }; @@ -89,6 +153,7 @@ export const useTableCellElement = ({ return { props: { colSpan: element.colSpan, + rowSpan: element.rowSpan, }, }; }; diff --git a/packages/table/src/components/TableElement/useSelectedCells.ts b/packages/table/src/components/TableElement/useSelectedCells.ts index 366b5cc164..b7f426c710 100644 --- a/packages/table/src/components/TableElement/useSelectedCells.ts +++ b/packages/table/src/components/TableElement/useSelectedCells.ts @@ -16,23 +16,38 @@ export const useSelectedCells = () => { const editor = useEditorRef(); const [selectedCells, setSelectedCells] = useTableStore().use.selectedCells(); + const setSelectedTable = useTableStore().set.selectedTable(); useEffect(() => { - if (!selected || readOnly) setSelectedCells(null); - }, [selected, editor, setSelectedCells, readOnly]); + if (!selected || readOnly) { + setSelectedCells(null); + setSelectedTable(null); + } + }, [selected, editor, setSelectedCells, readOnly, setSelectedTable]); useEffect(() => { if (readOnly) return; + const tableEntries = getTableGridAbove(editor, { format: 'table' }); const cellEntries = getTableGridAbove(editor, { format: 'cell' }); - if (cellEntries.length > 1) { + if (cellEntries?.length > 1) { const cells = cellEntries.map((entry) => entry[0]); + const tables = tableEntries.map((entry) => entry[0]); if (JSON.stringify(cells) !== JSON.stringify(selectedCells)) { setSelectedCells(cells); + setSelectedTable(tables); } } else if (selectedCells) { setSelectedCells(null); + setSelectedTable(null); } - }, [editor, editor?.selection, readOnly, selectedCells, setSelectedCells]); + }, [ + editor, + editor.selection, + readOnly, + selectedCells, + setSelectedCells, + setSelectedTable, + ]); }; diff --git a/packages/table/src/components/TableElement/useTableElement.ts b/packages/table/src/components/TableElement/useTableElement.ts index e0d5b76765..694f098ffe 100644 --- a/packages/table/src/components/TableElement/useTableElement.ts +++ b/packages/table/src/components/TableElement/useTableElement.ts @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { collapseSelection, getPluginOptions, @@ -6,6 +7,7 @@ import { } from '@udecode/plate-common'; import { ELEMENT_TABLE } from '../../createTablePlugin'; +import { computeAllCellIndices } from '../../merge/computeCellIndices'; import { useTableStore } from '../../stores/tableStore'; import { TablePlugin, TTableElement } from '../../types'; import { useSelectedCells } from './useSelectedCells'; @@ -28,10 +30,8 @@ export const useTableElementState = ({ } = {}): TableElementState => { const editor = useEditorRef(); - const { minColumnWidth, disableMarginLeft } = getPluginOptions( - editor, - ELEMENT_TABLE - ); + const { minColumnWidth, disableMarginLeft, enableMerging } = + getPluginOptions(editor, ELEMENT_TABLE); const element = useElement(); const selectedCells = useTableStore().get.selectedCells(); @@ -43,6 +43,12 @@ export const useTableElementState = ({ let colSizes = useTableColSizes(element); + useEffect(() => { + if (enableMerging) { + computeAllCellIndices(editor, element); + } + }, [editor, element, enableMerging]); + if (transformColSizes) { colSizes = transformColSizes(colSizes); } diff --git a/packages/table/src/constants.ts b/packages/table/src/constants.ts index 1f876c4c97..38e37070b3 100644 --- a/packages/table/src/constants.ts +++ b/packages/table/src/constants.ts @@ -4,3 +4,5 @@ export const keyShiftEdges = { 'shift+down': 'bottom', 'shift+left': 'left', }; + +export const MIN_COLUMN_HEIGHT = 48; diff --git a/packages/table/src/createTablePlugin.ts b/packages/table/src/createTablePlugin.ts index 533f30ef13..9e1384ba0e 100644 --- a/packages/table/src/createTablePlugin.ts +++ b/packages/table/src/createTablePlugin.ts @@ -2,7 +2,7 @@ import { createPluginFactory } from '@udecode/plate-common'; import { onKeyDownTable } from './onKeyDownTable'; import { insertTableColumn, insertTableRow } from './transforms/index'; -import { TablePlugin } from './types'; +import { TablePlugin, TableStoreCellAttributes } from './types'; import { withTable } from './withTable'; export const ELEMENT_TABLE = 'table'; @@ -36,6 +36,8 @@ export const createTablePlugin = createPluginFactory({ }); }, minColumnWidth: 48, + enableMerging: false, + _cellIndices: new WeakMap() as TableStoreCellAttributes, }, withOverrides: withTable, plugins: [ diff --git a/packages/table/src/index.ts b/packages/table/src/index.ts index 81ea22f4b0..8821555967 100644 --- a/packages/table/src/index.ts +++ b/packages/table/src/index.ts @@ -19,3 +19,4 @@ export * from './queries/index'; export * from './stores/index'; export * from './transforms/index'; export * from './utils/index'; +export * from './merge/index'; diff --git a/packages/table/src/merge/computeCellIndices.ts b/packages/table/src/merge/computeCellIndices.ts new file mode 100644 index 0000000000..57c727f86c --- /dev/null +++ b/packages/table/src/merge/computeCellIndices.ts @@ -0,0 +1,89 @@ +import { getPluginOptions, PlateEditor, Value } from '@udecode/plate-common'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { + TablePlugin, + TTableCellElement, + TTableElement, + TTableRowElement, +} from '../types'; + +export function computeCellIndices( + editor: PlateEditor, + tableEl: TTableElement, + cellEl: TTableCellElement +) { + const options = getPluginOptions(editor, ELEMENT_TABLE); + + const tableNodes = tableEl.children; + + let rowIndex = -1; + let colIndex = -1; + + for (let r = 0; r < tableNodes.length; r++) { + const row = tableNodes[r] as TTableRowElement; + + let cIndex = 0; + for (let c = 0; c < row.children.length; c++) { + const cell = row.children[c] as TTableCellElement; + if (cellEl === cell) { + colIndex = cIndex; + rowIndex = r; + break; + } + cIndex += cell.colSpan || 1; // consider 0 and undefined as 1 + } + } + + tableNodes.slice(0, rowIndex).forEach((pR, _rowIndex) => { + const prevRow = pR as TTableRowElement; + prevRow.children.forEach((pC) => { + const prevCell = pC as TTableCellElement; + const prevIndices = options?._cellIndices?.get(prevCell); + if (prevIndices) { + const { col: prevColIndex } = prevIndices; + if ( + // colIndex affects + prevColIndex <= colIndex && + // rowSpan affects + prevCell.rowSpan && + prevCell.rowSpan > 1 && + rowIndex - _rowIndex < prevCell.rowSpan + ) { + colIndex += prevCell.colSpan || 1; + } + } + }); + }); + + if (rowIndex === -1 || colIndex === -1) { + return null; + } + + const indices = { row: rowIndex, col: colIndex }; + options?._cellIndices?.set(cellEl, indices); + + return indices; +} + +export const computeAllCellIndices = ( + editor: PlateEditor, + tableNode: TTableElement +) => { + const options = getPluginOptions(editor, ELEMENT_TABLE); + + // Iterate through the table rows + for (let r = 0; r < tableNode.children.length; r++) { + const row = tableNode.children[r] as TTableRowElement; + + // Iterate through the row cells + for (let c = 0; c < row.children.length; c++) { + const cell = row.children[c] as TTableCellElement; + + const indices = computeCellIndices(editor, tableNode, cell); + if (indices) { + options._cellIndices?.set(cell, indices); + } + } + } +}; diff --git a/packages/table/src/merge/createEmptyCell.ts b/packages/table/src/merge/createEmptyCell.ts new file mode 100644 index 0000000000..d9fd8c7ffd --- /dev/null +++ b/packages/table/src/merge/createEmptyCell.ts @@ -0,0 +1,30 @@ +import { + getPluginType, + PlateEditor, + TDescendant, + TElement, + Value, +} from '@udecode/plate-common'; + +import { ELEMENT_TH } from '../createTablePlugin'; +import { TTableRowElement } from '../types'; +import { getEmptyCellNode } from '../utils'; + +export const createEmptyCell = ( + editor: PlateEditor, + row: TTableRowElement, + newCellChildren?: TDescendant[], + header?: boolean +) => { + const isHeaderRow = + header === undefined + ? (row as TElement).children.every( + (c) => c.type === getPluginType(editor, ELEMENT_TH) + ) + : header; + + return getEmptyCellNode(editor, { + header: isHeaderRow, + newCellChildren, + }); +}; diff --git a/packages/table/src/merge/deleteColumn.ts b/packages/table/src/merge/deleteColumn.ts new file mode 100644 index 0000000000..7b466576de --- /dev/null +++ b/packages/table/src/merge/deleteColumn.ts @@ -0,0 +1,195 @@ +import { + getAboveNode, + getPluginOptions, + getPluginType, + PlateEditor, + removeNodes, + setNodes, + someNode, + Value, + withoutNormalizing, +} from '@udecode/plate-common'; +import { Path } from 'slate'; + +import { ELEMENT_TABLE, ELEMENT_TR } from '../createTablePlugin'; +import { getColSpan } from '../queries/getColSpan'; +import { TablePlugin, TTableCellElement, TTableElement } from '../types'; +import { getCellTypes } from '../utils'; +import { findCellByIndexes } from './findCellByIndexes'; +import { getCellIndices } from './getCellIndices'; +import { getCellPath } from './getCellPath'; + +export const deleteTableMergeColumn = ( + editor: PlateEditor +) => { + if ( + someNode(editor, { + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + }) + ) { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const tableEntry = getAboveNode(editor, { + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + }); + if (!tableEntry) return; + const table = tableEntry[0] as TTableElement; + + const selectedCellEntry = getAboveNode(editor, { + match: { + type: getCellTypes(editor), + }, + }); + if (!selectedCellEntry) return; + const selectedCell = selectedCellEntry[0] as TTableCellElement; + + const { col: deletingColIndex } = getCellIndices( + cellIndices!, + selectedCell + )!; + const colsDeleteNumber = getColSpan(selectedCell); + + const endingColIndex = deletingColIndex + colsDeleteNumber - 1; + + const rowNumber = table.children.length; + const affectedCellsSet = new Set(); + // iterating by rows is important here to keep the order of affected cells + Array.from({ length: rowNumber }, (_, i) => i).forEach((rI) => { + return Array.from({ length: colsDeleteNumber }, (_, i) => i).forEach( + (cI) => { + const colIndex = deletingColIndex + cI; + const found = findCellByIndexes(editor, table, rI, colIndex); + if (found) { + affectedCellsSet.add(found); + } + } + ); + }); + const affectedCells = Array.from(affectedCellsSet) as TTableCellElement[]; + + const { squizeColSpanCells } = affectedCells.reduce<{ + squizeColSpanCells: TTableCellElement[]; + }>( + (acc, cur) => { + if (!cur) return acc; + + const currentCell = cur as TTableCellElement; + const { col: curColIndex } = getCellIndices(cellIndices!, currentCell)!; + const curColSpan = getColSpan(currentCell); + + if (curColIndex < deletingColIndex && curColSpan > 1) { + acc.squizeColSpanCells.push(currentCell); + } else if ( + curColSpan > 1 && + curColIndex + curColSpan - 1 > endingColIndex + ) { + acc.squizeColSpanCells.push(currentCell); + } + return acc; + }, + { squizeColSpanCells: [] } + ); + + /** + * Change colSpans + */ + squizeColSpanCells.forEach((cur) => { + const curCell = cur as TTableCellElement; + + const { col: curColIndex, row: curColRowIndex } = getCellIndices( + cellIndices!, + curCell + )!; + const curColSpan = getColSpan(curCell); + + const curCellPath = getCellPath( + editor, + tableEntry, + curColRowIndex, + curColIndex + ); + + const curCellEndingColIndex = Math.min( + curColIndex + curColSpan - 1, + endingColIndex + ); + const colsNumberAffected = curCellEndingColIndex - deletingColIndex + 1; + + setNodes( + editor, + { ...curCell, colSpan: curColSpan - colsNumberAffected }, + { at: curCellPath } + ); + }); + + const trEntry = getAboveNode(editor, { + match: { type: getPluginType(editor, ELEMENT_TR) }, + }); + + /** + * Remove cells + */ + if ( + selectedCell && + trEntry && + tableEntry && + // Cannot delete the last cell + trEntry[0].children.length > 1 + ) { + const [tableNode, tablePath] = tableEntry; + + // calc paths to delete + const paths: Array = []; + affectedCells.forEach((cur) => { + const curCell = cur as TTableCellElement; + const { col: curColIndex, row: curRowIndex } = getCellIndices( + cellIndices!, + curCell + )!; + if ( + !squizeColSpanCells.includes(curCell) && + curColIndex >= deletingColIndex && + curColIndex <= endingColIndex + ) { + const cellPath = getCellPath( + editor, + tableEntry, + curRowIndex, + curColIndex + ); + + if (!paths[curRowIndex]) { + paths[curRowIndex] = []; + } + paths[curRowIndex].push(cellPath); + } + }); + + withoutNormalizing(editor, () => { + paths.forEach((cellPaths) => { + const pathToDelete = cellPaths[0]; + cellPaths.forEach(() => { + removeNodes(editor, { + at: pathToDelete, + }); + }); + }); + + const { colSizes } = tableNode; + if (colSizes) { + const newColSizes = [...colSizes]; + newColSizes.splice(deletingColIndex, 1); + + setNodes( + editor, + { colSizes: newColSizes }, + { at: tablePath } + ); + } + }); + } + } +}; diff --git a/packages/table/src/merge/deleteRow.ts b/packages/table/src/merge/deleteRow.ts new file mode 100644 index 0000000000..33155bd639 --- /dev/null +++ b/packages/table/src/merge/deleteRow.ts @@ -0,0 +1,189 @@ +import { + findNodePath, + getAboveNode, + getPluginOptions, + getPluginType, + insertElements, + PlateEditor, + removeNodes, + setNodes, + someNode, + Value, +} from '@udecode/plate-common'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { getTableColumnCount } from '../queries'; +import { getRowSpan } from '../queries/getRowSpan'; +import { + TablePlugin, + TTableCellElement, + TTableElement, + TTableRowElement, +} from '../types'; +import { getCellTypes } from '../utils'; +import { findCellByIndexes } from './findCellByIndexes'; +import { getCellIndices } from './getCellIndices'; + +export const deleteTableMergeRow = ( + editor: PlateEditor +) => { + if ( + someNode(editor, { + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + }) + ) { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const currentTableItem = getAboveNode(editor, { + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + }); + if (!currentTableItem) return; + const table = currentTableItem[0] as TTableElement; + + const selectedCellEntry = getAboveNode(editor, { + match: { type: getCellTypes(editor) }, + }); + if (!selectedCellEntry) return; + + const selectedCell = selectedCellEntry[0] as TTableCellElement; + const { row: deletingRowIndex } = getCellIndices( + cellIndices!, + selectedCell + )!; + const rowsDeleteNumber = getRowSpan(selectedCell); + const endingRowIndex = deletingRowIndex + rowsDeleteNumber - 1; + + const colNumber = getTableColumnCount(table); + const affectedCellsSet = new Set(); + // iterating by columns is important here to keep the order of affected cells + Array.from({ length: colNumber }, (_, i) => i).forEach((cI) => { + return Array.from({ length: rowsDeleteNumber }, (_, i) => i).forEach( + (rI) => { + const rowIndex = deletingRowIndex + rI; + const found = findCellByIndexes(editor, table, rowIndex, cI); + affectedCellsSet.add(found); + } + ); + }); + const affectedCells = Array.from(affectedCellsSet) as TTableCellElement[]; + + const { moveToNextRowCells, squizeRowSpanCells } = affectedCells.reduce<{ + squizeRowSpanCells: TTableCellElement[]; + moveToNextRowCells: TTableCellElement[]; + }>( + (acc, cur) => { + if (!cur) return acc; + + const currentCell = cur as TTableCellElement; + const { row: curRowIndex } = getCellIndices(cellIndices!, currentCell)!; + const curRowSpan = getRowSpan(currentCell); + + // if (!curRowIndex || !curRowSpan) return acc; + + if (curRowIndex < deletingRowIndex && curRowSpan > 1) { + acc.squizeRowSpanCells.push(currentCell); + } else if ( + curRowSpan > 1 && + curRowIndex + curRowSpan - 1 > endingRowIndex + ) { + acc.moveToNextRowCells.push(currentCell); + } + + return acc; + }, + { squizeRowSpanCells: [], moveToNextRowCells: [] } + ); + + const nextRowIndex = deletingRowIndex + rowsDeleteNumber; + const nextRow = table.children[nextRowIndex] as + | TTableCellElement + | undefined; + + if (nextRow) { + moveToNextRowCells.forEach((cur, index) => { + const curRowCell = cur as TTableCellElement; + const { col: curRowCellColIndex } = getCellIndices( + cellIndices!, + curRowCell + )!; + const curRowCellRowSpan = getRowSpan(curRowCell); + + // search for anchor cell where to place current cell + const startingCellIndex = nextRow.children.findIndex((curC) => { + const cell = curC as TTableCellElement; + const { col: curColIndex } = getCellIndices(cellIndices!, cell)!; + return curColIndex >= curRowCellColIndex; + }); + + const startingCell = nextRow.children[ + startingCellIndex + ] as TTableCellElement; + const { col: startingColIndex } = getCellIndices( + cellIndices!, + startingCell + )!; + + // consider already inserted cell by adding index each time to the col path + let incrementBy = index; + if (startingColIndex < curRowCellColIndex) { + // place current cell after starting cell, if placing cell col index is grather than col index of starting cell + incrementBy += 1; + } + + const startingCellPath = findNodePath(editor, startingCell)!; + const tablePath = startingCellPath.slice(0, -2); + const colPath = startingCellPath.at(-1)!; + + const nextRowStartCellPath = [ + ...tablePath, + nextRowIndex, + colPath + incrementBy, + ]; + + const rowsNumberAffected = endingRowIndex - curRowCellColIndex + 1; + + // TODO: consider make deep clone here + // making cell smaller and moving it to next row + const newCell = { + ...curRowCell, + rowSpan: curRowCellRowSpan - rowsNumberAffected, + }; + insertElements(editor, newCell, { at: nextRowStartCellPath }); + }); + } + + squizeRowSpanCells.forEach((cur) => { + const curRowCell = cur as TTableCellElement; + const { row: curRowCellRowIndex } = getCellIndices( + cellIndices!, + curRowCell + )!; + const curRowCellRowSpan = getRowSpan(curRowCell); + + const curCellPath = findNodePath(editor, curRowCell)!; + + const curCellEndingRowIndex = Math.min( + curRowCellRowIndex + curRowCellRowSpan - 1, + endingRowIndex + ); + const rowsNumberAffected = curCellEndingRowIndex - deletingRowIndex + 1; + + setNodes( + editor, + { ...curRowCell, rowSpan: curRowCellRowSpan - rowsNumberAffected }, + { at: curCellPath } + ); + }); + + const rowToDelete = table.children[deletingRowIndex] as TTableRowElement; + const rowPath = findNodePath(editor, rowToDelete); + Array.from({ length: rowsDeleteNumber }).forEach(() => { + removeNodes(editor, { + at: rowPath, + }); + }); + } +}; diff --git a/packages/table/src/merge/findCellByIndexes.ts b/packages/table/src/merge/findCellByIndexes.ts new file mode 100644 index 0000000000..aeb0f352e5 --- /dev/null +++ b/packages/table/src/merge/findCellByIndexes.ts @@ -0,0 +1,50 @@ +import { getPluginOptions, PlateEditor, Value } from '@udecode/plate-common'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { getCellIndices } from '../merge/getCellIndices'; +import { TablePlugin, TTableCellElement, TTableElement } from '../types'; +import { computeCellIndices } from './computeCellIndices'; +import { getCellIndicesWithSpans } from './getCellIndicesWithSpans'; + +export const findCellByIndexes = ( + editor: PlateEditor, + table: TTableElement, + searchRowIndex: number, + searchColIndex: number +) => { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const allCells = table.children.flatMap( + (current) => current.children + ) as TTableCellElement[]; + + const foundCell = allCells.find((cell) => { + const cellElement = cell as TTableCellElement; + + const indices = + getCellIndices(cellIndices!, cellElement) || + computeCellIndices(editor, table, cellElement)!; + + const { col: _startColIndex, row: _startRowIndex } = indices; + const { row: _endRowIndex, col: _endColIndex } = getCellIndicesWithSpans( + indices, + cellElement + ); + + if ( + searchColIndex >= _startColIndex && + searchColIndex <= _endColIndex && + searchRowIndex >= _startRowIndex && + searchRowIndex <= _endRowIndex + ) { + return true; + } + + return false; + }); + + return foundCell; +}; diff --git a/packages/table/src/merge/getCellIndices.ts b/packages/table/src/merge/getCellIndices.ts new file mode 100644 index 0000000000..f690cf2cbe --- /dev/null +++ b/packages/table/src/merge/getCellIndices.ts @@ -0,0 +1,9 @@ +import { TableStoreCellAttributes, TTableCellElement } from '../types'; + +export const getCellIndices = ( + cellIndices: TableStoreCellAttributes, + startCell: TTableCellElement +) => { + // optional typing needs for tests + return cellIndices?.get(startCell); +}; diff --git a/packages/table/src/merge/getCellIndicesWithSpans.ts b/packages/table/src/merge/getCellIndicesWithSpans.ts new file mode 100644 index 0000000000..c3175eaf2b --- /dev/null +++ b/packages/table/src/merge/getCellIndicesWithSpans.ts @@ -0,0 +1,13 @@ +import { getColSpan } from '../queries/getColSpan'; +import { getRowSpan } from '../queries/getRowSpan'; +import { TTableCellElement } from '../types'; + +export const getCellIndicesWithSpans = ( + { col, row }: { col: number; row: number }, + endCell: TTableCellElement +) => { + return { + row: row + getRowSpan(endCell) - 1, + col: col + getColSpan(endCell) - 1, + }; +}; diff --git a/packages/table/src/merge/getCellPath.ts b/packages/table/src/merge/getCellPath.ts new file mode 100644 index 0000000000..63d07325b3 --- /dev/null +++ b/packages/table/src/merge/getCellPath.ts @@ -0,0 +1,36 @@ +import { + getPluginOptions, + PlateEditor, + TNodeEntry, + Value, +} from '@udecode/plate-common'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { + TablePlugin, + TTableCellElement, + TTableElement, + TTableRowElement, +} from '../types'; +import { getCellIndices } from './getCellIndices'; + +export const getCellPath = ( + editor: PlateEditor, + tableEntry: TNodeEntry, + curRowIndex: number, + curColIndex: number +) => { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + const [tableNode, tablePath] = tableEntry; + + const rowElem = tableNode.children[curRowIndex] as TTableRowElement; + const foundColIndex = rowElem.children.findIndex((c) => { + const cE = c as TTableCellElement; + const { col: colIndex } = getCellIndices(cellIndices!, cE)!; + return colIndex === curColIndex; + }); + return tablePath.concat([curRowIndex, foundColIndex]); +}; diff --git a/packages/table/src/merge/getTableGridByRange.ts b/packages/table/src/merge/getTableGridByRange.ts new file mode 100644 index 0000000000..f832fb170c --- /dev/null +++ b/packages/table/src/merge/getTableGridByRange.ts @@ -0,0 +1,164 @@ +import { + findNode, + findNodePath, + getPluginOptions, + getPluginType, + PlateEditor, + TElement, + TElementEntry, + Value, +} from '@udecode/plate-common'; +import { Range } from 'slate'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { computeCellIndices } from '../merge/computeCellIndices'; +import { findCellByIndexes } from '../merge/findCellByIndexes'; +import { getCellIndices } from '../merge/getCellIndices'; +import { getCellIndicesWithSpans } from '../merge/getCellIndicesWithSpans'; +import { + TablePlugin, + TTableCellElement, + TTableElement, + TTableRowElement, +} from '../types'; +import { getCellTypes } from '../utils'; +import { getEmptyTableNode } from '../utils/getEmptyTableNode'; + +export type FormatType = 'table' | 'cell' | 'all'; + +export interface TableGridEntries { + tableEntries: TElementEntry[]; + cellEntries: TElementEntry[]; +} + +export type GetTableGridReturnType = T extends 'all' + ? TableGridEntries + : TElementEntry[]; + +export interface GetTableGridByRangeOptions { + at: Range; + + /** + * Format of the output: + * - table element + * - array of cells + */ + format?: T; +} + +/** + * Get sub table between 2 cell paths. + */ +export const getTableMergeGridByRange = ( + editor: PlateEditor, + { at, format }: GetTableGridByRangeOptions +): GetTableGridReturnType => { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const startCellEntry = findNode(editor, { + at: (at as any).anchor.path, + match: { type: getCellTypes(editor) }, + })!; // TODO: improve typing + const endCellEntry = findNode(editor, { + at: (at as any).focus.path, + match: { type: getCellTypes(editor) }, + })!; + + const startCell = startCellEntry[0] as TTableCellElement; + const endCell = endCellEntry[0] as TTableCellElement; + + const startCellPath = (at as any).anchor.path; + const tablePath = startCellPath.slice(0, -2); + + const tableEntry = findNode(editor, { + at: tablePath, + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + })!; // TODO: improve typing + const realTable = tableEntry[0] as TTableElement; + + const { col: _startColIndex, row: _startRowIndex } = + getCellIndices(cellIndices!, startCell) || + computeCellIndices(editor, realTable, startCell)!; + + const { row: _endRowIndex, col: _endColIndex } = getCellIndicesWithSpans( + getCellIndices(cellIndices!, endCell) || + computeCellIndices(editor, realTable, endCell)!, + endCell + ); + + const startRowIndex = Math.min(_startRowIndex, _endRowIndex); + const endRowIndex = Math.max(_startRowIndex, _endRowIndex); + const startColIndex = Math.min(_startColIndex, _endColIndex); + const endColIndex = Math.max(_startColIndex, _endColIndex); + + const relativeRowIndex = endRowIndex - startRowIndex; + const relativeColIndex = endColIndex - startColIndex; + + const table: TTableElement = getEmptyTableNode(editor, { + rowCount: relativeRowIndex + 1, + colCount: relativeColIndex + 1, + newCellChildren: [], + }); + + const cellEntries: TElementEntry[] = []; + const cellsSet = new WeakSet(); + + let rowIndex = startRowIndex; + let colIndex = startColIndex; + while (true) { + const cell = findCellByIndexes(editor, realTable, rowIndex, colIndex); + if (!cell) { + break; + } + + if (!cellsSet.has(cell)) { + cellsSet.add(cell); + + const rows = table.children[rowIndex - startRowIndex] + .children as TElement[]; + rows[colIndex - startColIndex] = cell; + + const cellPath = findNodePath(editor, cell)!; + cellEntries.push([cell, cellPath]); + } + + if (colIndex + 1 <= endColIndex) { + colIndex = colIndex + 1; + } else if (rowIndex + 1 <= endRowIndex) { + colIndex = startColIndex; + rowIndex = rowIndex + 1; + } else { + break; + } + } + + const formatType = (format as string) || 'table'; + + if (formatType === 'cell') { + return cellEntries as GetTableGridReturnType; + } + + // clear redundant cells + table.children?.forEach((rowEl) => { + const rowElement = rowEl as TTableRowElement; + + const filteredChildren = rowElement.children?.filter((cellEl) => { + const cellElement = cellEl as TTableCellElement; + return !!cellElement?.children.length; + }); + + rowElement.children = filteredChildren; + }); + + if (formatType === 'table') { + return [[table, tablePath]] as GetTableGridReturnType; + } + + return { + tableEntries: [[table, tablePath]], + cellEntries, + } as GetTableGridReturnType; +}; diff --git a/packages/table/src/merge/index.ts b/packages/table/src/merge/index.ts new file mode 100644 index 0000000000..099e84d8fd --- /dev/null +++ b/packages/table/src/merge/index.ts @@ -0,0 +1,7 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './mergeTableCells'; +export * from './unmergeTableCells'; +export * from './useTableMergeState'; diff --git a/packages/table/src/merge/insertTableColumn.ts b/packages/table/src/merge/insertTableColumn.ts new file mode 100644 index 0000000000..aa4aad98bc --- /dev/null +++ b/packages/table/src/merge/insertTableColumn.ts @@ -0,0 +1,202 @@ +import { + findNode, + getBlockAbove, + getParentNode, + getPluginOptions, + getPluginType, + insertElements, + PlateEditor, + setNodes, + Value, + withoutNormalizing, +} from '@udecode/plate-common'; +import { Path } from 'slate'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { getColSpan } from '../queries/getColSpan'; +import { getRowSpan } from '../queries/getRowSpan'; +import { + TablePlugin, + TTableCellElement, + TTableElement, + TTableRowElement, +} from '../types'; +import { getCellTypes } from '../utils'; +import { createEmptyCell } from './createEmptyCell'; +import { findCellByIndexes } from './findCellByIndexes'; +import { getCellIndices } from './getCellIndices'; +import { getCellPath } from './getCellPath'; + +export const insertTableMergeColumn = ( + editor: PlateEditor, + { + disableSelect, + fromCell, + at, + header, + }: { + header?: boolean; + + /** + * Path of the cell to insert the column from. + */ + fromCell?: Path; + + /** + * Exact path of the cell to insert the column at. + * Will overrule `fromCell`. + */ + at?: Path; + + /** + * Disable selection after insertion. + */ + disableSelect?: boolean; + } = {} +) => { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const cellEntry = fromCell + ? findNode(editor, { + at: fromCell, + match: { type: getCellTypes(editor) }, + }) + : getBlockAbove(editor, { + match: { type: getCellTypes(editor) }, + }); + if (!cellEntry) return; + + const [, cellPath] = cellEntry; + const cell = cellEntry[0] as TTableCellElement; + + const tableEntry = getBlockAbove(editor, { + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + at: cellPath, + }); + if (!tableEntry) return; + + const { newCellChildren, initialTableWidth, minColumnWidth } = + getPluginOptions(editor, ELEMENT_TABLE); + const [tableNode, tablePath] = tableEntry; + + const { col: cellColIndex } = getCellIndices(cellIndices!, cell)!; + const cellColSpan = getColSpan(cell); + + let nextColIndex: number; + let checkingColIndex: number; + if (Path.isPath(at)) { + nextColIndex = cellColIndex; + checkingColIndex = cellColIndex - 1; + } else { + nextColIndex = cellColIndex + cellColSpan; + checkingColIndex = cellColIndex + cellColSpan - 1; + } + + const rowNumber = tableNode.children.length; + const firstCol = nextColIndex <= 0; + + // const colCount = getTableColumnCount(tableNode); + // const lastRow = nextColIndex === colCount; + + let placementCorrection = 1; + if (firstCol) { + checkingColIndex = 0; + placementCorrection = 0; + } + + const affectedCellsSet = new Set(); + Array.from({ length: rowNumber }, (_, i) => i).forEach((rI) => { + const found = findCellByIndexes(editor, tableNode, rI, checkingColIndex); + if (found) { + affectedCellsSet.add(found); + } + }); + const affectedCells = Array.from(affectedCellsSet) as TTableCellElement[]; + + affectedCells.forEach((cur) => { + const curCell = cur as TTableCellElement; + const { row: curRowIndex, col: curColIndex } = getCellIndices( + cellIndices!, + curCell + )!; + const curRowSpan = getRowSpan(curCell); + const curColSpan = getColSpan(curCell); + + const currentCellPath = getCellPath( + editor, + tableEntry, + curRowIndex, + curColIndex + ); + + const endCurI = curColIndex + curColSpan - 1; + if (endCurI >= nextColIndex && !firstCol) { + // make wider + setNodes( + editor, + { ...curCell, colSpan: curColSpan + 1 }, + { at: currentCellPath } + ); + } else { + // add new + const curRowPath = currentCellPath.slice(0, -1); + const curColPath = currentCellPath.at(-1)!; + const placementPath = [...curRowPath, curColPath + placementCorrection]; + + const row = getParentNode(editor, currentCellPath)!; + const rowElement = row[0] as TTableRowElement; + const emptyCell = { + ...createEmptyCell(editor, rowElement, newCellChildren, header), + rowSpan: curRowSpan, + colSpan: 1, + }; + insertElements(editor, emptyCell, { + at: placementPath, + // select: !disableSelect && curRowIndex === currentRowIndex, + }); + } + }); + + withoutNormalizing(editor, () => { + const { colSizes } = tableNode; + + if (colSizes) { + let newColSizes = [ + ...colSizes.slice(0, nextColIndex), + 0, + ...colSizes.slice(nextColIndex), + ]; + + if (initialTableWidth) { + newColSizes[nextColIndex] = + colSizes[nextColIndex] ?? + colSizes[nextColIndex - 1] ?? + initialTableWidth / colSizes.length; + + const oldTotal = colSizes.reduce((a, b) => a + b, 0); + const newTotal = newColSizes.reduce((a, b) => a + b, 0); + const maxTotal = Math.max(oldTotal, initialTableWidth); + + if (newTotal > maxTotal) { + const factor = maxTotal / newTotal; + newColSizes = newColSizes.map((size) => + Math.max(minColumnWidth ?? 0, Math.floor(size * factor)) + ); + } + } + + setNodes( + editor, + { + colSizes: newColSizes, + }, + { + at: tablePath, + } + ); + } + }); +}; diff --git a/packages/table/src/merge/insertTableRow.ts b/packages/table/src/merge/insertTableRow.ts new file mode 100644 index 0000000000..6731e159f6 --- /dev/null +++ b/packages/table/src/merge/insertTableRow.ts @@ -0,0 +1,177 @@ +import { + findNode, + getBlockAbove, + getParentNode, + getPluginOptions, + getPluginType, + insertElements, + PlateEditor, + setNodes, + Value, + withoutNormalizing, +} from '@udecode/plate-common'; +import { Path } from 'slate'; + +import { ELEMENT_TABLE, ELEMENT_TR } from '../createTablePlugin'; +import { getTableColumnCount } from '../queries'; +import { getColSpan } from '../queries/getColSpan'; +import { getRowSpan } from '../queries/getRowSpan'; +import { + TablePlugin, + TTableCellElement, + TTableElement, + TTableRowElement, +} from '../types'; +import { getCellTypes } from '../utils'; +import { createEmptyCell } from './createEmptyCell'; +import { findCellByIndexes } from './findCellByIndexes'; +import { getCellIndices } from './getCellIndices'; +import { getCellPath } from './getCellPath'; + +export const insertTableMergeRow = ( + editor: PlateEditor, + { + header, + fromRow, + at, + disableSelect, + }: { + header?: boolean; + fromRow?: Path; + /** + * Exact path of the row to insert the column at. + * Will overrule `fromRow`. + */ + at?: Path; + disableSelect?: boolean; + } = {} +) => { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const trEntry = fromRow + ? findNode(editor, { + at: fromRow, + match: { type: getPluginType(editor, ELEMENT_TR) }, + }) + : getBlockAbove(editor, { + match: { type: getPluginType(editor, ELEMENT_TR) }, + }); + if (!trEntry) return; + + const [, trPath] = trEntry; + + const tableEntry = getBlockAbove(editor, { + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + at: trPath, + }); + if (!tableEntry) return; + const tableNode = tableEntry[0] as TTableElement; + + const { newCellChildren } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + const cellEntry = findNode(editor, { + at: fromRow, + match: { type: getCellTypes(editor) }, + }); + if (!cellEntry) return; + const [cellNode, cellPath] = cellEntry; + const cellElement = cellNode as TTableCellElement; + const cellRowSpan = getRowSpan(cellElement); + const { row: cellRowIndex } = getCellIndices(cellIndices!, cellElement)!; + + const rowPath = cellPath.at(-2)!; + const tablePath = cellPath.slice(0, -2)!; + + let nextRowIndex: number; + let checkingRowIndex: number; + let nextRowPath: number[]; + if (Path.isPath(at)) { + nextRowIndex = at.at(-1)!; + checkingRowIndex = cellRowIndex - 1; + nextRowPath = at; + } else { + nextRowIndex = cellRowIndex + cellRowSpan; + checkingRowIndex = cellRowIndex + cellRowSpan - 1; + nextRowPath = [...tablePath, rowPath + cellRowSpan]; + } + + const firstRow = nextRowIndex === 0; + if (firstRow) { + checkingRowIndex = 0; + } + + const colCount = getTableColumnCount(tableNode); + const affectedCellsSet = new Set(); + Array.from({ length: colCount }, (_, i) => i).forEach((cI) => { + const found = findCellByIndexes(editor, tableNode, checkingRowIndex, cI); + if (found) { + affectedCellsSet.add(found); + } + }); + const affectedCells = Array.from(affectedCellsSet) as TTableCellElement[]; + + const newRowChildren: TTableCellElement[] = []; + affectedCells.forEach((cur) => { + if (!cur) return; + + const curCell = cur as TTableCellElement; + const { row: curRowIndex, col: curColIndex } = getCellIndices( + cellIndices!, + curCell + )!; + + const curRowSpan = getRowSpan(curCell); + const curColSpan = getColSpan(curCell); + const currentCellPath = getCellPath( + editor, + tableEntry, + curRowIndex, + curColIndex + ); + + const endCurI = curRowIndex + curRowSpan - 1; + if (endCurI >= nextRowIndex && !firstRow) { + // make higher + setNodes( + editor, + { ...curCell, rowSpan: curRowSpan + 1 }, + { at: currentCellPath } + ); + } else { + // add new + const row = getParentNode(editor, currentCellPath)!; + const rowElement = row[0] as TTableRowElement; + const emptyCell = createEmptyCell( + editor, + rowElement, + newCellChildren, + header + ) as TTableCellElement; + + newRowChildren.push({ + ...emptyCell, + colSpan: curColSpan, + rowSpan: 1, + }); + } + }); + + withoutNormalizing(editor, () => { + insertElements( + editor, + { + type: getPluginType(editor, ELEMENT_TR), + children: newRowChildren, + }, + { + at: nextRowPath, + // select: !disableSelect + } + ); + }); +}; diff --git a/packages/table/src/merge/isTableRectangular.ts b/packages/table/src/merge/isTableRectangular.ts new file mode 100644 index 0000000000..ff3b216712 --- /dev/null +++ b/packages/table/src/merge/isTableRectangular.ts @@ -0,0 +1,28 @@ +import { TTableCellElement, TTableElement, TTableRowElement } from '../types'; + +const allEqual = (arr: number[]) => arr.every((val) => val === arr[0]); + +/** + * Checks if the given table is rectangular, meaning all rows have the same effective number of cells, considering colspan and rowspan. + */ +export const isTableRectangular = (table?: TTableElement) => { + const arr: number[] = []; + table?.children?.forEach((row, rI) => { + const rowEl = row as TTableRowElement; + + rowEl.children?.forEach((cell) => { + const cellElem = cell as TTableCellElement; + + Array.from({ + length: cellElem?.rowSpan || 1, + } as ArrayLike).forEach((_, i) => { + if (!arr[rI + i]) { + arr[rI + i] = 0; + } + arr[rI + i] += cellElem?.colSpan || 1; + }); + }); + }); + + return allEqual(arr); +}; diff --git a/packages/table/src/merge/mergeTableCells.ts b/packages/table/src/merge/mergeTableCells.ts new file mode 100644 index 0000000000..18b88d0ab5 --- /dev/null +++ b/packages/table/src/merge/mergeTableCells.ts @@ -0,0 +1,115 @@ +import { + getBlockAbove, + getPluginOptions, + getPluginType, + insertElements, + PlateEditor, + removeNodes, + Value, + withoutNormalizing, +} from '@udecode/plate-common'; +import { cloneDeep } from 'lodash'; + +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { getTableGridAbove } from '../queries'; +import { getColSpan } from '../queries/getColSpan'; +import { getRowSpan } from '../queries/getRowSpan'; +import { TablePlugin, TTableCellElement, TTableElement } from '../types'; +import { getEmptyCellNode } from '../utils'; +import { computeCellIndices } from './computeCellIndices'; +import { getCellIndices } from './getCellIndices'; + +/** + * Merges multiple selected cells into one. + */ +export const mergeTableCells = ( + editor: PlateEditor +) => { + withoutNormalizing(editor, () => { + const { _cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + const tableEntry = getBlockAbove(editor, { + at: editor.selection?.anchor.path, + match: { type: getPluginType(editor, ELEMENT_TABLE) }, + })!; + + const cellEntries = getTableGridAbove(editor, { format: 'cell' }); + + // calculate the colSpan which is the number of horizontal cells that a cell should span. + let colSpan = 0; + for (const entry of cellEntries) { + const [data, path] = entry; + + // count only those cells that are in the first selected row. + if (path[1] === cellEntries[0][1][1]) { + const cellColSpan = getColSpan(data as TTableCellElement); + colSpan += cellColSpan; + } + } + + // calculate the rowSpan which is the number of vertical cells that a cell should span. + let rowSpan = 0; + const { col } = getCellIndices( + _cellIndices!, + cellEntries[0][0] as TTableCellElement + )!; + cellEntries.forEach((cE) => { + const cell = cE[0] as TTableCellElement; + const { col: curCol } = + _cellIndices?.get(cell) || + computeCellIndices(editor, tableEntry[0] as TTableElement, cell)!; + if (col === curCol) { + rowSpan += getRowSpan(cell); + } + }); + + // This will store the content of all cells we are merging + const contents = []; + for (const cellEntry of cellEntries) { + const [el] = cellEntry; + contents.push(...cloneDeep(el.children)); + } + + // Create a hash map where keys are col paths, + // and values are an array of all paths with that column + const cols: { [key: string]: number[][] } = {}; + + // A boolean to keep track if we have a header cell among the cells we are merging + let hasHeaderCell = false; + + cellEntries.forEach(([entry, path]) => { + if (!hasHeaderCell && entry.type === 'table_header_cell') { + hasHeaderCell = true; + } + if (cols[path[1]]) { + cols[path[1]].push(path); + } else { + cols[path[1]] = [path]; + } + }); + + // removes multiple cells with on same path. + // once cell removed, next cell in the row will settle down on that path + Object.values(cols).forEach((paths) => { + paths?.forEach(() => { + removeNodes(editor, { at: paths[0] }); + }); + }); + + // Create a new cell to replace the merged cells, with + // calculated colSpan and rowSpan attributes and combined content + const mergedCell = { + ...getEmptyCellNode(editor, { + header: cellEntries[0][0].type === 'th', + newCellChildren: contents, + }), + colSpan, + rowSpan, + }; + + // insert the new merged cell in place of the first cell in the selection + insertElements(editor, mergedCell, { at: cellEntries[0][1] }); + }); +}; diff --git a/packages/table/src/merge/unmergeTableCells.ts b/packages/table/src/merge/unmergeTableCells.ts new file mode 100644 index 0000000000..400e34f21d --- /dev/null +++ b/packages/table/src/merge/unmergeTableCells.ts @@ -0,0 +1,109 @@ +import { + findNode, + getPluginOptions, + getPluginType, + insertElements, + PlateEditor, + removeNodes, + TDescendant, + Value, + withoutNormalizing, +} from '@udecode/plate-common'; + +import { ELEMENT_TABLE, ELEMENT_TR } from '../createTablePlugin'; +import { getTableGridAbove } from '../queries'; +import { getColSpan } from '../queries/getColSpan'; +import { TablePlugin, TTableCellElement, TTableRowElement } from '../types'; +import { getEmptyCellNode } from '../utils'; +import { getCellIndices } from './getCellIndices'; + +export const unmergeTableCells = ( + editor: PlateEditor +) => { + withoutNormalizing(editor, () => { + const { _cellIndices: cellIndices } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + + const cellEntries = getTableGridAbove(editor, { format: 'cell' }); + const [[cellElem, path]] = cellEntries; + + // creating new object per iteration is essential here + const createEmptyCell = (children?: TDescendant[]) => { + return { + ...getEmptyCellNode(editor, { + header: cellElem.type === 'th', + newCellChildren: children, + }), + colSpan: 1, + rowSpan: 1, + }; + }; + + const tablePath = path.slice(0, -2); + + const cellPath = path.slice(-2); + const [rowPath, colPath] = cellPath; + const colSpan = cellElem.colSpan as number; + const rowSpan = cellElem.rowSpan as number; + + // Generate an array of column paths from the colspan + const colPaths: number[] = []; + for (let i = 0; i < colSpan; i++) { + colPaths.push(colPath + i); + } + + // Remove the original merged cell from the editor + removeNodes(editor, { at: path }); + + const { col } = getCellIndices( + cellIndices!, + cellElem as TTableCellElement + )!; + + const getColPathForRow = (row: number) => { + let newColPath = 0; + + const rowEntry = findNode(editor, { + at: [...tablePath, row], + match: { type: getPluginType(editor, ELEMENT_TR) }, + })!; // TODO: improve typing + const rowEl = rowEntry[0] as TTableRowElement; + + for (const item of rowEl.children) { + const { col: c } = getCellIndices( + cellIndices!, + item as TTableCellElement + )!; + if (c === col - 1) { + newColPath = rowEl.children.indexOf(item) + 1; + break; + } + if (col + getColSpan(cellElem as TTableCellElement) === c - 1) { + newColPath = rowEl.children.indexOf(item); + break; + } + } + + return newColPath; + }; + + // Generate an array of cell paths from the row and col spans and then insert empty cells at those paths + for (let i = 0; i < rowSpan; i++) { + const currentRowPath = rowPath + i; + const pathForNextRows = getColPathForRow(currentRowPath); + for (let j = 0; j < colPaths.length; j++) { + const currentColPath = i === 0 ? colPaths[j] : pathForNextRows; + + const pathForNewCell = [...tablePath, currentRowPath, currentColPath]; + const cellToInsert = + i === 0 && j === 0 + ? createEmptyCell(cellElem.children) + : createEmptyCell(); + + insertElements(editor, cellToInsert, { at: pathForNewCell }); + } + } + }); +}; diff --git a/packages/table/src/merge/useTableMergeState.ts b/packages/table/src/merge/useTableMergeState.ts new file mode 100644 index 0000000000..5c8308e296 --- /dev/null +++ b/packages/table/src/merge/useTableMergeState.ts @@ -0,0 +1,45 @@ +import { useMemo } from 'react'; +import { isCollapsed, isExpanded, useEditorState } from '@udecode/plate-common'; +import { useReadOnly, useSelected } from 'slate-react'; + +import { getTableGridAbove } from '../queries'; +import { useTableStore } from '../stores'; +import { isTableRectangular } from './isTableRectangular'; + +export const useTableMergeState = () => { + const editor = useEditorState(); + + const readOnly = useReadOnly(); + const selected = useSelected(); + + const collapsed = !readOnly && selected && isCollapsed(editor.selection); + const selectedTables = useTableStore().get.selectedTable(); + const selectedTable = selectedTables?.[0]; + + const selectedCellEntries = useMemo( + () => + getTableGridAbove(editor, { + format: 'cell', + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [editor.selection] + ); + + const canMerge = useMemo(() => { + return ( + !readOnly && + selected && + isExpanded(editor.selection) && + isTableRectangular(selectedTable) + ); + }, [editor.selection, selectedTable, readOnly, selected]); + + const canUnmerge = + collapsed && + selectedCellEntries && + selectedCellEntries.length === 1 && + ((selectedCellEntries[0][0] as any)?.colSpan > 1 || + (selectedCellEntries[0][0] as any)?.rowSpan > 1); + + return { canMerge, canUnmerge }; +}; diff --git a/packages/table/src/queries/getColSpan.ts b/packages/table/src/queries/getColSpan.ts new file mode 100644 index 0000000000..2df1070a04 --- /dev/null +++ b/packages/table/src/queries/getColSpan.ts @@ -0,0 +1,10 @@ +import { TTableCellElement } from '../types'; + +/** + * Returns the colspan attribute of the table cell element. + * @default 1 if undefined. + */ +export const getColSpan = (cellElem: TTableCellElement) => { + const attrColSpan = Number(cellElem.attributes?.colspan); + return cellElem.colSpan || attrColSpan || 1; +}; diff --git a/packages/table/src/queries/getRowSpan.ts b/packages/table/src/queries/getRowSpan.ts new file mode 100644 index 0000000000..6e83104836 --- /dev/null +++ b/packages/table/src/queries/getRowSpan.ts @@ -0,0 +1,10 @@ +import { TTableCellElement } from '../types'; + +/** + * Returns the rowspan attribute of the table cell element. + * @default 1 if undefined + */ +export const getRowSpan = (cellElem: TTableCellElement) => { + const attrRowSpan = Number(cellElem.attributes?.rowspan); + return cellElem.rowSpan || attrRowSpan || 1; +}; diff --git a/packages/table/src/queries/getTableGridAbove.ts b/packages/table/src/queries/getTableGridAbove.ts index c916df2e26..fe677ef16c 100644 --- a/packages/table/src/queries/getTableGridAbove.ts +++ b/packages/table/src/queries/getTableGridAbove.ts @@ -32,7 +32,6 @@ export const getTableGridAbove = ( }, ...options, }); - if (edges) { const [start, end] = edges; diff --git a/packages/table/src/queries/getTableGridByRange.ts b/packages/table/src/queries/getTableGridByRange.ts index 84caada168..ce65d711f1 100644 --- a/packages/table/src/queries/getTableGridByRange.ts +++ b/packages/table/src/queries/getTableGridByRange.ts @@ -1,5 +1,6 @@ import { getNode, + getPluginOptions, PlateEditor, TElement, TElementEntry, @@ -7,7 +8,9 @@ import { } from '@udecode/plate-common'; import { Range } from 'slate'; -import { TTableElement } from '../types'; +import { ELEMENT_TABLE } from '../createTablePlugin'; +import { getTableMergeGridByRange } from '../merge/getTableGridByRange'; +import { TablePlugin, TTableElement } from '../types'; import { getEmptyTableNode } from '../utils/getEmptyTableNode'; export interface GetTableGridByRangeOptions { @@ -28,6 +31,14 @@ export const getTableGridByRange = ( editor: PlateEditor, { at, format = 'table' }: GetTableGridByRangeOptions ): TElementEntry[] => { + const { enableMerging } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + if (enableMerging) { + return getTableMergeGridByRange(editor, { at, format }); + } + const startCellPath = at.anchor.path; const endCellPath = at.focus.path; diff --git a/packages/table/src/stores/tableStore.ts b/packages/table/src/stores/tableStore.ts index 91842d9a93..c89ef2d363 100644 --- a/packages/table/src/stores/tableStore.ts +++ b/packages/table/src/stores/tableStore.ts @@ -12,6 +12,7 @@ export const { tableStore, useTableStore } = createAtomStore( marginLeftOverride: null as number | null, hoveredColIndex: null as number | null, selectedCells: null as TElement[] | null, + selectedTable: null as TElement[] | null, }, { name: 'table' as const, scope: ELEMENT_TABLE } ); diff --git a/packages/table/src/transforms/deleteColumn.ts b/packages/table/src/transforms/deleteColumn.ts index fcd905f7c6..b7262d2c3d 100644 --- a/packages/table/src/transforms/deleteColumn.ts +++ b/packages/table/src/transforms/deleteColumn.ts @@ -1,5 +1,6 @@ import { getAboveNode, + getPluginOptions, getPluginType, PlateEditor, removeNodes, @@ -16,9 +17,18 @@ import { ELEMENT_TH, ELEMENT_TR, } from '../createTablePlugin'; -import { TTableElement } from '../types'; +import { deleteTableMergeColumn } from '../merge/deleteColumn'; +import { TablePlugin, TTableElement } from '../types'; export const deleteColumn = (editor: PlateEditor) => { + const { enableMerging } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + if (enableMerging) { + return deleteTableMergeColumn(editor); + } + if ( someNode(editor, { match: { type: getPluginType(editor, ELEMENT_TABLE) }, diff --git a/packages/table/src/transforms/deleteRow.ts b/packages/table/src/transforms/deleteRow.ts index 703afed4ea..6bc239bf24 100644 --- a/packages/table/src/transforms/deleteRow.ts +++ b/packages/table/src/transforms/deleteRow.ts @@ -1,5 +1,6 @@ import { getAboveNode, + getPluginOptions, getPluginType, PlateEditor, removeNodes, @@ -8,9 +9,18 @@ import { } from '@udecode/plate-common'; import { ELEMENT_TABLE, ELEMENT_TR } from '../createTablePlugin'; -import { TTableElement } from '../types'; +import { deleteTableMergeRow } from '../merge/deleteRow'; +import { TablePlugin, TTableElement } from '../types'; export const deleteRow = (editor: PlateEditor) => { + const { enableMerging } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + if (enableMerging) { + return deleteTableMergeRow(editor); + } + if ( someNode(editor, { match: { type: getPluginType(editor, ELEMENT_TABLE) }, diff --git a/packages/table/src/transforms/insertTableColumn.ts b/packages/table/src/transforms/insertTableColumn.ts index fea3f48338..80233a52da 100644 --- a/packages/table/src/transforms/insertTableColumn.ts +++ b/packages/table/src/transforms/insertTableColumn.ts @@ -13,18 +13,14 @@ import { import { Path } from 'slate'; import { ELEMENT_TABLE, ELEMENT_TH } from '../createTablePlugin'; +import { insertTableMergeColumn } from '../merge/insertTableColumn'; import { TablePlugin, TTableElement } from '../types'; import { getEmptyCellNode } from '../utils/getEmptyCellNode'; import { getCellTypes } from '../utils/index'; export const insertTableColumn = ( editor: PlateEditor, - { - disableSelect, - fromCell, - at, - header, - }: { + options: { header?: boolean; /** @@ -44,6 +40,16 @@ export const insertTableColumn = ( disableSelect?: boolean; } = {} ) => { + const { enableMerging } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + if (enableMerging) { + return insertTableMergeColumn(editor, options); + } + + const { disableSelect, fromCell, at, header } = options; + const cellEntry = fromCell ? findNode(editor, { at: fromCell, diff --git a/packages/table/src/transforms/insertTableRow.spec.tsx b/packages/table/src/transforms/insertTableRow.spec.tsx index b1c6161ede..7f036c86ea 100644 --- a/packages/table/src/transforms/insertTableRow.spec.tsx +++ b/packages/table/src/transforms/insertTableRow.spec.tsx @@ -76,7 +76,9 @@ describe('insertTableRow', () => { editor: input, plugins: [ createTablePlugin({ - options: { newCellChildren: [{ text: '' }] }, + options: { + newCellChildren: [{ text: '' }], + }, }), ], }); diff --git a/packages/table/src/transforms/insertTableRow.ts b/packages/table/src/transforms/insertTableRow.ts index 5204bd182b..575543feb8 100644 --- a/packages/table/src/transforms/insertTableRow.ts +++ b/packages/table/src/transforms/insertTableRow.ts @@ -13,17 +13,13 @@ import { import { Path } from 'slate'; import { ELEMENT_TABLE, ELEMENT_TH, ELEMENT_TR } from '../createTablePlugin'; +import { insertTableMergeRow } from '../merge/insertTableRow'; import { TablePlugin } from '../types'; import { getCellTypes, getEmptyCellNode } from '../utils/index'; export const insertTableRow = ( editor: PlateEditor, - { - header, - fromRow, - at, - disableSelect, - }: { + options: { header?: boolean; fromRow?: Path; /** @@ -34,6 +30,16 @@ export const insertTableRow = ( disableSelect?: boolean; } = {} ) => { + const { enableMerging } = getPluginOptions( + editor, + ELEMENT_TABLE + ); + if (enableMerging) { + return insertTableMergeRow(editor, options); + } + + const { header, fromRow, at, disableSelect } = options; + const trEntry = fromRow ? findNode(editor, { at: fromRow, diff --git a/packages/table/src/types.ts b/packages/table/src/types.ts index 36117f5c06..d8c3627daa 100644 --- a/packages/table/src/types.ts +++ b/packages/table/src/types.ts @@ -58,8 +58,24 @@ export interface TablePlugin { * @default 48 */ minColumnWidth?: number; + + /** + * Enable cells merging functionality. + * @default false + */ + enableMerging?: boolean; + + /** + * For internal use. Keeps track of cell indices. Used only when enableMerging is true. + */ + _cellIndices?: TableStoreCellAttributes; } +export type TableStoreCellAttributes = WeakMap< + TTableCellElement, + { row: number; col: number } +>; + export interface BorderStyle { // https://docx.js.org/api/enums/BorderStyle.html style?: string; @@ -78,6 +94,7 @@ export interface TTableRowElement extends TElement { export interface TTableCellElement extends TElement { colSpan?: number; + rowSpan?: number; size?: number; background?: string; borders?: { @@ -94,6 +111,10 @@ export interface TTableCellElement extends TElement { */ right?: BorderStyle; }; + attributes: { + colspan?: string; + rowspan?: string; + }; } export type BorderDirection = 'top' | 'left' | 'bottom' | 'right'; diff --git a/packages/table/src/utils/getEmptyCellNode.ts b/packages/table/src/utils/getEmptyCellNode.ts index d9b87b5b5f..98c84984bb 100644 --- a/packages/table/src/utils/getEmptyCellNode.ts +++ b/packages/table/src/utils/getEmptyCellNode.ts @@ -3,7 +3,8 @@ import { getPluginType, PlateEditor, Value } from '@udecode/plate-common'; import { ELEMENT_TD, ELEMENT_TH } from '../createTablePlugin'; import { TablePlugin } from '../types'; -export interface GetEmptyCellNodeOptions extends TablePlugin { +export interface GetEmptyCellNodeOptions + extends Omit { /** * Header cell */ diff --git a/packages/table/src/utils/getEmptyRowNode.ts b/packages/table/src/utils/getEmptyRowNode.ts index 38682507a0..063434e34e 100644 --- a/packages/table/src/utils/getEmptyRowNode.ts +++ b/packages/table/src/utils/getEmptyRowNode.ts @@ -3,7 +3,8 @@ import { getPluginType, PlateEditor, Value } from '@udecode/plate-common'; import { ELEMENT_TR } from '../createTablePlugin'; import { getEmptyCellNode, GetEmptyCellNodeOptions } from './getEmptyCellNode'; -export interface GetEmptyRowNodeOptions extends GetEmptyCellNodeOptions { +export interface GetEmptyRowNodeOptions + extends Omit { colCount?: number; } diff --git a/packages/table/src/withDeleteTable.spec.tsx b/packages/table/src/withDeleteTable.spec.tsx index f40e3b6322..98ef7814d4 100644 --- a/packages/table/src/withDeleteTable.spec.tsx +++ b/packages/table/src/withDeleteTable.spec.tsx @@ -3,6 +3,7 @@ import { createPlateEditor, PlateEditor } from '@udecode/plate-common'; import { jsx } from '@udecode/plate-test-utils'; +import { createTablePlugin } from './createTablePlugin'; import { withDeleteTable } from './withDeleteTable'; jsx; @@ -50,6 +51,7 @@ describe('withDeleteTable', () => { let editor = createPlateEditor({ editor: input, + plugins: [createTablePlugin()], }); editor = withDeleteTable(editor); @@ -104,6 +106,7 @@ describe('withDeleteTable', () => { let editor = createPlateEditor({ editor: input, + plugins: [createTablePlugin()], }); editor = withDeleteTable(editor); @@ -178,6 +181,7 @@ describe('withDeleteTable', () => { editor = createPlateEditor({ editor: input, + plugins: [createTablePlugin()], }); editor = withDeleteTable(editor); diff --git a/yarn.lock b/yarn.lock index 275d00b803..464392dbfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7108,11 +7108,11 @@ __metadata: languageName: node linkType: hard -"@udecode/plate-alignment@npm:24.5.2, @udecode/plate-alignment@workspace:^, @udecode/plate-alignment@workspace:packages/alignment": +"@udecode/plate-alignment@npm:25.0.1, @udecode/plate-alignment@workspace:^, @udecode/plate-alignment@workspace:packages/alignment": version: 0.0.0-use.local resolution: "@udecode/plate-alignment@workspace:packages/alignment" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7123,11 +7123,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-autoformat@npm:24.5.2, @udecode/plate-autoformat@workspace:^, @udecode/plate-autoformat@workspace:packages/autoformat": +"@udecode/plate-autoformat@npm:25.0.1, @udecode/plate-autoformat@workspace:^, @udecode/plate-autoformat@workspace:packages/autoformat": version: 0.0.0-use.local resolution: "@udecode/plate-autoformat@workspace:packages/autoformat" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7139,15 +7139,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-basic-elements@npm:24.5.2, @udecode/plate-basic-elements@workspace:^, @udecode/plate-basic-elements@workspace:packages/basic-elements": +"@udecode/plate-basic-elements@npm:25.0.1, @udecode/plate-basic-elements@workspace:^, @udecode/plate-basic-elements@workspace:packages/basic-elements": version: 0.0.0-use.local resolution: "@udecode/plate-basic-elements@workspace:packages/basic-elements" dependencies: - "@udecode/plate-block-quote": "npm:24.5.2" - "@udecode/plate-code-block": "npm:24.5.2" - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-heading": "npm:24.5.2" - "@udecode/plate-paragraph": "npm:24.5.2" + "@udecode/plate-block-quote": "npm:25.0.1" + "@udecode/plate-code-block": "npm:25.0.1" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-heading": "npm:25.0.1" + "@udecode/plate-paragraph": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7158,11 +7158,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-basic-marks@npm:24.5.2, @udecode/plate-basic-marks@workspace:^, @udecode/plate-basic-marks@workspace:packages/basic-marks": +"@udecode/plate-basic-marks@npm:25.0.1, @udecode/plate-basic-marks@workspace:^, @udecode/plate-basic-marks@workspace:packages/basic-marks": version: 0.0.0-use.local resolution: "@udecode/plate-basic-marks@workspace:packages/basic-marks" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7173,11 +7173,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-block-quote@npm:24.5.2, @udecode/plate-block-quote@workspace:^, @udecode/plate-block-quote@workspace:packages/block-quote": +"@udecode/plate-block-quote@npm:25.0.1, @udecode/plate-block-quote@workspace:^, @udecode/plate-block-quote@workspace:packages/block-quote": version: 0.0.0-use.local resolution: "@udecode/plate-block-quote@workspace:packages/block-quote" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7188,11 +7188,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-break@npm:24.5.2, @udecode/plate-break@workspace:^, @udecode/plate-break@workspace:packages/break": +"@udecode/plate-break@npm:25.0.1, @udecode/plate-break@workspace:^, @udecode/plate-break@workspace:packages/break": version: 0.0.0-use.local resolution: "@udecode/plate-break@workspace:packages/break" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7207,7 +7207,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-caption@workspace:packages/caption" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" react-textarea-autosize: "npm:^8.5.2" peerDependencies: react: ">=16.8.0" @@ -7224,7 +7224,7 @@ __metadata: resolution: "@udecode/plate-cloud@workspace:packages/cloud" dependencies: "@portive/client": "npm:10.0.3" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" delay: "npm:5.0.0" p-defer: "npm:^3.0.0" peerDependencies: @@ -7237,11 +7237,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-code-block@npm:24.5.2, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": +"@udecode/plate-code-block@npm:25.0.1, @udecode/plate-code-block@workspace:^, @udecode/plate-code-block@workspace:packages/code-block": version: 0.0.0-use.local resolution: "@udecode/plate-code-block@workspace:packages/code-block" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" prismjs: "npm:^1.29.0" peerDependencies: react: ">=16.8.0" @@ -7253,11 +7253,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-combobox@npm:24.5.2, @udecode/plate-combobox@workspace:^, @udecode/plate-combobox@workspace:packages/combobox": +"@udecode/plate-combobox@npm:25.0.1, @udecode/plate-combobox@workspace:^, @udecode/plate-combobox@workspace:packages/combobox": version: 0.0.0-use.local resolution: "@udecode/plate-combobox@workspace:packages/combobox" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" downshift: "npm:^6.1.12" peerDependencies: react: ">=16.8.0" @@ -7269,11 +7269,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-comments@npm:24.5.2, @udecode/plate-comments@workspace:^, @udecode/plate-comments@workspace:packages/comments": +"@udecode/plate-comments@npm:25.0.1, @udecode/plate-comments@workspace:^, @udecode/plate-comments@workspace:packages/comments": version: 0.0.0-use.local resolution: "@udecode/plate-comments@workspace:packages/comments" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7285,15 +7285,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-common@npm:24.5.2, @udecode/plate-common@workspace:^, @udecode/plate-common@workspace:packages/common": +"@udecode/plate-common@npm:25.0.1, @udecode/plate-common@workspace:^, @udecode/plate-common@workspace:packages/common": version: 0.0.0-use.local resolution: "@udecode/plate-common@workspace:packages/common" dependencies: - "@udecode/plate-core": "npm:24.4.0" - "@udecode/plate-utils": "npm:24.5.2" - "@udecode/slate": "npm:24.3.6" - "@udecode/slate-react": "npm:24.4.0" - "@udecode/slate-utils": "npm:24.3.6" + "@udecode/plate-core": "npm:25.0.1" + "@udecode/plate-utils": "npm:25.0.1" + "@udecode/slate": "npm:25.0.0" + "@udecode/slate-react": "npm:25.0.0" + "@udecode/slate-utils": "npm:25.0.0" "@udecode/utils": "npm:24.3.0" peerDependencies: react: ">=16.8.0" @@ -7305,13 +7305,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-core@npm:24.4.0, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core": +"@udecode/plate-core@npm:25.0.1, @udecode/plate-core@workspace:^, @udecode/plate-core@workspace:packages/core": version: 0.0.0-use.local resolution: "@udecode/plate-core@workspace:packages/core" dependencies: - "@udecode/slate": "npm:24.3.6" - "@udecode/slate-react": "npm:24.4.0" - "@udecode/slate-utils": "npm:24.3.6" + "@udecode/slate": "npm:25.0.0" + "@udecode/slate-react": "npm:25.0.0" + "@udecode/slate-utils": "npm:25.0.0" "@udecode/utils": "npm:24.3.0" "@udecode/zustood": "npm:^1.1.3" clsx: "npm:^1.2.1" @@ -7336,7 +7336,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-cursor@workspace:packages/cursor" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7351,7 +7351,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-dnd@workspace:packages/dnd" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" lodash: "npm:^4.17.21" raf: "npm:^3.4.1" peerDependencies: @@ -7371,8 +7371,8 @@ __metadata: resolution: "@udecode/plate-emoji@workspace:packages/emoji" dependencies: "@emoji-mart/data": "npm:^1.1.2" - "@udecode/plate-combobox": "npm:24.5.2" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-combobox": "npm:25.0.1" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7388,7 +7388,7 @@ __metadata: resolution: "@udecode/plate-excalidraw@workspace:packages/excalidraw" dependencies: "@excalidraw/excalidraw": "npm:0.12.0" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7399,11 +7399,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-find-replace@npm:24.5.2, @udecode/plate-find-replace@workspace:^, @udecode/plate-find-replace@workspace:packages/find-replace": +"@udecode/plate-find-replace@npm:25.0.1, @udecode/plate-find-replace@workspace:^, @udecode/plate-find-replace@workspace:packages/find-replace": version: 0.0.0-use.local resolution: "@udecode/plate-find-replace@workspace:packages/find-replace" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7414,14 +7414,14 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-floating@npm:24.5.2, @udecode/plate-floating@workspace:^, @udecode/plate-floating@workspace:packages/floating": +"@udecode/plate-floating@npm:25.0.1, @udecode/plate-floating@workspace:^, @udecode/plate-floating@workspace:packages/floating": version: 0.0.0-use.local resolution: "@udecode/plate-floating@workspace:packages/floating" dependencies: "@floating-ui/core": "npm:^1.3.1" "@floating-ui/react": "npm:^0.22.3" "@radix-ui/react-dropdown-menu": "npm:^2.0.5" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7432,11 +7432,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-font@npm:24.5.2, @udecode/plate-font@workspace:^, @udecode/plate-font@workspace:packages/font": +"@udecode/plate-font@npm:25.0.1, @udecode/plate-font@workspace:^, @udecode/plate-font@workspace:packages/font": version: 0.0.0-use.local resolution: "@udecode/plate-font@workspace:packages/font" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7448,11 +7448,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-heading@npm:24.5.2, @udecode/plate-heading@workspace:^, @udecode/plate-heading@workspace:packages/heading": +"@udecode/plate-heading@npm:25.0.1, @udecode/plate-heading@workspace:^, @udecode/plate-heading@workspace:packages/heading": version: 0.0.0-use.local resolution: "@udecode/plate-heading@workspace:packages/heading" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7463,11 +7463,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-highlight@npm:24.5.2, @udecode/plate-highlight@workspace:^, @udecode/plate-highlight@workspace:packages/highlight": +"@udecode/plate-highlight@npm:25.0.1, @udecode/plate-highlight@workspace:^, @udecode/plate-highlight@workspace:packages/highlight": version: 0.0.0-use.local resolution: "@udecode/plate-highlight@workspace:packages/highlight" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7478,11 +7478,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-horizontal-rule@npm:24.5.2, @udecode/plate-horizontal-rule@workspace:^, @udecode/plate-horizontal-rule@workspace:packages/horizontal-rule": +"@udecode/plate-horizontal-rule@npm:25.0.1, @udecode/plate-horizontal-rule@workspace:^, @udecode/plate-horizontal-rule@workspace:packages/horizontal-rule": version: 0.0.0-use.local resolution: "@udecode/plate-horizontal-rule@workspace:packages/horizontal-rule" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7493,13 +7493,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-indent-list@npm:24.5.2, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list": +"@udecode/plate-indent-list@npm:25.0.1, @udecode/plate-indent-list@workspace:^, @udecode/plate-indent-list@workspace:packages/indent-list": version: 0.0.0-use.local resolution: "@udecode/plate-indent-list@workspace:packages/indent-list" dependencies: - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-indent": "npm:24.5.2" - "@udecode/plate-list": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-indent": "npm:25.0.1" + "@udecode/plate-list": "npm:25.0.1" clsx: "npm:^1.2.1" peerDependencies: react: ">=16.8.0" @@ -7511,11 +7511,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-indent@npm:24.5.2, @udecode/plate-indent@workspace:^, @udecode/plate-indent@workspace:packages/indent": +"@udecode/plate-indent@npm:25.0.1, @udecode/plate-indent@workspace:^, @udecode/plate-indent@workspace:packages/indent": version: 0.0.0-use.local resolution: "@udecode/plate-indent@workspace:packages/indent" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7530,7 +7530,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-juice@workspace:packages/juice" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" juice: "npm:^8.1.0" peerDependencies: react: ">=16.8.0" @@ -7542,11 +7542,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-kbd@npm:24.5.2, @udecode/plate-kbd@workspace:^, @udecode/plate-kbd@workspace:packages/kbd": +"@udecode/plate-kbd@npm:25.0.1, @udecode/plate-kbd@workspace:^, @udecode/plate-kbd@workspace:packages/kbd": version: 0.0.0-use.local resolution: "@udecode/plate-kbd@workspace:packages/kbd" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7557,11 +7557,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-line-height@npm:24.5.2, @udecode/plate-line-height@workspace:^, @udecode/plate-line-height@workspace:packages/line-height": +"@udecode/plate-line-height@npm:25.0.1, @udecode/plate-line-height@workspace:^, @udecode/plate-line-height@workspace:packages/line-height": version: 0.0.0-use.local resolution: "@udecode/plate-line-height@workspace:packages/line-height" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7572,13 +7572,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-link@npm:24.5.2, @udecode/plate-link@workspace:^, @udecode/plate-link@workspace:packages/link": +"@udecode/plate-link@npm:25.0.1, @udecode/plate-link@workspace:^, @udecode/plate-link@workspace:packages/link": version: 0.0.0-use.local resolution: "@udecode/plate-link@workspace:packages/link" dependencies: - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-floating": "npm:24.5.2" - "@udecode/plate-normalizers": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-floating": "npm:25.0.1" + "@udecode/plate-normalizers": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7589,12 +7589,12 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-list@npm:24.5.2, @udecode/plate-list@workspace:^, @udecode/plate-list@workspace:packages/list": +"@udecode/plate-list@npm:25.0.1, @udecode/plate-list@workspace:^, @udecode/plate-list@workspace:packages/list": version: 0.0.0-use.local resolution: "@udecode/plate-list@workspace:packages/list" dependencies: - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-reset-node": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-reset-node": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7606,11 +7606,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-media@npm:24.5.2, @udecode/plate-media@workspace:^, @udecode/plate-media@workspace:packages/media": +"@udecode/plate-media@npm:25.0.1, @udecode/plate-media@workspace:^, @udecode/plate-media@workspace:packages/media": version: 0.0.0-use.local resolution: "@udecode/plate-media@workspace:packages/media" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" js-video-url-parser: "npm:^0.5.1" peerDependencies: react: ">=16.8.0" @@ -7622,12 +7622,12 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-mention@npm:24.5.2, @udecode/plate-mention@workspace:^, @udecode/plate-mention@workspace:packages/mention": +"@udecode/plate-mention@npm:25.0.1, @udecode/plate-mention@workspace:^, @udecode/plate-mention@workspace:packages/mention": version: 0.0.0-use.local resolution: "@udecode/plate-mention@workspace:packages/mention" dependencies: - "@udecode/plate-combobox": "npm:24.5.2" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-combobox": "npm:25.0.1" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7638,11 +7638,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-node-id@npm:24.5.2, @udecode/plate-node-id@workspace:^, @udecode/plate-node-id@workspace:packages/node-id": +"@udecode/plate-node-id@npm:25.0.1, @udecode/plate-node-id@workspace:^, @udecode/plate-node-id@workspace:packages/node-id": version: 0.0.0-use.local resolution: "@udecode/plate-node-id@workspace:packages/node-id" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7654,11 +7654,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-normalizers@npm:24.5.2, @udecode/plate-normalizers@workspace:^, @udecode/plate-normalizers@workspace:packages/normalizers": +"@udecode/plate-normalizers@npm:25.0.1, @udecode/plate-normalizers@workspace:^, @udecode/plate-normalizers@workspace:packages/normalizers": version: 0.0.0-use.local resolution: "@udecode/plate-normalizers@workspace:packages/normalizers" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7670,11 +7670,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-paragraph@npm:24.5.2, @udecode/plate-paragraph@workspace:^, @udecode/plate-paragraph@workspace:packages/paragraph": +"@udecode/plate-paragraph@npm:25.0.1, @udecode/plate-paragraph@workspace:^, @udecode/plate-paragraph@workspace:packages/paragraph": version: 0.0.0-use.local resolution: "@udecode/plate-paragraph@workspace:packages/paragraph" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7685,11 +7685,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-reset-node@npm:24.5.2, @udecode/plate-reset-node@workspace:^, @udecode/plate-reset-node@workspace:packages/reset-node": +"@udecode/plate-reset-node@npm:25.0.1, @udecode/plate-reset-node@workspace:^, @udecode/plate-reset-node@workspace:packages/reset-node": version: 0.0.0-use.local resolution: "@udecode/plate-reset-node@workspace:packages/reset-node" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7700,11 +7700,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-resizable@npm:24.5.2, @udecode/plate-resizable@workspace:^, @udecode/plate-resizable@workspace:packages/resizable": +"@udecode/plate-resizable@npm:25.0.1, @udecode/plate-resizable@workspace:^, @udecode/plate-resizable@workspace:packages/resizable": version: 0.0.0-use.local resolution: "@udecode/plate-resizable@workspace:packages/resizable" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7715,11 +7715,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-select@npm:24.5.2, @udecode/plate-select@workspace:^, @udecode/plate-select@workspace:packages/select": +"@udecode/plate-select@npm:25.0.1, @udecode/plate-select@workspace:^, @udecode/plate-select@workspace:packages/select": version: 0.0.0-use.local resolution: "@udecode/plate-select@workspace:packages/select" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7734,7 +7734,7 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate-selection@workspace:packages/selection" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" "@viselect/vanilla": "npm:3.2.5" copy-to-clipboard: "npm:^3.3.3" peerDependencies: @@ -7747,13 +7747,13 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-serializer-csv@npm:24.5.2, @udecode/plate-serializer-csv@workspace:^, @udecode/plate-serializer-csv@workspace:packages/serializer-csv": +"@udecode/plate-serializer-csv@npm:25.0.1, @udecode/plate-serializer-csv@workspace:^, @udecode/plate-serializer-csv@workspace:packages/serializer-csv": version: 0.0.0-use.local resolution: "@udecode/plate-serializer-csv@workspace:packages/serializer-csv" dependencies: "@types/papaparse": "npm:^5.3.7" - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-table": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-table": "npm:25.0.1" papaparse: "npm:^5.4.1" peerDependencies: react: ">=16.8.0" @@ -7765,17 +7765,17 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-serializer-docx@npm:24.5.2, @udecode/plate-serializer-docx@workspace:^, @udecode/plate-serializer-docx@workspace:packages/serializer-docx": +"@udecode/plate-serializer-docx@npm:25.0.1, @udecode/plate-serializer-docx@workspace:^, @udecode/plate-serializer-docx@workspace:packages/serializer-docx": version: 0.0.0-use.local resolution: "@udecode/plate-serializer-docx@workspace:packages/serializer-docx" dependencies: - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-heading": "npm:24.5.2" - "@udecode/plate-indent": "npm:24.5.2" - "@udecode/plate-indent-list": "npm:24.5.2" - "@udecode/plate-media": "npm:24.5.2" - "@udecode/plate-paragraph": "npm:24.5.2" - "@udecode/plate-table": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-heading": "npm:25.0.1" + "@udecode/plate-indent": "npm:25.0.1" + "@udecode/plate-indent-list": "npm:25.0.1" + "@udecode/plate-media": "npm:25.0.1" + "@udecode/plate-paragraph": "npm:25.0.1" + "@udecode/plate-table": "npm:25.0.1" validator: "npm:^13.9.0" peerDependencies: react: ">=16.8.0" @@ -7787,12 +7787,12 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-serializer-html@npm:24.5.2, @udecode/plate-serializer-html@workspace:^, @udecode/plate-serializer-html@workspace:packages/serializer-html": +"@udecode/plate-serializer-html@npm:25.0.1, @udecode/plate-serializer-html@workspace:^, @udecode/plate-serializer-html@workspace:packages/serializer-html": version: 0.0.0-use.local resolution: "@udecode/plate-serializer-html@workspace:packages/serializer-html" dependencies: "@types/papaparse": "npm:^5.3.7" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" html-entities: "npm:^2.4.0" peerDependencies: react: ">=16.8.0" @@ -7804,20 +7804,20 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-serializer-md@npm:24.5.2, @udecode/plate-serializer-md@workspace:^, @udecode/plate-serializer-md@workspace:packages/serializer-md": +"@udecode/plate-serializer-md@npm:25.0.1, @udecode/plate-serializer-md@workspace:^, @udecode/plate-serializer-md@workspace:packages/serializer-md": version: 0.0.0-use.local resolution: "@udecode/plate-serializer-md@workspace:packages/serializer-md" dependencies: - "@udecode/plate-basic-marks": "npm:24.5.2" - "@udecode/plate-block-quote": "npm:24.5.2" - "@udecode/plate-code-block": "npm:24.5.2" - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-heading": "npm:24.5.2" - "@udecode/plate-horizontal-rule": "npm:24.5.2" - "@udecode/plate-link": "npm:24.5.2" - "@udecode/plate-list": "npm:24.5.2" - "@udecode/plate-media": "npm:24.5.2" - "@udecode/plate-paragraph": "npm:24.5.2" + "@udecode/plate-basic-marks": "npm:25.0.1" + "@udecode/plate-block-quote": "npm:25.0.1" + "@udecode/plate-code-block": "npm:25.0.1" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-heading": "npm:25.0.1" + "@udecode/plate-horizontal-rule": "npm:25.0.1" + "@udecode/plate-link": "npm:25.0.1" + "@udecode/plate-list": "npm:25.0.1" + "@udecode/plate-media": "npm:25.0.1" + "@udecode/plate-paragraph": "npm:25.0.1" remark-parse: "npm:^9.0.0" unified: "npm:^9.2.2" peerDependencies: @@ -7830,11 +7830,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-suggestion@npm:24.5.2, @udecode/plate-suggestion@workspace:^, @udecode/plate-suggestion@workspace:packages/suggestion": +"@udecode/plate-suggestion@npm:25.0.1, @udecode/plate-suggestion@workspace:^, @udecode/plate-suggestion@workspace:packages/suggestion": version: 0.0.0-use.local resolution: "@udecode/plate-suggestion@workspace:packages/suggestion" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7845,11 +7845,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-tabbable@npm:24.5.2, @udecode/plate-tabbable@workspace:^, @udecode/plate-tabbable@workspace:packages/tabbable": +"@udecode/plate-tabbable@npm:25.0.1, @udecode/plate-tabbable@workspace:^, @udecode/plate-tabbable@workspace:packages/tabbable": version: 0.0.0-use.local resolution: "@udecode/plate-tabbable@workspace:packages/tabbable" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" tabbable: "npm:^6.2.0" peerDependencies: react: ">=16.8.0" @@ -7861,12 +7861,12 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-table@npm:24.5.2, @udecode/plate-table@workspace:^, @udecode/plate-table@workspace:packages/table": +"@udecode/plate-table@npm:25.0.1, @udecode/plate-table@workspace:^, @udecode/plate-table@workspace:packages/table": version: 0.0.0-use.local resolution: "@udecode/plate-table@workspace:packages/table" dependencies: - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-resizable": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-resizable": "npm:25.0.1" lodash: "npm:^4.17.21" peerDependencies: react: ">=16.8.0" @@ -7886,11 +7886,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-trailing-block@npm:24.5.2, @udecode/plate-trailing-block@workspace:^, @udecode/plate-trailing-block@workspace:packages/trailing-block": +"@udecode/plate-trailing-block@npm:25.0.1, @udecode/plate-trailing-block@workspace:^, @udecode/plate-trailing-block@workspace:packages/trailing-block": version: 0.0.0-use.local resolution: "@udecode/plate-trailing-block@workspace:packages/trailing-block" dependencies: - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -7930,15 +7930,15 @@ __metadata: languageName: unknown linkType: soft -"@udecode/plate-utils@npm:24.5.2, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils": +"@udecode/plate-utils@npm:25.0.1, @udecode/plate-utils@workspace:^, @udecode/plate-utils@workspace:packages/plate-utils": version: 0.0.0-use.local resolution: "@udecode/plate-utils@workspace:packages/plate-utils" dependencies: "@radix-ui/react-slot": "npm:^1.0.2" - "@udecode/plate-core": "npm:24.4.0" - "@udecode/slate": "npm:24.3.6" - "@udecode/slate-react": "npm:24.4.0" - "@udecode/slate-utils": "npm:24.3.6" + "@udecode/plate-core": "npm:25.0.1" + "@udecode/slate": "npm:25.0.0" + "@udecode/slate-react": "npm:25.0.0" + "@udecode/slate-utils": "npm:25.0.0" "@udecode/utils": "npm:24.3.0" clsx: "npm:^1.2.1" lodash: "npm:^4.17.21" @@ -7958,7 +7958,7 @@ __metadata: dependencies: "@hocuspocus/provider": "npm:^2.2.1" "@slate-yjs/core": "npm:^1.0.1" - "@udecode/plate-common": "npm:24.5.2" + "@udecode/plate-common": "npm:25.0.1" yjs: "npm:^13.5.42" peerDependencies: react: ">=16.8.0" @@ -7974,44 +7974,44 @@ __metadata: version: 0.0.0-use.local resolution: "@udecode/plate@workspace:packages/plate" dependencies: - "@udecode/plate-alignment": "npm:24.5.2" - "@udecode/plate-autoformat": "npm:24.5.2" - "@udecode/plate-basic-elements": "npm:24.5.2" - "@udecode/plate-basic-marks": "npm:24.5.2" - "@udecode/plate-block-quote": "npm:24.5.2" - "@udecode/plate-break": "npm:24.5.2" - "@udecode/plate-code-block": "npm:24.5.2" - "@udecode/plate-combobox": "npm:24.5.2" - "@udecode/plate-comments": "npm:24.5.2" - "@udecode/plate-common": "npm:24.5.2" - "@udecode/plate-find-replace": "npm:24.5.2" - "@udecode/plate-floating": "npm:24.5.2" - "@udecode/plate-font": "npm:24.5.2" - "@udecode/plate-heading": "npm:24.5.2" - "@udecode/plate-highlight": "npm:24.5.2" - "@udecode/plate-horizontal-rule": "npm:24.5.2" - "@udecode/plate-indent": "npm:24.5.2" - "@udecode/plate-indent-list": "npm:24.5.2" - "@udecode/plate-kbd": "npm:24.5.2" - "@udecode/plate-line-height": "npm:24.5.2" - "@udecode/plate-link": "npm:24.5.2" - "@udecode/plate-list": "npm:24.5.2" - "@udecode/plate-media": "npm:24.5.2" - "@udecode/plate-mention": "npm:24.5.2" - "@udecode/plate-node-id": "npm:24.5.2" - "@udecode/plate-normalizers": "npm:24.5.2" - "@udecode/plate-paragraph": "npm:24.5.2" - "@udecode/plate-reset-node": "npm:24.5.2" - "@udecode/plate-resizable": "npm:24.5.2" - "@udecode/plate-select": "npm:24.5.2" - "@udecode/plate-serializer-csv": "npm:24.5.2" - "@udecode/plate-serializer-docx": "npm:24.5.2" - "@udecode/plate-serializer-html": "npm:24.5.2" - "@udecode/plate-serializer-md": "npm:24.5.2" - "@udecode/plate-suggestion": "npm:24.5.2" - "@udecode/plate-tabbable": "npm:24.5.2" - "@udecode/plate-table": "npm:24.5.2" - "@udecode/plate-trailing-block": "npm:24.5.2" + "@udecode/plate-alignment": "npm:25.0.1" + "@udecode/plate-autoformat": "npm:25.0.1" + "@udecode/plate-basic-elements": "npm:25.0.1" + "@udecode/plate-basic-marks": "npm:25.0.1" + "@udecode/plate-block-quote": "npm:25.0.1" + "@udecode/plate-break": "npm:25.0.1" + "@udecode/plate-code-block": "npm:25.0.1" + "@udecode/plate-combobox": "npm:25.0.1" + "@udecode/plate-comments": "npm:25.0.1" + "@udecode/plate-common": "npm:25.0.1" + "@udecode/plate-find-replace": "npm:25.0.1" + "@udecode/plate-floating": "npm:25.0.1" + "@udecode/plate-font": "npm:25.0.1" + "@udecode/plate-heading": "npm:25.0.1" + "@udecode/plate-highlight": "npm:25.0.1" + "@udecode/plate-horizontal-rule": "npm:25.0.1" + "@udecode/plate-indent": "npm:25.0.1" + "@udecode/plate-indent-list": "npm:25.0.1" + "@udecode/plate-kbd": "npm:25.0.1" + "@udecode/plate-line-height": "npm:25.0.1" + "@udecode/plate-link": "npm:25.0.1" + "@udecode/plate-list": "npm:25.0.1" + "@udecode/plate-media": "npm:25.0.1" + "@udecode/plate-mention": "npm:25.0.1" + "@udecode/plate-node-id": "npm:25.0.1" + "@udecode/plate-normalizers": "npm:25.0.1" + "@udecode/plate-paragraph": "npm:25.0.1" + "@udecode/plate-reset-node": "npm:25.0.1" + "@udecode/plate-resizable": "npm:25.0.1" + "@udecode/plate-select": "npm:25.0.1" + "@udecode/plate-serializer-csv": "npm:25.0.1" + "@udecode/plate-serializer-docx": "npm:25.0.1" + "@udecode/plate-serializer-html": "npm:25.0.1" + "@udecode/plate-serializer-md": "npm:25.0.1" + "@udecode/plate-suggestion": "npm:25.0.1" + "@udecode/plate-tabbable": "npm:25.0.1" + "@udecode/plate-table": "npm:25.0.1" + "@udecode/plate-trailing-block": "npm:25.0.1" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" @@ -8022,11 +8022,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate-react@npm:24.4.0, @udecode/slate-react@workspace:^, @udecode/slate-react@workspace:packages/slate-react": +"@udecode/slate-react@npm:25.0.0, @udecode/slate-react@workspace:^, @udecode/slate-react@workspace:packages/slate-react": version: 0.0.0-use.local resolution: "@udecode/slate-react@workspace:packages/slate-react" dependencies: - "@udecode/slate": "npm:24.3.6" + "@udecode/slate": "npm:25.0.0" "@udecode/utils": "npm:24.3.0" peerDependencies: react: ">=16.8.0" @@ -8037,11 +8037,11 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate-utils@npm:24.3.6, @udecode/slate-utils@workspace:^, @udecode/slate-utils@workspace:packages/slate-utils": +"@udecode/slate-utils@npm:25.0.0, @udecode/slate-utils@workspace:^, @udecode/slate-utils@workspace:packages/slate-utils": version: 0.0.0-use.local resolution: "@udecode/slate-utils@workspace:packages/slate-utils" dependencies: - "@udecode/slate": "npm:24.3.6" + "@udecode/slate": "npm:25.0.0" "@udecode/utils": "npm:24.3.0" lodash: "npm:^4.17.21" peerDependencies: @@ -8050,7 +8050,7 @@ __metadata: languageName: unknown linkType: soft -"@udecode/slate@npm:24.3.6, @udecode/slate@workspace:^, @udecode/slate@workspace:packages/slate": +"@udecode/slate@npm:25.0.0, @udecode/slate@workspace:^, @udecode/slate@workspace:packages/slate": version: 0.0.0-use.local resolution: "@udecode/slate@workspace:packages/slate" dependencies: