Skip to content

Commit d25ebe8

Browse files
Reset spanAbove when merging tables to avoid unexpected vertical merge on paste
When pasting cells that came from a horizontal split next to a vertically split cell, the pasted cells were being vertically merged. Reset spanAbove on all incoming source table cells during a table-into-table merge so pasted cells become standalone cells instead of continuations of a vertical merge. Repro: OS: Win 11 Monarch: 1.2026.226.200 Ring - Dogfood OWA: 20260227032 - New mail - Insert a table - Split a cell horizontally > Split another cell vertically in the same row - Input some content in the horizontal split cells - Copy the horizontal split cells and then paste - Observe Actual: The pasted cells were vertically merged. Expect: Pasting should not cause cells to merge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f27a803-6207-4fd0-a540-4ab839bbe28a
1 parent f686989 commit d25ebe8

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ function mergeTables(
198198
const lastTargetColIndex = getTargetColIndex(table, rowIndex, colIndex, newTableColCount);
199199
const extraColsNeeded = lastTargetColIndex - table.rows[0].cells.length;
200200

201+
newTable.rows.forEach(row => {
202+
row.cells.forEach(cell => {
203+
cell.spanAbove = false;
204+
});
205+
});
206+
201207
if (extraColsNeeded > 0) {
202208
const currentColCount = table.rows[0].cells.length;
203209
for (let col = 0; col < extraColsNeeded; col++) {

packages/roosterjs-content-model-dom/test/modelApi/editing/mergeModelTest.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6478,5 +6478,79 @@ describe('mergeModel', () => {
64786478
expect(normalizeTable.normalizeTable).toHaveBeenCalledTimes(1);
64796479
});
64806480

6481+
it('table to table, merge source table with spanAbove cells - should reset spanAbove on merged cells', () => {
6482+
const majorModel = createContentModelDocument();
6483+
const sourceModel = createContentModelDocument();
6484+
6485+
// Create a simple 2x2 target table with selection in cell [0,0]
6486+
const para1 = createParagraph();
6487+
const text1 = createText('test1');
6488+
const cell00 = createTableCell(false, false, false, { backgroundColor: '00' });
6489+
const cell01 = createTableCell(false, false, false, { backgroundColor: '01' });
6490+
const cell10 = createTableCell(false, false, false, { backgroundColor: '10' });
6491+
const cell11 = createTableCell(false, false, false, { backgroundColor: '11' });
6492+
const table1 = createTable(2);
6493+
6494+
para1.segments.push(text1);
6495+
text1.isSelected = true;
6496+
cell00.blocks.push(para1);
6497+
table1.rows = [
6498+
{ format: {}, height: 0, cells: [cell00, cell01] },
6499+
{ format: {}, height: 0, cells: [cell10, cell11] },
6500+
];
6501+
6502+
majorModel.blocks.push(table1);
6503+
6504+
// Source table is 2x2 where the second row cells are vertically merged (spanAbove)
6505+
const newPara1 = createParagraph();
6506+
const newText1 = createText('newText1');
6507+
const newCell00 = createTableCell(false, false, false, { backgroundColor: 'n00' });
6508+
const newCell01 = createTableCell(false, false, false, { backgroundColor: 'n01' });
6509+
const newCell10 = createTableCell(false, true, false, { backgroundColor: 'n10' }); // spanAbove
6510+
const newCell11 = createTableCell(false, true, false, { backgroundColor: 'n11' }); // spanAbove
6511+
const newTable1 = createTable(2);
6512+
6513+
newPara1.segments.push(newText1);
6514+
newCell00.blocks.push(newPara1);
6515+
newTable1.rows = [
6516+
{ format: {}, height: 0, cells: [newCell00, newCell01] },
6517+
{ format: {}, height: 0, cells: [newCell10, newCell11] },
6518+
];
6519+
6520+
sourceModel.blocks.push(newTable1);
6521+
6522+
spyOn(applyTableFormat, 'applyTableFormat');
6523+
spyOn(normalizeTable, 'normalizeTable');
6524+
6525+
mergeModel(
6526+
majorModel,
6527+
sourceModel,
6528+
{ newEntities: [], deletedEntities: [], newImages: [] },
6529+
{
6530+
mergeTable: true,
6531+
}
6532+
);
6533+
6534+
const table = majorModel.blocks[0] as ContentModelTable;
6535+
6536+
// Target stays 2x2, all source cells are placed one-to-one
6537+
expect(table.rows.length).toEqual(2);
6538+
expect(table.rows[0].cells.length).toEqual(2);
6539+
6540+
expect(table.rows[0].cells[0]).toBe(newCell00);
6541+
expect(table.rows[0].cells[1]).toBe(newCell01);
6542+
expect(table.rows[1].cells[0]).toBe(newCell10);
6543+
expect(table.rows[1].cells[1]).toBe(newCell11);
6544+
6545+
// The spanAbove flag from the source table must be reset so the merged cells
6546+
// become standalone cells instead of continuations of a vertical merge
6547+
expect(newCell10.spanAbove).toBe(false);
6548+
expect(newCell11.spanAbove).toBe(false);
6549+
expect(newCell00.spanAbove).toBe(false);
6550+
expect(newCell01.spanAbove).toBe(false);
6551+
6552+
expect(normalizeTable.normalizeTable).toHaveBeenCalledTimes(1);
6553+
});
6554+
64816555
// #endregion
64826556
});

0 commit comments

Comments
 (0)