/** * Where each cell lands when a tablet/mobile breakpoint stacks the table, * shared by the editor's real block tree and its read-only preview so both * stack the way the frontend does (see frontend/responsive.ts). */ export interface StackedCell { cell: T; /** * A copy of the first column's cell, which "Repeat first column" puts at * the start of every stack after the first one. */ repeat: boolean; } /** * @param rows The table's rows. `null` marks a position no cell * starts at (covered by a span, or missing). * @param stackCount How many cells a stacked row holds. * @param transpose Stack the columns instead of the rows. * @param repeatFirstCol Start every stack with the first column's cell. */ export function buildStackRows( rows: Array>, stackCount: number, transpose: boolean, repeatFirstCol: boolean ): Array>> { if (rows.length === 0) { return []; } const maxCols = rows.reduce((max, row) => Math.max(max, row.length), 0); if (maxCols === 0) { return []; } const matrix: Array> = rows.map(row => { const padded = [...row]; while (padded.length < maxCols) { padded.push(null); } return padded; }); const sourceRows: Array> = transpose ? Array.from({ length: maxCols }, (_, row) => matrix.map(column => column[row] ?? null) ) : matrix; const sourceCols = sourceRows[0]?.length || 0; if (sourceCols === 0) { return []; } const effectiveMax = repeatFirstCol ? Math.max(2, stackCount) : Math.max(1, stackCount); const toStacked = (cell: T): StackedCell => ({ cell, repeat: false }); const outputRows: Array>> = []; if (!repeatFirstCol) { const groups = Math.ceil(sourceCols / effectiveMax); for (let group = 0; group < groups; group++) { const start = group * effectiveMax; const end = Math.min(start + effectiveMax, sourceCols); for (let row = 0; row < sourceRows.length; row++) { const cells = sourceRows[row] .slice(start, end) .filter((cell): cell is T => cell !== null) .map(toStacked); if (cells.length > 0) { outputRows.push(cells); } } } return outputRows; } const dataChunkSize = Math.max(1, effectiveMax - 1); const groups = sourceCols <= 1 ? 1 : Math.ceil((sourceCols - 1) / dataChunkSize); for (let group = 0; group < groups; group++) { const start = 1 + group * dataChunkSize; const end = Math.min(start + dataChunkSize, sourceCols); for (let row = 0; row < sourceRows.length; row++) { const firstCell = sourceRows[row][0]; const rowCells: Array> = []; if (firstCell !== null) { rowCells.push({ cell: firstCell, repeat: group > 0 }); } rowCells.push( ...sourceRows[row] .slice(start, end) .filter((cell): cell is T => cell !== null) .map(toStacked) ); if (rowCells.length > 0) { outputRows.push(rowCells); } } } return outputRows; }