Skip to content

Commit 9d57425

Browse files
authored
[Table Improvements] Ignore span cells when merge table cells (#3281)
When merging table cells, count table that are span as one cell, so two or more cells cannot be merge to one single span cell.
1 parent a190117 commit 9d57425

2 files changed

Lines changed: 371 additions & 32 deletions

File tree

packages/roosterjs-content-model-dom/lib/modelApi/editing/mergeModel.ts

Lines changed: 111 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import type {
2727
ReadonlyContentModelBlock,
2828
ReadonlyContentModelBlockGroup,
2929
ReadonlyContentModelDocument,
30+
ReadonlyContentModelTable,
3031
ShallowMutableContentModelParagraph,
3132
} from 'roosterjs-content-model-types';
3233

@@ -191,44 +192,64 @@ function mergeTables(
191192
const { table: readonlyTable, colIndex, rowIndex } = tableContext;
192193
const table = mutateBlock(readonlyTable);
193194

195+
const newTableColCount = newTable.rows[0]?.cells.length || 0;
196+
const newTableRowCount = newTable.rows.length;
197+
198+
const lastTargetColIndex = getTargetColIndex(table, rowIndex, colIndex, newTableColCount);
199+
const extraColsNeeded = lastTargetColIndex - table.rows[0].cells.length;
200+
201+
if (extraColsNeeded > 0) {
202+
const currentColCount = table.rows[0].cells.length;
203+
for (let col = 0; col < extraColsNeeded; col++) {
204+
const newColIndex = currentColCount + col;
205+
for (let k = 0; k < table.rows.length; k++) {
206+
const leftCell = table.rows[k]?.cells[newColIndex - 1];
207+
table.rows[k].cells[newColIndex] = createTableCell(
208+
false /*spanLeft*/,
209+
false /*spanAbove*/,
210+
leftCell?.isHeader,
211+
leftCell?.format
212+
);
213+
}
214+
}
215+
}
216+
217+
const lastTargetRowIndex = getTargetRowIndex(table, rowIndex, newTableRowCount, colIndex);
218+
const extraRowsNeeded = lastTargetRowIndex - table.rows.length;
219+
220+
if (extraRowsNeeded > 0) {
221+
const currentRowCount = table.rows.length;
222+
const colCount = table.rows[0]?.cells.length || 0;
223+
for (let row = 0; row < extraRowsNeeded; row++) {
224+
const newRowIndex = currentRowCount + row;
225+
table.rows[newRowIndex] = {
226+
cells: [],
227+
format: {},
228+
height: 0,
229+
};
230+
for (let k = 0; k < colCount; k++) {
231+
const aboveCell = table.rows[newRowIndex - 1]?.cells[k];
232+
table.rows[newRowIndex].cells[k] = createTableCell(
233+
false /*spanLeft*/,
234+
false /*spanAbove*/,
235+
false /*isHeader*/,
236+
aboveCell?.format
237+
);
238+
}
239+
}
240+
}
241+
194242
for (let i = 0; i < newTable.rows.length; i++) {
243+
const targetRowIndex = getTargetRowIndex(table, rowIndex, i, colIndex);
244+
195245
for (let j = 0; j < newTable.rows[i].cells.length; j++) {
196246
const newCell = newTable.rows[i].cells[j];
197247

198-
if (i == 0 && colIndex + j >= table.rows[0].cells.length) {
199-
for (let k = 0; k < table.rows.length; k++) {
200-
const leftCell = table.rows[k]?.cells[colIndex + j - 1];
201-
table.rows[k].cells[colIndex + j] = createTableCell(
202-
false /*spanLeft*/,
203-
false /*spanAbove*/,
204-
leftCell?.isHeader,
205-
leftCell?.format
206-
);
207-
}
208-
}
209-
210-
if (j == 0 && rowIndex + i >= table.rows.length) {
211-
if (!table.rows[rowIndex + i]) {
212-
table.rows[rowIndex + i] = {
213-
cells: [],
214-
format: {},
215-
height: 0,
216-
};
217-
}
248+
const targetColIndex = getTargetColIndex(table, targetRowIndex, colIndex, j);
218249

219-
for (let k = 0; k < table.rows[rowIndex].cells.length; k++) {
220-
const aboveCell = table.rows[rowIndex + i - 1]?.cells[k];
221-
table.rows[rowIndex + i].cells[k] = createTableCell(
222-
false /*spanLeft*/,
223-
false /*spanAbove*/,
224-
false /*isHeader*/,
225-
aboveCell?.format
226-
);
227-
}
228-
}
250+
const oldCell = table.rows[targetRowIndex]?.cells[targetColIndex];
229251

230-
const oldCell = table.rows[rowIndex + i].cells[colIndex + j];
231-
table.rows[rowIndex + i].cells[colIndex + j] = newCell;
252+
table.rows[targetRowIndex].cells[targetColIndex] = newCell;
232253

233254
if (i == 0 && j == 0) {
234255
const newMarker = createSelectionMarker(marker.format);
@@ -494,6 +515,7 @@ function getFormatWithoutSegmentFormat(
494515
KeysOfSegmentFormat.forEach(key => delete resultFormat[key]);
495516
return resultFormat;
496517
}
518+
497519
function getHyperlinkTextColor(sourceFormat: ContentModelHyperLinkFormat) {
498520
const result: ContentModelHyperLinkFormat = {};
499521
if (sourceFormat.textColor) {
@@ -502,3 +524,60 @@ function getHyperlinkTextColor(sourceFormat: ContentModelHyperLinkFormat) {
502524

503525
return result;
504526
}
527+
528+
function getTargetColIndex(
529+
table: ReadonlyContentModelTable,
530+
rowIndex: number,
531+
startColIndex: number,
532+
offset: number
533+
): number {
534+
const row = table.rows[rowIndex];
535+
if (!row) {
536+
return startColIndex + offset;
537+
}
538+
539+
if (offset === 0) {
540+
return startColIndex;
541+
}
542+
543+
let targetColIndex = startColIndex;
544+
let logicalCellsToSkip = offset;
545+
546+
while (logicalCellsToSkip > 0) {
547+
targetColIndex++;
548+
549+
if (targetColIndex >= row.cells.length) {
550+
logicalCellsToSkip--;
551+
} else if (!row.cells[targetColIndex].spanLeft) {
552+
logicalCellsToSkip--;
553+
}
554+
}
555+
556+
return targetColIndex;
557+
}
558+
559+
function getTargetRowIndex(
560+
table: ReadonlyContentModelTable,
561+
startRowIndex: number,
562+
offset: number,
563+
colIndex: number
564+
): number {
565+
if (offset === 0) {
566+
return startRowIndex;
567+
}
568+
569+
let targetRowIndex = startRowIndex;
570+
let logicalRowsToSkip = offset;
571+
572+
while (logicalRowsToSkip > 0) {
573+
targetRowIndex++;
574+
575+
if (targetRowIndex >= table.rows.length) {
576+
logicalRowsToSkip--;
577+
} else if (!table.rows[targetRowIndex]?.cells[colIndex]?.spanAbove) {
578+
logicalRowsToSkip--;
579+
}
580+
}
581+
582+
return targetRowIndex;
583+
}

0 commit comments

Comments
 (0)