All files / roosterjs-content-model-dom/lib/modelToDom/handlers handleTable.ts

100% Statements 83/83
98.8% Branches 82/83
100% Functions 3/3
100% Lines 79/79

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 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 1711x 1x 1x 1x 1x 1x                     1x             130x   2x     128x   128x 9x   9x   119x   119x 89x     119x 119x   119x 119x 119x 119x     128x   128x 128x   128x 255x   255x   2x     253x 253x 253x   253x 234x 199x     234x     253x   580x 580x   580x 12x                 12x 12x 12x     12x     580x 569x   569x   569x   569x 569x 569x 569x   569x 4x   569x 6x     569x 4x     569x 6x     569x 523x 426x     523x 425x       569x 569x 522x 472x     522x 522x 522x           522x     569x       569x         569x         128x   128x    
import { applyFormat } from '../utils/applyFormat';
import { hasMetadata } from '../../modelApi/metadata/updateMetadata';
import { isBlockEmpty } from '../../modelApi/common/isEmpty';
import { moveChildNodes } from '../../domUtils/moveChildNodes';
import { reuseCachedElement } from '../../domUtils/reuseCachedElement';
import { stackFormat } from '../utils/stackFormat';
import type {
    ContentModelBlockHandler,
    ContentModelTable,
    ModelToDomContext,
    TableSelection,
} from 'roosterjs-content-model-types';
 
/**
 * @internal
 */
export const handleTable: ContentModelBlockHandler<ContentModelTable> = (
    doc: Document,
    parent: Node,
    table: ContentModelTable,
    context: ModelToDomContext,
    refNode: Node | null
) => {
    if (isBlockEmpty(table)) {
        // Empty table, do not create TABLE element and just return
        return refNode;
    }
 
    let tableNode = context.allowCacheElement ? table.cachedElement : undefined;
 
    if (tableNode) {
        refNode = reuseCachedElement(parent, tableNode, refNode, context.rewriteFromModel);
 
        moveChildNodes(tableNode);
    } else {
        tableNode = doc.createElement('table');
 
        if (context.allowCacheElement) {
            table.cachedElement = tableNode;
        }
 
        parent.insertBefore(tableNode, refNode);
        context.rewriteFromModel.addedBlockElements.push(tableNode);
 
        applyFormat(tableNode, context.formatAppliers.block, table.format, context);
        applyFormat(tableNode, context.formatAppliers.table, table.format, context);
        applyFormat(tableNode, context.formatAppliers.tableBorder, table.format, context);
        applyFormat(tableNode, context.formatAppliers.dataset, table.dataset, context);
    }
 
    context.onNodeCreated?.(table, tableNode);
 
    const tbody = doc.createElement('tbody');
    tableNode.appendChild(tbody);
 
    for (let row = 0; row < table.rows.length; row++) {
        const tableRow = table.rows[row];
 
        if (tableRow.cells.length == 0) {
            // Skip empty row
            continue;
        }
 
        const tr = (context.allowCacheElement && tableRow.cachedElement) || doc.createElement('tr');
        tbody.appendChild(tr);
        moveChildNodes(tr);
 
        if (!tableRow.cachedElement) {
            if (context.allowCacheElement) {
                tableRow.cachedElement = tr;
            }
 
            applyFormat(tr, context.formatAppliers.tableRow, tableRow.format, context);
        }
 
        context.onNodeCreated?.(tableRow, tr);
 
        for (let col = 0; col < tableRow.cells.length; col++) {
            const cell = tableRow.cells[col];
 
            if (cell.isSelected) {
                const tableSelection: TableSelection = context.tableSelection || {
                    type: 'table',
                    table: tableNode,
                    firstColumn: col,
                    lastColumn: col,
                    firstRow: row,
                    lastRow: row,
                };
 
                Eif (tableSelection.table == tableNode) {
                    tableSelection.lastColumn = Math.max(tableSelection.lastColumn, col);
                    tableSelection.lastRow = Math.max(tableSelection.lastRow, row);
                }
 
                context.tableSelection = tableSelection;
            }
 
            if (!cell.spanAbove && !cell.spanLeft) {
                const tag = cell.isHeader ? 'th' : 'td';
                const td =
                    (context.allowCacheElement && cell.cachedElement) || doc.createElement(tag);
 
                tr.appendChild(td);
 
                let rowSpan = 1;
                let colSpan = 1;
                let width = table.widths[col];
                let height = tableRow.height;
 
                for (; table.rows[row + rowSpan]?.cells[col]?.spanAbove; rowSpan++) {
                    height += table.rows[row + rowSpan].height;
                }
                for (; tableRow.cells[col + colSpan]?.spanLeft; colSpan++) {
                    width += table.widths[col + colSpan];
                }
 
                if (rowSpan > 1) {
                    td.rowSpan = rowSpan;
                }
 
                if (colSpan > 1) {
                    td.colSpan = colSpan;
                }
 
                if (!cell.cachedElement || (cell.format.useBorderBox && hasMetadata(table))) {
                    if (width > 0 && !td.style.width) {
                        td.style.width = width + 'px';
                    }
 
                    if (height > 0 && !td.style.height) {
                        td.style.height = height + 'px';
                    }
                }
 
                stackFormat(context, tag, () => {
                    if (!cell.cachedElement) {
                        if (context.allowCacheElement) {
                            cell.cachedElement = td;
                        }
 
                        applyFormat(td, context.formatAppliers.block, cell.format, context);
                        applyFormat(td, context.formatAppliers.tableCell, cell.format, context);
                        applyFormat(
                            td,
                            context.formatAppliers.tableCellBorder,
                            cell.format,
                            context
                        );
                        applyFormat(td, context.formatAppliers.dataset, cell.dataset, context);
                    }
 
                    stackFormat(
                        context,
                        cell.format.direction ? { direction: cell.format.direction } : null,
                        () => {
                            context.modelHandlers.blockGroupChildren(doc, td, cell, context);
                        }
                    );
                });
 
                context.onNodeCreated?.(cell, td);
            }
        }
    }
 
    context.domIndexer?.onTable(tableNode, table);
 
    return refNode;
};