fix: a styled table writes no table-level borders

Every cell already writes all four sides itself, and on a styled table
the table-level black default showed in Word, which lets table borders
beat a cell's "none" (only "nil" loses) — LibreOffice lets the cell
win, so only Word drew the grid over the style. Plain tables keep the
default grid via the per-cell fallback.
This commit is contained in:
Steffen Becker committed 2026-08-30 10:05:43 +00:00
1 parent 1e88427154
commit 3f047efca8
2 files changed
+15 -5

No files matched your search

+11 -5
View File
@@ -2039,11 +2039,13 @@ function cellForce(style: TableStyle | undefined, cell: TiptapNode, bg: unknown)
return force;
}
function docxCellBorder(attrs: Record<string, unknown> | undefined, side: BorderSide): IBorderOptions {
const noneBorder: IBorderOptions = { style: BorderStyle.NONE, size: 0, color: 'auto' };
function docxCellBorder(attrs: Record<string, unknown> | undefined, side: BorderSide, fallback: IBorderOptions = cellBorder): IBorderOptions {
const b = parseBorderAttr(attrs?.[side] as string | null);
if (b === 'none') return { style: BorderStyle.NONE, size: 0, color: 'auto' };
if (b === 'none') return noneBorder;
if (b) return { style: BorderStyle.SINGLE, size: Math.max(2, Math.round(b.widthPt * 8)), color: hexColor(b.color) ?? '000000' };
return cellBorder;
return fallback;
}
// A synthetic inline node standing for the whole content of a formula cell; the
@@ -2097,6 +2099,10 @@ function tableToDocx(node: TiptapNode, contentWidthCm: number, num: Numbering):
// w:tblHeader on the first row is what makes Word repeat it on every page.
const repeatHeader = node.attrs?.repeatHeader === true;
// Every cell writes all four sides itself, so the table level adds nothing — and on a
// styled table its black default shows in Word, whose table borders beat a cell's
// "none" (only "nil" loses). The editor's base grid still arrives via the cell fallback.
const tableBorder = tableStyle ? noneBorder : cellBorder;
const tableRows = rows.map((row, rowIndex) => {
const rh = row.attrs?.rowHeight;
let col = 0;
@@ -2158,8 +2164,8 @@ function tableToDocx(node: TiptapNode, contentWidthCm: number, num: Numbering):
layout: TableLayoutType.FIXED,
margins: { marginUnitType: WidthType.DXA, top: cmToTwip(pad[0]), right: cmToTwip(pad[1]), bottom: cmToTwip(pad[2]), left: cmToTwip(pad[3]) },
borders: {
top: cellBorder, bottom: cellBorder, left: cellBorder, right: cellBorder,
insideHorizontal: cellBorder, insideVertical: cellBorder,
top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder,
insideHorizontal: tableBorder, insideVertical: tableBorder,
},
});
}
+4
View File
@@ -153,6 +153,10 @@ describe('DOCX export → import round trip', () => {
expect(stylesXml, 'the imported style is a direct w:styles child').not.toContain('<undefined');
// The header region's white bold is presentational in the editor, baked here.
expect(xml).toContain('<w:color w:val="FFFFFF"');
// The style paints every border via the cells; a black table-level default would
// show in Word, whose table borders beat a cell's "none" (only "nil" loses).
const tblPr = /<w:tblPr>[\s\S]*?<\/w:tblPr>/.exec(xml)![0];
expect(tblPr).not.toContain('w:val="single"');
const res = importDocx(bytes).content as N;
const table = walk(res, 'table')[0];