All files / roosterjs-content-model-api/lib/modelApi/table ensureFocusableParagraphForTable.ts

100% Statements 24/24
90.48% Branches 19/21
100% Functions 4/4
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 751x                             1x           9x   9x   5x 2x     5x   4x 4x       4x   4x       4x 7x   7x 7x     7x           3x     4x         9x       8x 8x   8x   8x    
import { createBr, createParagraph, mutateBlock } from 'roosterjs-content-model-dom';
import type {
    ContentModelParagraph,
    ReadonlyContentModelBlock,
    ReadonlyContentModelBlockGroup,
    ReadonlyContentModelDocument,
    ReadonlyContentModelTable,
} from 'roosterjs-content-model-types';
 
/**
 * @internal
 * After edit table, it maybe in a abnormal state, e.g. selected table cell is removed, or all rows are removed causes no place to put cursor.
 * We need to make sure table is in normal state, and there is a place to put cursor.
 * @returns a new paragraph that can but put focus in, or undefined if not needed
 */
export function ensureFocusableParagraphForTable(
    model: ReadonlyContentModelDocument,
    path: ReadonlyContentModelBlockGroup[],
    table: ReadonlyContentModelTable
): ContentModelParagraph | undefined {
    let paragraph: ContentModelParagraph | undefined;
    const firstCell = table.rows.filter(row => row.cells.length > 0)[0]?.cells[0];
 
    if (firstCell) {
        // When there is a valid cell to put focus, use it
        paragraph = firstCell.blocks.filter(
            (block): block is ContentModelParagraph => block.blockType == 'Paragraph'
        )[0];
 
        if (!paragraph) {
            // If there is not a paragraph under this cell, create one
            paragraph = createEmptyParagraph(model);
            mutateBlock(firstCell).blocks.push(paragraph);
        }
    } else {
        // No table cell at all, which means the whole table is deleted. So we need to remove it from content model.
        let block: ReadonlyContentModelBlock = table;
        let parent: ReadonlyContentModelBlockGroup | undefined;
        paragraph = createEmptyParagraph(model);
 
        // If the table is the only block of its parent and parent is a FormatContainer, remove the parent as well.
        // We need to do this in a loop in case there are multiple layer of FormatContainer that match this case
        while ((parent = path.shift())) {
            const index = parent.blocks.indexOf(block) ?? -1;
 
            Eif (parent && index >= 0) {
                mutateBlock(parent).blocks.splice(index, 1, paragraph);
            }
 
            if (
                parent.blockGroupType == 'FormatContainer' &&
                parent.blocks.length == 1 &&
                parent.blocks[0] == paragraph
            ) {
                // If the new paragraph is the only child of parent format container, unwrap parent as well
                block = parent;
            } else {
                // Otherwise, just stop here and keep processing the new paragraph
                break;
            }
        }
    }
 
    return paragraph;
}
 
function createEmptyParagraph(model: ReadonlyContentModelDocument) {
    const newPara = createParagraph(false /*isImplicit*/, undefined /*blockFormat*/, model.format);
    const br = createBr(model.format);
 
    newPara.segments.push(br);
 
    return newPara;
}