GridTable: support partial colspan (2-of-4 columns)
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 39s
CI / test-go-edu-search (push) Successful in 40s
CI / test-python-klausur (push) Failing after 2m16s
CI / test-python-agent-core (push) Successful in 28s
CI / test-nodejs-website (push) Successful in 31s

Previously GridTable only supported full-row spanning (one cell across
all columns). Now renders each spanning_header cell with its actual
colspan, positioned at the correct grid column. This allows rows like
"In Britain..." (colspan=2) + "In Germany..." (colspan=2) to render
side by side instead of only showing the first cell.

Also fix box row fields: is_header always set (was undefined for
flowing/bullet_list), y_min_px/y_max_px for header_only rows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-13 12:47:14 +02:00
parent 7b3e8c576d
commit 709e41e050

View File

@@ -410,34 +410,40 @@ export function GridTable({
{/* Cells — spanning header or normal columns */}
{isSpanning ? (
<div
className="border-b border-r border-gray-200 dark:border-gray-700 bg-blue-50/50 dark:bg-blue-900/10 flex items-center"
style={{
gridColumn: `2 / ${numCols + 2}`,
height: `${rowH}px`,
}}
>
{(() => {
const spanCell = zone.cells.find(
(c) => c.row_index === row.index && c.col_type === 'spanning_header',
)
if (!spanCell) return null
const cellId = spanCell.cell_id
const isSelected = selectedCell === cellId
const cellColor = getCellColor(spanCell)
return (
<div className="flex items-center w-full">
{cellColor && (
<span
className="flex-shrink-0 w-1.5 self-stretch rounded-l-sm"
style={{ backgroundColor: cellColor }}
/>
)}
<input
id={`cell-${cellId}`}
type="text"
value={spanCell.text}
onChange={(e) => onCellTextChange(cellId, e.target.value)}
<>
{/* Render each spanning cell with its colspan */}
{zone.cells
.filter((c) => c.row_index === row.index && c.col_type === 'spanning_header')
.sort((a, b) => a.col_index - b.col_index)
.map((spanCell) => {
const colspan = spanCell.colspan || numCols
const cellId = spanCell.cell_id
const isSelected = selectedCell === cellId
const cellColor = getCellColor(spanCell)
// Grid column: starts at col_index+2 (1-based, +1 for row number column)
const gridColStart = spanCell.col_index + 2
const gridColEnd = gridColStart + colspan
return (
<div
key={cellId}
className="border-b border-r border-gray-200 dark:border-gray-700 bg-blue-50/50 dark:bg-blue-900/10 flex items-center"
style={{
gridColumn: `${gridColStart} / ${gridColEnd}`,
height: `${rowH}px`,
}}
>
<div className="flex items-center w-full">
{cellColor && (
<span
className="flex-shrink-0 w-1.5 self-stretch rounded-l-sm"
style={{ backgroundColor: cellColor }}
/>
)}
<input
id={`cell-${cellId}`}
type="text"
value={spanCell.text}
onChange={(e) => onCellTextChange(cellId, e.target.value)}
onFocus={() => onSelectCell(cellId)}
onKeyDown={(e) => handleKeyDown(e, cellId)}
className={`w-full px-3 py-1 bg-transparent border-0 outline-none text-center ${
@@ -447,9 +453,10 @@ export function GridTable({
spellCheck={false}
/>
</div>
)
})()}
</div>
</div>
)
})}
</>
) : (
zone.columns.map((col) => {
const cell = cellMap.get(`${row.index}_${col.index}`)