diff --git a/.changeset/ninety-badgers-run.md b/.changeset/ninety-badgers-run.md new file mode 100644 index 0000000000..30f13df052 --- /dev/null +++ b/.changeset/ninety-badgers-run.md @@ -0,0 +1,7 @@ +--- +"@udecode/plate-table": patch +--- + +Fix unmerge & compute cell indices + +Remove computeAllCellIndices, use computeCellIndices instead diff --git a/packages/table/src/lib/merge/computeCellIndices.ts b/packages/table/src/lib/merge/computeCellIndices.ts index 90fd1ce5b3..e33b79e6af 100644 --- a/packages/table/src/lib/merge/computeCellIndices.ts +++ b/packages/table/src/lib/merge/computeCellIndices.ts @@ -12,88 +12,48 @@ import { getRowSpan } from '../queries/getRowSpan'; export function computeCellIndices( editor: SlateEditor, - tableEl: TTableElement, - cellEl: TTableCellElement + tableNode: TTableElement, + cellNode?: TTableCellElement ) { const options = editor.getOptions(BaseTablePlugin); - const tableNodes = tableEl.children; + const skipCells: boolean[][] = []; + let targetIndices: { col: number; row: number } | undefined; - let rowIndex = -1; - let colIndex = -1; + for (let rowIndex = 0; rowIndex < tableNode.children.length; rowIndex++) { + const row = tableNode.children[rowIndex] as TTableRowElement; + let colIndex = 0; - for (let r = 0; r < tableNodes.length; r++) { - const row = tableNodes[r] as TTableRowElement; - - let cIndex = 0; + for (const cellElement of row.children as TTableCellElement[]) { + while (skipCells[rowIndex]?.[colIndex]) { + colIndex++; + } - for (const item of row.children) { - const cell = item as TTableCellElement; + const currentIndices = { col: colIndex, row: rowIndex }; + options._cellIndices?.set(cellElement, currentIndices); - if (cellEl === cell) { - colIndex = cIndex; - rowIndex = r; + if (cellElement === cellNode) { + targetIndices = currentIndices; break; } - cIndex += getColSpan(cell); - } - } - - 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); - const _rowSpan = getRowSpan(prevCell); + const colSpan = getColSpan(cellElement); + const rowSpan = getRowSpan(cellElement); - if (prevIndices) { - const { col: prevColIndex } = prevIndices; + for (let r = 0; r < rowSpan; r++) { + skipCells[rowIndex + r] = skipCells[rowIndex + r] || []; - if ( - // colIndex affects - prevColIndex <= colIndex && - // rowSpan affects - _rowSpan && - _rowSpan > 1 && - rowIndex - _rowIndex < _rowSpan - ) { - colIndex += getColSpan(prevCell); + for (let c = 0; c < colSpan; c++) { + skipCells[rowIndex + r][colIndex + c] = true; } } - }); - }); - if (rowIndex === -1 || colIndex === -1) { - return null; - } + colIndex += colSpan; + } - const indices = { col: colIndex, row: rowIndex }; - options?._cellIndices?.set(cellEl, indices); + if (targetIndices) break; + } - return indices; + return targetIndices; } - -export const computeAllCellIndices = ( - editor: SlateEditor, - tableNode: TTableElement -) => { - const options = editor.getOptions(BaseTablePlugin); - - // Iterate through the table rows - for (const tableChild of tableNode.children) { - const row = tableChild as TTableRowElement; - - // Iterate through the row cells - for (const rowChild of row.children) { - const cell = rowChild as TTableCellElement; - - const indices = computeCellIndices(editor, tableNode, cell); - - if (indices) { - options._cellIndices?.set(cell, indices); - } - } - } -}; diff --git a/packages/table/src/lib/merge/insertTableColumn.ts b/packages/table/src/lib/merge/insertTableColumn.ts index d44f7998a8..f983c1ee6c 100644 --- a/packages/table/src/lib/merge/insertTableColumn.ts +++ b/packages/table/src/lib/merge/insertTableColumn.ts @@ -8,6 +8,7 @@ import { withoutNormalizing, } from '@udecode/plate-common'; import { getEditorPlugin } from '@udecode/plate-common'; +import cloneDeep from 'lodash/cloneDeep'; import { Path } from 'slate'; import type { @@ -131,12 +132,15 @@ export const insertTableMergeColumn = ( const endCurI = curColIndex + curColSpan - 1; if (endCurI >= nextColIndex && !firstCol) { + const colSpan = curColSpan + 1; + const newCell = cloneDeep({ ...curCell, colSpan }); + + if (newCell.attributes?.colspan) { + newCell.attributes.colspan = colSpan.toString(); + } + // make wider - setNodes( - editor, - { ...curCell, colSpan: curColSpan + 1 }, - { at: currentCellPath } - ); + setNodes(editor, newCell, { at: currentCellPath }); } else { // add new const curRowPath = currentCellPath.slice(0, -1); diff --git a/packages/table/src/lib/merge/insertTableRow.ts b/packages/table/src/lib/merge/insertTableRow.ts index 06527a4bd7..9ed756b45d 100644 --- a/packages/table/src/lib/merge/insertTableRow.ts +++ b/packages/table/src/lib/merge/insertTableRow.ts @@ -8,6 +8,7 @@ import { setNodes, withoutNormalizing, } from '@udecode/plate-common'; +import cloneDeep from 'lodash/cloneDeep'; import { Path } from 'slate'; import type { @@ -134,12 +135,15 @@ export const insertTableMergeRow = ( const endCurI = curRowIndex + curRowSpan - 1; if (endCurI >= nextRowIndex && !firstRow) { + const rowSpan = curRowSpan + 1; + const newCell = cloneDeep({ ...curCell, rowSpan }); + + if (newCell.attributes?.rowspan) { + newCell.attributes.rowspan = rowSpan.toString(); + } + // make higher - setNodes( - editor, - { ...curCell, rowSpan: curRowSpan + 1 }, - { at: currentCellPath } - ); + setNodes(editor, newCell, { at: currentCellPath }); } else { // add new const row = getParentNode(editor, currentCellPath)!; diff --git a/packages/table/src/react/components/TableElement/useTableElement.ts b/packages/table/src/react/components/TableElement/useTableElement.ts index 5df0f848b2..1921b96e95 100644 --- a/packages/table/src/react/components/TableElement/useTableElement.ts +++ b/packages/table/src/react/components/TableElement/useTableElement.ts @@ -3,7 +3,7 @@ import React from 'react'; import { collapseSelection } from '@udecode/plate-common'; import { useEditorRef, useElement } from '@udecode/plate-common/react'; -import { type TTableElement, computeAllCellIndices } from '../../../lib'; +import { type TTableElement, computeCellIndices } from '../../../lib'; import { TablePlugin } from '../../TablePlugin'; import { useTableStore } from '../../stores'; import { useSelectedCells } from './useSelectedCells'; @@ -39,7 +39,7 @@ export const useTableElementState = ({ React.useEffect(() => { if (enableMerging) { - computeAllCellIndices(editor, element); + computeCellIndices(editor, element); } }, [editor, element, enableMerging]); diff --git a/packages/table/src/react/merge/deleteColumn.ts b/packages/table/src/react/merge/deleteColumn.ts index a1dc4a2984..29780f35e3 100644 --- a/packages/table/src/react/merge/deleteColumn.ts +++ b/packages/table/src/react/merge/deleteColumn.ts @@ -10,6 +10,7 @@ import { someNode, withoutNormalizing, } from '@udecode/plate-common'; +import cloneDeep from 'lodash/cloneDeep'; import { type TTableCellElement, @@ -125,12 +126,14 @@ export const deleteTableMergeColumn = (editor: SlateEditor) => { endingColIndex ); const colsNumberAffected = curCellEndingColIndex - deletingColIndex + 1; + const colSpan = curColSpan - colsNumberAffected; + const newCell = cloneDeep({ ...curCell, colSpan }); - setNodes( - editor, - { ...curCell, colSpan: curColSpan - colsNumberAffected }, - { at: curCellPath } - ); + if (newCell.attributes?.colspan) { + newCell.attributes.colspan = colSpan.toString(); + } + + setNodes(editor, newCell, { at: curCellPath }); }); const trEntry = getAboveNode(editor, { diff --git a/packages/table/src/react/merge/deleteRow.ts b/packages/table/src/react/merge/deleteRow.ts index 213874b6d9..bb029696a5 100644 --- a/packages/table/src/react/merge/deleteRow.ts +++ b/packages/table/src/react/merge/deleteRow.ts @@ -9,6 +9,7 @@ import { someNode, } from '@udecode/plate-common'; import { findNodePath } from '@udecode/plate-common/react'; +import cloneDeep from 'lodash/cloneDeep'; import { type TTableCellElement, @@ -110,12 +111,10 @@ export const deleteTableMergeRow = (editor: SlateEditor) => { return; } if (nextRow) { - moveToNextRowCells.forEach((cur, index) => { - const curRowCell = cur as TTableCellElement; - const { col: curRowCellColIndex } = getCellIndices( - cellIndices!, - curRowCell - )!; + for (let index = 0; index < moveToNextRowCells.length; index++) { + const curRowCell = moveToNextRowCells[index] as TTableCellElement; + const { col: curRowCellColIndex, row: curRowCellRowIndex } = + getCellIndices(cellIndices!, curRowCell)!; const curRowCellRowSpan = getRowSpan(curRowCell); // search for anchor cell where to place current cell @@ -126,6 +125,28 @@ export const deleteTableMergeRow = (editor: SlateEditor) => { return curColIndex >= curRowCellColIndex; }); + if (startingCellIndex === -1) { + const startingCell = nextRow.children.at(-1) as TTableCellElement; + const startingCellPath = findNodePath(editor, startingCell)!; + const tablePath = startingCellPath.slice(0, -2); + const colPath = startingCellPath.at(-1)! + index + 1; + const nextRowStartCellPath = [...tablePath, nextRowIndex, colPath]; + + const rowsNumberAffected = endingRowIndex - curRowCellRowIndex + 1; + const rowSpan = curRowCellRowSpan - rowsNumberAffected; + const newCell = cloneDeep({ ...curRowCell, rowSpan }); + + if (newCell.attributes?.rowspan) { + newCell.attributes.rowspan = rowSpan.toString(); + } + + insertElements(editor, newCell, { + at: nextRowStartCellPath, + }); + + continue; + } + const startingCell = nextRow.children[ startingCellIndex ] as TTableCellElement; @@ -152,16 +173,18 @@ export const deleteTableMergeRow = (editor: SlateEditor) => { colPath + incrementBy, ]; - const rowsNumberAffected = endingRowIndex - curRowCellColIndex + 1; + const rowsNumberAffected = endingRowIndex - curRowCellRowIndex + 1; + const rowSpan = curRowCellRowSpan - rowsNumberAffected; + const newCell = cloneDeep({ ...curRowCell, rowSpan }); - // 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 }); - }); + if (newCell.attributes?.rowspan) { + newCell.attributes.rowspan = rowSpan.toString(); + } + + insertElements(editor, newCell, { + at: nextRowStartCellPath, + }); + } } squizeRowSpanCells.forEach((cur) => { @@ -179,12 +202,14 @@ export const deleteTableMergeRow = (editor: SlateEditor) => { endingRowIndex ); const rowsNumberAffected = curCellEndingRowIndex - deletingRowIndex + 1; + const rowSpan = curRowCellRowSpan - rowsNumberAffected; + const newCell = cloneDeep({ ...curRowCell, rowSpan }); - setNodes( - editor, - { ...curRowCell, rowSpan: curRowCellRowSpan - rowsNumberAffected }, - { at: curCellPath } - ); + if (newCell.attributes?.rowspan) { + newCell.attributes.rowspan = rowSpan.toString(); + } + + setNodes(editor, newCell, { at: curCellPath }); }); const rowToDelete = table.children[deletingRowIndex] as TTableRowElement; diff --git a/packages/table/src/react/merge/unmergeTableCells.ts b/packages/table/src/react/merge/unmergeTableCells.ts index f2aa6f0869..3b0a246d14 100644 --- a/packages/table/src/react/merge/unmergeTableCells.ts +++ b/packages/table/src/react/merge/unmergeTableCells.ts @@ -1,13 +1,15 @@ +import type { Path } from 'slate'; + import { type SlateEditor, type TDescendant, findNode, getEditorPlugin, - getNode, insertElements, removeNodes, withoutNormalizing, } from '@udecode/plate-common'; +import { findNodePath } from '@udecode/plate-common/react'; import { type TTableCellElement, @@ -19,12 +21,17 @@ import { getColSpan, getRowSpan, } from '../../lib'; -import { TableCellHeaderPlugin, TablePlugin } from '../TablePlugin'; +import { + TableCellHeaderPlugin, + TablePlugin, + TableRowPlugin, +} from '../TablePlugin'; import { getTableGridAbove } from '../queries'; export const unmergeTableCells = (editor: SlateEditor) => { - const { api, getOptions, type } = getEditorPlugin(editor, TablePlugin); + const { api, getOptions } = getEditorPlugin(editor, TablePlugin); const { _cellIndices: cellIndices } = getOptions(); + const tableRowType = editor.getType(TableRowPlugin); withoutNormalizing(editor, () => { const cellEntries = getTableGridAbove(editor, { format: 'cell' }); @@ -64,50 +71,56 @@ export const unmergeTableCells = (editor: SlateEditor) => { cellElem as TTableCellElement )!; - const getColPathForRow = (row: number) => { - let newColPath = 0; - + const getClosestColPathForRow = (row: number, targetCol: number) => { const rowEntry = findNode(editor, { at: [...tablePath, row], - match: { type: editor.getType(BaseTableRowPlugin) }, - })!; // TODO: improve typing + match: { type: tableRowType }, + }); if (!rowEntry) { - return newColPath; + return 0; } const rowEl = rowEntry[0] as TTableRowElement; + let closestColPath: Path = []; + let smallestDiff = Number.POSITIVE_INFINITY; + let isDirectionLeft = false; - for (const item of rowEl.children) { - const { col: c } = getCellIndices( - cellIndices!, - item as TTableCellElement - )!; + rowEl.children.forEach((cell) => { + const cellElement = cell as TTableCellElement; + const { col: cellCol } = getCellIndices(cellIndices!, cellElement)!; - if (c === col - 1) { - newColPath = rowEl.children.indexOf(item) + 1; + const diff = Math.abs(cellCol - targetCol); - break; + if (diff < smallestDiff) { + smallestDiff = diff; + closestColPath = findNodePath(editor, cellElement)!; + isDirectionLeft = cellCol < targetCol; } - if (col + getColSpan(cellElem as TTableCellElement) === c - 1) { - newColPath = rowEl.children.indexOf(item); + }); + + if (closestColPath.length > 0) { + const lastIndex = closestColPath.at(-1)!; - break; + if (isDirectionLeft) { + return lastIndex + 1; } + + return lastIndex; } - return newColPath; + return 1; }; // 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); + const pathForNextRows = getClosestColPathForRow(currentRowPath, col); const newRowChildren: TTableRowElement[] = []; const _rowPath = [...tablePath, currentRowPath]; const rowEntry = findNode(editor, { at: _rowPath, - match: { type }, + match: { type: tableRowType }, }); for (let j = 0; j < colPaths.length; j++) { @@ -142,25 +155,12 @@ export const unmergeTableCells = (editor: SlateEditor) => { } // Recalculate the split cells - const needComputeCells: number[][] = []; - const cols = []; - const maxCol = colPath + colSpan; - const maxRow = rowPath + rowSpan; + const tableElement = findNode(editor, { + at: tablePath, + })?.[0]; - for (let col = colPath; col < maxCol; col++) { - cols.push(col); + if (tableElement) { + computeCellIndices(editor, tableElement); } - - for (let row = rowPath; row < maxRow; row++) { - cols.forEach((col) => { - needComputeCells.push([...tablePath, row, col]); - }); - } - - const tableElement = getNode(editor, tablePath) as TTableElement; - needComputeCells.forEach((path) => { - const cell = getNode(editor, path); - computeCellIndices(editor, tableElement, cell as TTableCellElement); - }); }); }; diff --git a/packages/table/src/react/withSetFragmentDataTable.ts b/packages/table/src/react/withSetFragmentDataTable.ts index 143b349083..36c7ee31af 100644 --- a/packages/table/src/react/withSetFragmentDataTable.ts +++ b/packages/table/src/react/withSetFragmentDataTable.ts @@ -1,5 +1,3 @@ -import type { ExtendEditor } from '@udecode/plate-common/react'; - import { type TElement, getEndPoint, @@ -7,7 +5,7 @@ import { select, withoutNormalizing, } from '@udecode/plate-common'; -import { Path } from 'slate'; +import { type ExtendEditor, findNodePath } from '@udecode/plate-common/react'; import { type TTableCellElement, @@ -42,22 +40,12 @@ export const withSetFragmentDataTable: ExtendEditor = ({ return; } - const selectionStart = - Path.compare(initialSelection.anchor.path, initialSelection.focus.path) < - 1 - ? initialSelection.anchor - : initialSelection.focus; - - const [tableNode, tablePath] = tableEntry; + const [tableNode] = tableEntry; const tableRows = tableNode.children as TElement[]; - - const tableSelectionStart = selectionStart.path.slice( - tablePath.length, - tablePath.length + 2 + tableNode.children = tableNode.children.filter( + (v) => (v as TTableCellElement).children.length > 0 ); - const [y, x] = tableSelectionStart; - let textCsv = ''; let textTsv = ''; @@ -80,9 +68,8 @@ export const withSetFragmentDataTable: ExtendEditor = ({ } withoutNormalizing(editor, () => { - tableRows.forEach((row, rowIndex) => { + tableRows.forEach((row) => { const rowCells = row.children as TTableCellElement[]; - const rowPath = tablePath.concat(y + rowIndex); const cellStrings: string[] = []; const rowElement = @@ -90,11 +77,11 @@ export const withSetFragmentDataTable: ExtendEditor = ({ ? document.createElement('th') : document.createElement('tr'); - rowCells.forEach((cell, cellIndex) => { + rowCells.forEach((cell) => { // need to clean data before every iteration data.clearData(); - const cellPath = rowPath.concat(x + cellIndex); + const cellPath = findNodePath(editor, cell)!; // select cell by cell select(editor, {