diff --git a/packages/super-editor/src/extensions/table/table.js b/packages/super-editor/src/extensions/table/table.js index a3242a8865..139418ae12 100644 --- a/packages/super-editor/src/extensions/table/table.js +++ b/packages/super-editor/src/extensions/table/table.js @@ -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 */ /** @@ -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; + 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; diff --git a/packages/super-editor/src/extensions/table/tableHelpers/createCell.js b/packages/super-editor/src/extensions/table/tableHelpers/createCell.js index 81a4dce9cc..a7613f5197 100644 --- a/packages/super-editor/src/extensions/table/tableHelpers/createCell.js +++ b/packages/super-editor/src/extensions/table/tableHelpers/createCell.js @@ -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(); }; diff --git a/packages/super-editor/src/extensions/table/tableHelpers/createTable.js b/packages/super-editor/src/extensions/table/tableHelpers/createTable.js index 749c9f38fe..783522b3e2 100644 --- a/packages/super-editor/src/extensions/table/tableHelpers/createTable.js +++ b/packages/super-editor/src/extensions/table/tableHelpers/createTable.js @@ -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) => { const types = { table: getNodeType('table', schema), tableRow: getNodeType('tableRow', schema), @@ -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; + 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); } diff --git a/packages/super-editor/src/extensions/table/tableHelpers/tableHelpers.test.js b/packages/super-editor/src/extensions/table/tableHelpers/tableHelpers.test.js index 04c9f5f9a6..8d729b6634 100644 --- a/packages/super-editor/src/extensions/table/tableHelpers/tableHelpers.test.js +++ b/packages/super-editor/src/extensions/table/tableHelpers/tableHelpers.test.js @@ -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; @@ -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' }); diff --git a/tests/behavior/tests/tables/insert-table-column-widths.spec.ts b/tests/behavior/tests/tables/insert-table-column-widths.spec.ts new file mode 100644 index 0000000000..0f25963a42 --- /dev/null +++ b/tests/behavior/tests/tables/insert-table-column-widths.spec.ts @@ -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); + }); +}); diff --git a/tests/behavior/tests/tables/resize.spec.ts b/tests/behavior/tests/tables/resize.spec.ts index ea6a85af30..744b1fe129 100644 --- a/tests/behavior/tests/tables/resize.spec.ts +++ b/tests/behavior/tests/tables/resize.spec.ts @@ -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');