Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guide/headers.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ There are a few properties on `header` objects that are only useful if the heade
- `isPlaceholder`: A boolean flag that is true if the header is a placeholder header. Placeholder headers fill the rows above a shallow leaf column's real header so that every header row accounts for every visible column. Render them as empty cells to keep the header grid aligned, or use `header.rowSpan` to merge each placeholder chain into one vertically spanning header cell.
- `placeholderId`: The unique identifier for the placeholder header.
- `subHeaders`: The array of sub/child headers that belong to this header. Will be empty if the header is a leaf header.
- `getLeafHeaders()`: The leaf headers nested under this header, in left-to-right order. Group and placeholder headers are excluded, so a leaf header returns just itself. Use `table.getFlatHeaders()` if you need every header, group headers included.

> [!NOTE]
> `header.index` refers to its index within the header group (row of headers), i.e. its position from left to right. It is not the same as `header.depth`, which refers to the header group "row index".
Expand Down
11 changes: 7 additions & 4 deletions packages/table-core/src/core/headers/coreHeadersFeature.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,21 @@ function collectLeafHeaders<
header: Header_Header<TFeatures, TData, TValue>,
leafHeaders: Array<Header<TFeatures, TData, TValue>>,
): void {
if (!header.subHeaders.length) {
leafHeaders.push(header as Header<TFeatures, TData, TValue>)
return
}

for (let i = 0; i < header.subHeaders.length; i++) {
collectLeafHeaders(header.subHeaders[i]!, leafHeaders)
}

leafHeaders.push(header as Header<TFeatures, TData, TValue>)
}

/**
* Walks a header tree and collects all descendant leaf headers.
*
* The header itself is included after its descendants, matching the recursive
* shape used by nested header groups.
* Parent/group headers are skipped, so the result is one header per leaf column
* in left-to-right order. A header that is itself a leaf returns just itself.
*
* @example
* ```ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,19 +300,15 @@ describe('header rowSpan for uneven column trees', () => {
})

describe('header_getLeafHeaders', () => {
it('should collect descendant leaf headers before the header itself', () => {
it('should collect descendant leaf headers without the group header itself', () => {
const table = makeTable()
const groupHeader = table_getHeaderGroups(table)[0]!.headers.find(
(header) => header.column.id === 'group',
)!

const leafHeaders = header_getLeafHeaders(groupHeader)

expect(leafHeaders.map((header) => header.column.id)).toEqual([
'a',
'b',
'group',
])
expect(leafHeaders.map((header) => header.column.id)).toEqual(['a', 'b'])
})

it('should return only itself for a leaf header', () => {
Expand All @@ -324,7 +320,7 @@ describe('header_getLeafHeaders', () => {
expect(header_getLeafHeaders(leafHeader)).toEqual([leafHeader])
})

it('should preserve descendant-first identity across three levels', () => {
it('should skip intermediate group headers across three levels', () => {
const deepColumns: Array<ColumnDef<typeof features, Item, any>> = [
{
id: 'outer',
Expand All @@ -346,18 +342,12 @@ describe('header_getLeafHeaders', () => {
})
const headerGroups = table_getHeaderGroups(table)
const outer = headerGroups[0]!.headers[0]!
const inner = headerGroups[1]!.headers[0]!
const [a, b] = headerGroups[2]!.headers

const leafHeaders = header_getLeafHeaders(outer)

expect(leafHeaders.map((header) => header.column.id)).toEqual([
'a',
'b',
'inner',
'outer',
])
expect(leafHeaders).toEqual([a, b, inner, outer])
expect(leafHeaders.map((header) => header.column.id)).toEqual(['a', 'b'])
expect(leafHeaders).toEqual([a, b])
})
})

Expand All @@ -379,6 +369,15 @@ describe('table_getLeafHeaders', () => {
const table = makeTable()
const ids = table_getLeafHeaders(table).map((header) => header.column.id)

expect(ids).toEqual(expect.arrayContaining(['a', 'b', 'c']))
expect(ids).toEqual(['a', 'b', 'c'])
})

it('should return the real leaf header instead of its placeholder chain', () => {
const table = makeTable()
const leafHeaders = table_getLeafHeaders(table)

expect(leafHeaders.every((header) => !header.isPlaceholder)).toBe(true)
// Real leaf headers keep the plain column id; placeholders do not.
expect(leafHeaders.map((header) => header.id)).toEqual(['a', 'b', 'c'])
})
})