fix(table): set min-width for cols #5435 (#5464)

This commit is contained in:
Rägnar O'ock 2024-10-30 10:31:25 +01:00 committed by GitHub
parent d6e4cafef3
commit 152390130e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 102 additions and 34 deletions

View File

@ -0,0 +1,5 @@
---
"@tiptap/extension-table": patch
---
enforce cellMinWidth even on column not resized by the user, fixes #5435

View File

@ -56,6 +56,14 @@ context('/src/Nodes/Table/React/', () => {
})
})
it('sets the minimum width on the colgroups by default (3x1)', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertTable({ cols: 3, rows: 1, withHeaderRow: false })
cy.get('.tiptap').find('col').invoke('attr', 'style').should('eq', 'min-width: 25px;')
})
})
it('generates correct markup for a table (1x1)', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
@ -63,7 +71,7 @@ context('/src/Nodes/Table/React/', () => {
const html = editor.getHTML()
expect(html).to.equal(
'<table style="min-width: 25px"><colgroup><col></colgroup><tbody><tr><td colspan="1" rowspan="1"><p></p></td></tr></tbody></table>',
'<table style="min-width: 25px"><colgroup><col style="min-width: 25px"></colgroup><tbody><tr><td colspan="1" rowspan="1"><p></p></td></tr></tbody></table>',
)
})
})
@ -75,7 +83,7 @@ context('/src/Nodes/Table/React/', () => {
const html = editor.getHTML()
expect(html).to.equal(
'<table style="min-width: 25px"><colgroup><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th></tr></tbody></table>',
'<table style="min-width: 25px"><colgroup><col style="min-width: 25px"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th></tr></tbody></table>',
)
})
})

View File

@ -56,13 +56,23 @@ context('/src/Nodes/Table/Vue/', () => {
})
})
it('sets the minimum width on the colgroups by default (3x1)', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertTable({ cols: 3, rows: 1, withHeaderRow: false })
cy.get('.tiptap').find('col').invoke('attr', 'style').should('eq', 'min-width: 25px;')
})
})
it('generates correct markup for a table (1x1)', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
const html = editor.getHTML()
expect(html).to.equal('<table style="min-width: 25px"><colgroup><col></colgroup><tbody><tr><td colspan="1" rowspan="1"><p></p></td></tr></tbody></table>')
expect(html).to.equal(
'<table style="min-width: 25px"><colgroup><col style="min-width: 25px"></colgroup><tbody><tr><td colspan="1" rowspan="1"><p></p></td></tr></tbody></table>',
)
})
})
@ -72,7 +82,9 @@ context('/src/Nodes/Table/Vue/', () => {
const html = editor.getHTML()
expect(html).to.equal('<table style="min-width: 25px"><colgroup><col></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th></tr></tbody></table>')
expect(html).to.equal(
'<table style="min-width: 25px"><colgroup><col style="min-width: 25px"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th></tr></tbody></table>',
)
})
})

View File

@ -1,25 +1,27 @@
// @ts-nocheck
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { NodeView } from '@tiptap/pm/view'
import { getColStyleDeclaration } from './utilities/colStyle.js'
export function updateColumns(
node: ProseMirrorNode,
colgroup: Element,
table: Element,
colgroup: HTMLTableColElement, // <colgroup> has the same prototype as <col>
table: HTMLTableElement,
cellMinWidth: number,
overrideCol?: number,
overrideValue?: any,
overrideValue?: number,
) {
let totalWidth = 0
let fixedWidth = true
let nextDOM = colgroup.firstChild
const row = node.firstChild
if (row !== null) {
for (let i = 0, col = 0; i < row.childCount; i += 1) {
const { colspan, colwidth } = row.child(i).attrs
for (let j = 0; j < colspan; j += 1, col += 1) {
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j]
const hasWidth = overrideCol === col ? overrideValue : (colwidth && colwidth[j]) as number | undefined
const cssWidth = hasWidth ? `${hasWidth}px` : ''
totalWidth += hasWidth || cellMinWidth
@ -29,21 +31,30 @@ export function updateColumns(
}
if (!nextDOM) {
colgroup.appendChild(document.createElement('col')).style.width = cssWidth
const colElement = document.createElement('col')
const [propertyKey, propertyValue] = getColStyleDeclaration(cellMinWidth, hasWidth)
colElement.style.setProperty(propertyKey, propertyValue)
colgroup.appendChild(colElement)
} else {
if (nextDOM.style.width !== cssWidth) {
nextDOM.style.width = cssWidth
if ((nextDOM as HTMLTableColElement).style.width !== cssWidth) {
const [propertyKey, propertyValue] = getColStyleDeclaration(cellMinWidth, hasWidth);
(nextDOM as HTMLTableColElement).style.setProperty(propertyKey, propertyValue)
}
nextDOM = nextDOM.nextSibling
}
}
}
}
while (nextDOM) {
const after = nextDOM.nextSibling
nextDOM.parentNode.removeChild(nextDOM)
nextDOM.parentNode?.removeChild(nextDOM)
nextDOM = after
}
@ -61,13 +72,13 @@ export class TableView implements NodeView {
cellMinWidth: number
dom: Element
dom: HTMLDivElement
table: Element
table: HTMLTableElement
colgroup: Element
colgroup: HTMLTableColElement
contentDOM: Element
contentDOM: HTMLTableSectionElement
constructor(node: ProseMirrorNode, cellMinWidth: number) {
this.node = node

View File

@ -0,0 +1,10 @@
export function getColStyleDeclaration(minWidth: number, width: number | undefined): [string, string] {
if (width) {
// apply the stored width unless it is below the configured minimum cell width
return ['width', `${Math.max(width, minWidth)}px`]
}
// set the minimum with on the column if it has no stored width
return ['min-width', `${minWidth}px`]
}

View File

@ -1,5 +1,13 @@
import { DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model'
import { getColStyleDeclaration } from './colStyle.js'
export type ColGroup = {
colgroup: DOMOutputSpec
tableWidth: string
tableMinWidth: string
} | Record<string, never>;
/**
* Creates a colgroup element for a table node in ProseMirror.
*
@ -9,12 +17,22 @@ import { DOMOutputSpec, Node as ProseMirrorNode } from '@tiptap/pm/model'
* @param overrideValue - (Optional) The width value to use for the overridden column.
* @returns An object containing the colgroup element, the total width of the table, and the minimum width of the table.
*/
export function createColGroup(
node: ProseMirrorNode,
cellMinWidth: number,
): ColGroup
export function createColGroup(
node: ProseMirrorNode,
cellMinWidth: number,
overrideCol: number,
overrideValue: number,
): ColGroup
export function createColGroup(
node: ProseMirrorNode,
cellMinWidth: number,
overrideCol?: number,
overrideValue?: any,
) {
overrideValue?: number,
): ColGroup {
let totalWidth = 0
let fixedWidth = true
const cols: DOMOutputSpec[] = []
@ -28,8 +46,7 @@ export function createColGroup(
const { colspan, colwidth } = row.child(i).attrs
for (let j = 0; j < colspan; j += 1, col += 1) {
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j]
const cssWidth = hasWidth ? `${hasWidth}px` : ''
const hasWidth = overrideCol === col ? overrideValue : colwidth && colwidth[j] as number | undefined
totalWidth += hasWidth || cellMinWidth
@ -37,7 +54,12 @@ export function createColGroup(
fixedWidth = false
}
cols.push(['col', cssWidth ? { style: `width: ${cssWidth}` } : {}])
const [property, value] = getColStyleDeclaration(cellMinWidth, hasWidth)
cols.push([
'col',
{ style: `${property}: ${value}` },
])
}
}