Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions packages/super-editor/src/extensions/table/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ const isImportedTableElement = (element) => Boolean(element?.closest?.(IMPORT_CO
* @property {number} [rows=3] - Number of rows to create
* @property {number} [cols=3] - Number of columns to create
* @property {boolean} [withHeaderRow=false] - Create first row as header row
* @property {number[]} [columnWidths] - Explicit column widths in pixels
*/

/**
Expand Down Expand Up @@ -554,11 +555,28 @@ export const Table = Node.create({
* @example
* editor.commands.insertTable()
* editor.commands.insertTable({ rows: 3, cols: 3, withHeaderRow: true })
* editor.commands.insertTable({ rows: 3, cols: 3, columnWidths: [200, 100, 200] })
*/
insertTable:
({ rows = 3, cols = 3, withHeaderRow = false } = {}) =>
({ rows = 3, cols = 3, withHeaderRow = false, columnWidths = null } = {}) =>
({ tr, dispatch, editor }) => {
const node = createTable(editor.schema, rows, cols, withHeaderRow);
let widths = columnWidths;

// If no widths provided, auto-calculate to fill available page width
if (!widths) {
const { pageSize = {}, pageMargins = {} } = editor.converter?.pageStyles ?? {};
const { width: pageWidth } = pageSize;
const { left = 0, right = 0 } = pageMargins;

if (pageWidth) {
// Page dimensions are in inches, convert to pixels (96 PPI)
const availableWidth = (pageWidth - left - right) * 96;
Comment thread
harbournick marked this conversation as resolved.
const columnWidth = Math.floor(availableWidth / cols);
widths = Array(cols).fill(columnWidth);
}
}

const node = createTable(editor.schema, rows, cols, withHeaderRow, null, widths);

if (dispatch) {
let offset = tr.selection.$from.end() + 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// @ts-check
export const createCell = (cellType, cellContent = null) => {
export const createCell = (cellType, cellContent = null, attrs = null) => {
if (cellContent) {
return cellType.createChecked(null, cellContent);
return cellType.createChecked(attrs, cellContent);
}
if (attrs) {
return cellType.createAndFill(attrs);
}
return cellType.createAndFill();
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import { createTableBorders } from './createTableBorders.js';
* @param {number} colsCount - Number of columns
* @param {boolean} withHeaderRow - Create first row as header
* @param {Object} [cellContent=null] - Initial cell content
* @param {number[]} [columnWidths=null] - Array of pixel widths per column
* @returns {Object} Complete table node with borders
* @example
* const table = createTable(schema, 3, 3, true)
* @example
* const table = createTable(schema, 2, 4, false, paragraphNode)
* @example
* const table = createTable(schema, 3, 3, false, null, [200, 100, 200])
*/
export const createTable = (schema, rowsCount, colsCount, withHeaderRow, cellContent = null) => {
export const createTable = (schema, rowsCount, colsCount, withHeaderRow, cellContent = null, columnWidths = null) => {
Comment thread
harbournick marked this conversation as resolved.
const types = {
table: getNodeType('table', schema),
tableRow: getNodeType('tableRow', schema),
Expand All @@ -30,10 +33,11 @@ export const createTable = (schema, rowsCount, colsCount, withHeaderRow, cellCon
const cells = [];

for (let index = 0; index < colsCount; index++) {
const cell = createCell(types.tableCell, cellContent);
const cellAttrs = columnWidths ? { colwidth: [columnWidths[index]] } : null;
Comment thread
harbournick marked this conversation as resolved.
const cell = createCell(types.tableCell, cellContent, cellAttrs);
if (cell) cells.push(cell);
if (withHeaderRow) {
const headerCell = createCell(types.tableHeader, cellContent);
const headerCell = createCell(types.tableHeader, cellContent, cellAttrs);
if (headerCell) {
headerCells.push(headerCell);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,20 @@ describe('tableHelpers', () => {
expect(filledCell.content.firstChild.textContent).toBe('Hello');
});

it('createCell accepts attrs parameter for setting cell attributes', () => {
const cellType = schema.nodes.tableCell;

const cellWithWidth = createCell(cellType, null, { colwidth: [200] });
expect(cellWithWidth.type.name).toBe('tableCell');
expect(cellWithWidth.attrs.colwidth).toEqual([200]);

const cellWithContent = createCell(cellType, schema.nodes.paragraph.create(null, schema.text('Test')), {
colwidth: [150],
});
expect(cellWithContent.attrs.colwidth).toEqual([150]);
expect(cellWithContent.content.firstChild.textContent).toBe('Test');
});

const buildRowTable = (widths, overrideCol, overrideValue) => {
const cellType = schema.nodes.tableCell;
const rowType = schema.nodes.tableRow;
Expand Down Expand Up @@ -166,6 +180,52 @@ describe('tableHelpers', () => {
expect(headerCell.type.name).toBe('tableHeader');
});

it('createTable applies column widths when provided', () => {
const columnWidths = [200, 100, 200];
const table = createTable(schema, 2, 3, false, null, columnWidths);

expect(table.type.name).toBe('table');
expect(table.childCount).toBe(2); // 2 rows

// Check first row cells have correct widths
const firstRow = table.firstChild;
expect(firstRow.childCount).toBe(3);
expect(firstRow.child(0).attrs.colwidth).toEqual([200]);
expect(firstRow.child(1).attrs.colwidth).toEqual([100]);
expect(firstRow.child(2).attrs.colwidth).toEqual([200]);

// Check second row cells also have correct widths
const secondRow = table.child(1);
expect(secondRow.child(0).attrs.colwidth).toEqual([200]);
expect(secondRow.child(1).attrs.colwidth).toEqual([100]);
expect(secondRow.child(2).attrs.colwidth).toEqual([200]);
});

it('createTable applies column widths to header row when withHeaderRow is true', () => {
const columnWidths = [150, 150];
const table = createTable(schema, 2, 2, true, null, columnWidths);

// First row should be header cells with widths
const headerRow = table.firstChild;
expect(headerRow.child(0).type.name).toBe('tableHeader');
expect(headerRow.child(0).attrs.colwidth).toEqual([150]);
expect(headerRow.child(1).attrs.colwidth).toEqual([150]);

// Second row should be regular cells with widths
const bodyRow = table.child(1);
expect(bodyRow.child(0).type.name).toBe('tableCell');
expect(bodyRow.child(0).attrs.colwidth).toEqual([150]);
});

it('createTable uses default widths when columnWidths is null', () => {
const table = createTable(schema, 1, 2, false, null, null);

const firstRow = table.firstChild;
// Default colwidth from schema is [100]
expect(firstRow.child(0).attrs.colwidth).toEqual([100]);
expect(firstRow.child(1).attrs.colwidth).toEqual([100]);
});

it('createTableBorders assigns uniform border configuration', () => {
const borders = createTableBorders({ size: 2, color: '#ccc' });
expect(borders.top).toEqual({ size: 2, color: '#ccc' });
Expand Down
110 changes: 110 additions & 0 deletions tests/behavior/tests/tables/insert-table-column-widths.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { test, expect } from '../../fixtures/superdoc.js';
import type { Page } from '@playwright/test';

test.use({ config: { toolbar: 'none' } });

/** Read colwidth arrays from every cell in the first table found in the document. */
async function getCellColwidths(page: Page): Promise<(number[] | null)[]> {
return page.evaluate(() => {
const doc = (window as any).editor.state.doc;
const widths: (number[] | null)[] = [];
let inTable = false;

doc.descendants((node: any) => {
if (node.type.name === 'table') {
inTable = true;
return true; // descend into this table
}
if (inTable && (node.type.name === 'tableCell' || node.type.name === 'tableHeader')) {
widths.push(node.attrs.colwidth ?? null);
}
});

return widths;
});
}

test.describe('insertTable column widths', () => {
test('auto-calculates equal column widths from page dimensions', async ({ superdoc }) => {
await superdoc.executeCommand('insertTable', { rows: 3, cols: 3 });
await superdoc.waitForStable();

await superdoc.assertTableExists(3, 3);

const colwidths = await getCellColwidths(superdoc.page);

// Every cell should have a colwidth set (not null)
for (const cw of colwidths) {
expect(cw).not.toBeNull();
expect(cw).toHaveLength(1);
}

// All columns should have the same width (equal division of page width)
const uniqueWidths = new Set(colwidths.map((cw) => cw![0]));
expect(uniqueWidths.size).toBe(1);

// The auto-calculated width should be a reasonable page-derived value (not the 100px default)
const autoWidth = colwidths[0]![0];
expect(autoWidth).toBeGreaterThan(100);
});

test('uses explicit columnWidths when provided', async ({ superdoc }) => {
const explicitWidths = [200, 100, 200];

await superdoc.executeCommand('insertTable', {
rows: 2,
cols: 3,
columnWidths: explicitWidths,
});
await superdoc.waitForStable();

await superdoc.assertTableExists(2, 3);

const colwidths = await getCellColwidths(superdoc.page);

// 2 rows x 3 cols = 6 cells
expect(colwidths).toHaveLength(6);

// Each cell's colwidth should match the explicit width for its column
for (let row = 0; row < 2; row++) {
for (let col = 0; col < 3; col++) {
const cellIndex = row * 3 + col;
expect(colwidths[cellIndex]).toEqual([explicitWidths[col]]);
}
}
});

test('auto widths differ from explicit widths', async ({ superdoc }) => {
// Insert table with auto widths
await superdoc.executeCommand('insertTable', { rows: 2, cols: 3 });
await superdoc.waitForStable();

const autoColwidths = await getCellColwidths(superdoc.page);
const autoWidth = autoColwidths[0]![0];

// Clear the editor and insert with explicit widths
await superdoc.selectAll();
await superdoc.press('Backspace');
await superdoc.waitForStable();

await superdoc.executeCommand('insertTable', {
rows: 2,
cols: 3,
columnWidths: [200, 100, 200],
});
await superdoc.waitForStable();

const explicitColwidths = await getCellColwidths(superdoc.page);

// Auto-calculated widths should all be equal
expect(autoColwidths[0]![0]).toBe(autoColwidths[1]![0]);
expect(autoColwidths[1]![0]).toBe(autoColwidths[2]![0]);

// Explicit widths should not all be equal
expect(explicitColwidths[0]![0]).not.toBe(explicitColwidths[1]![0]);

// The auto width should differ from any of the explicit widths
expect(autoWidth).not.toBe(200);
expect(autoWidth).not.toBe(100);
});
});
3 changes: 2 additions & 1 deletion tests/behavior/tests/tables/resize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ test('resize a column by dragging its boundary', async ({ superdoc }) => {
});

test('resize the table by dragging the right edge', async ({ superdoc }) => {
await superdoc.executeCommand('insertTable', { rows: 3, cols: 3, withHeaderRow: false });
// Use narrow explicit widths so the table has room to expand rightward
await superdoc.executeCommand('insertTable', { rows: 3, cols: 3, columnWidths: [100, 100, 100] });
await superdoc.waitForStable();

await superdoc.type('Content');
Expand Down
Loading