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)', () => { it('generates correct markup for a table (1x1)', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false }) editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
@ -63,7 +71,7 @@ context('/src/Nodes/Table/React/', () => {
const html = editor.getHTML() const html = editor.getHTML()
expect(html).to.equal( 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() const html = editor.getHTML()
expect(html).to.equal( 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)', () => { it('generates correct markup for a table (1x1)', () => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false }) editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
const html = editor.getHTML() 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() 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,41 +1,52 @@
// @ts-nocheck
import { Node as ProseMirrorNode } from '@tiptap/pm/model' import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { NodeView } from '@tiptap/pm/view' import { NodeView } from '@tiptap/pm/view'
import { getColStyleDeclaration } from './utilities/colStyle.js'
export function updateColumns( export function updateColumns(
node: ProseMirrorNode, node: ProseMirrorNode,
colgroup: Element, colgroup: HTMLTableColElement, // <colgroup> has the same prototype as <col>
table: Element, table: HTMLTableElement,
cellMinWidth: number, cellMinWidth: number,
overrideCol?: number, overrideCol?: number,
overrideValue?: any, overrideValue?: number,
) { ) {
let totalWidth = 0 let totalWidth = 0
let fixedWidth = true let fixedWidth = true
let nextDOM = colgroup.firstChild let nextDOM = colgroup.firstChild
const row = node.firstChild const row = node.firstChild
for (let i = 0, col = 0; i < row.childCount; i += 1) { if (row !== null) {
const { colspan, colwidth } = row.child(i).attrs 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) { 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` : '' const cssWidth = hasWidth ? `${hasWidth}px` : ''
totalWidth += hasWidth || cellMinWidth totalWidth += hasWidth || cellMinWidth
if (!hasWidth) { if (!hasWidth) {
fixedWidth = false fixedWidth = false
}
if (!nextDOM) {
colgroup.appendChild(document.createElement('col')).style.width = cssWidth
} else {
if (nextDOM.style.width !== cssWidth) {
nextDOM.style.width = cssWidth
} }
nextDOM = nextDOM.nextSibling if (!nextDOM) {
const colElement = document.createElement('col')
const [propertyKey, propertyValue] = getColStyleDeclaration(cellMinWidth, hasWidth)
colElement.style.setProperty(propertyKey, propertyValue)
colgroup.appendChild(colElement)
} else {
if ((nextDOM as HTMLTableColElement).style.width !== cssWidth) {
const [propertyKey, propertyValue] = getColStyleDeclaration(cellMinWidth, hasWidth);
(nextDOM as HTMLTableColElement).style.setProperty(propertyKey, propertyValue)
}
nextDOM = nextDOM.nextSibling
}
} }
} }
} }
@ -43,7 +54,7 @@ export function updateColumns(
while (nextDOM) { while (nextDOM) {
const after = nextDOM.nextSibling const after = nextDOM.nextSibling
nextDOM.parentNode.removeChild(nextDOM) nextDOM.parentNode?.removeChild(nextDOM)
nextDOM = after nextDOM = after
} }
@ -61,13 +72,13 @@ export class TableView implements NodeView {
cellMinWidth: number cellMinWidth: number
dom: Element dom: HTMLDivElement
table: Element table: HTMLTableElement
colgroup: Element colgroup: HTMLTableColElement
contentDOM: Element contentDOM: HTMLTableSectionElement
constructor(node: ProseMirrorNode, cellMinWidth: number) { constructor(node: ProseMirrorNode, cellMinWidth: number) {
this.node = node 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 { 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. * 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. * @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. * @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( export function createColGroup(
node: ProseMirrorNode, node: ProseMirrorNode,
cellMinWidth: number, cellMinWidth: number,
overrideCol?: number, overrideCol?: number,
overrideValue?: any, overrideValue?: number,
) { ): ColGroup {
let totalWidth = 0 let totalWidth = 0
let fixedWidth = true let fixedWidth = true
const cols: DOMOutputSpec[] = [] const cols: DOMOutputSpec[] = []
@ -28,8 +46,7 @@ export function createColGroup(
const { colspan, colwidth } = row.child(i).attrs const { colspan, colwidth } = row.child(i).attrs
for (let j = 0; j < colspan; j += 1, col += 1) { 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 totalWidth += hasWidth || cellMinWidth
@ -37,7 +54,12 @@ export function createColGroup(
fixedWidth = false fixedWidth = false
} }
cols.push(['col', cssWidth ? { style: `width: ${cssWidth}` } : {}]) const [property, value] = getColStyleDeclaration(cellMinWidth, hasWidth)
cols.push([
'col',
{ style: `${property}: ${value}` },
])
} }
} }