package.src.core.headers.ts Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of table-core Show documentation
Show all versions of table-core Show documentation
Headless UI for building powerful tables & datagrids for TS/JS.
The newest version!
import { RowData, Column, Header, HeaderGroup, Table } from '../types'
import { memo } from '../utils'
import { TableFeature } from './table'
export interface CoreHeaderGroup {
id: string
depth: number
headers: Header[]
}
export interface HeaderContext {
table: Table
header: Header
column: Column
}
export interface CoreHeader {
id: string
index: number
depth: number
column: Column
headerGroup: HeaderGroup
subHeaders: Header[]
colSpan: number
rowSpan: number
getLeafHeaders: () => Header[]
isPlaceholder: boolean
placeholderId?: string
getContext: () => HeaderContext
}
export interface HeadersInstance {
getHeaderGroups: () => HeaderGroup[]
getLeftHeaderGroups: () => HeaderGroup[]
getCenterHeaderGroups: () => HeaderGroup[]
getRightHeaderGroups: () => HeaderGroup[]
getFooterGroups: () => HeaderGroup[]
getLeftFooterGroups: () => HeaderGroup[]
getCenterFooterGroups: () => HeaderGroup[]
getRightFooterGroups: () => HeaderGroup[]
getFlatHeaders: () => Header[]
getLeftFlatHeaders: () => Header[]
getCenterFlatHeaders: () => Header[]
getRightFlatHeaders: () => Header[]
getLeafHeaders: () => Header[]
getLeftLeafHeaders: () => Header[]
getCenterLeafHeaders: () => Header[]
getRightLeafHeaders: () => Header[]
}
//
function createHeader(
table: Table,
column: Column,
options: {
id?: string
isPlaceholder?: boolean
placeholderId?: string
index: number
depth: number
}
): Header {
const id = options.id ?? column.id
let header: CoreHeader = {
id,
column,
index: options.index,
isPlaceholder: !!options.isPlaceholder,
placeholderId: options.placeholderId,
depth: options.depth,
subHeaders: [],
colSpan: 0,
rowSpan: 0,
headerGroup: null!,
getLeafHeaders: (): Header[] => {
const leafHeaders: Header[] = []
const recurseHeader = (h: CoreHeader) => {
if (h.subHeaders && h.subHeaders.length) {
h.subHeaders.map(recurseHeader)
}
leafHeaders.push(h as Header)
}
recurseHeader(header)
return leafHeaders
},
getContext: () => ({
table,
header: header as Header,
column,
}),
}
table._features.forEach(feature => {
Object.assign(header, feature.createHeader?.(header, table))
})
return header as Header
}
export const Headers: TableFeature = {
createTable: (
table: Table
): HeadersInstance => {
return {
// Header Groups
getHeaderGroups: memo(
() => [
table.getAllColumns(),
table.getVisibleLeafColumns(),
table.getState().columnPinning.left,
table.getState().columnPinning.right,
],
(allColumns, leafColumns, left, right) => {
const leftColumns =
left
?.map(columnId => leafColumns.find(d => d.id === columnId)!)
.filter(Boolean) ?? []
const rightColumns =
right
?.map(columnId => leafColumns.find(d => d.id === columnId)!)
.filter(Boolean) ?? []
const centerColumns = leafColumns.filter(
column => !left?.includes(column.id) && !right?.includes(column.id)
)
const headerGroups = buildHeaderGroups(
allColumns,
[...leftColumns, ...centerColumns, ...rightColumns],
table
)
return headerGroups
},
{
key: process.env.NODE_ENV === 'development' && 'getHeaderGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getCenterHeaderGroups: memo(
() => [
table.getAllColumns(),
table.getVisibleLeafColumns(),
table.getState().columnPinning.left,
table.getState().columnPinning.right,
],
(allColumns, leafColumns, left, right) => {
leafColumns = leafColumns.filter(
column => !left?.includes(column.id) && !right?.includes(column.id)
)
return buildHeaderGroups(allColumns, leafColumns, table, 'center')
},
{
key:
process.env.NODE_ENV === 'development' && 'getCenterHeaderGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getLeftHeaderGroups: memo(
() => [
table.getAllColumns(),
table.getVisibleLeafColumns(),
table.getState().columnPinning.left,
],
(allColumns, leafColumns, left) => {
const orderedLeafColumns =
left
?.map(columnId => leafColumns.find(d => d.id === columnId)!)
.filter(Boolean) ?? []
return buildHeaderGroups(
allColumns,
orderedLeafColumns,
table,
'left'
)
},
{
key: process.env.NODE_ENV === 'development' && 'getLeftHeaderGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getRightHeaderGroups: memo(
() => [
table.getAllColumns(),
table.getVisibleLeafColumns(),
table.getState().columnPinning.right,
],
(allColumns, leafColumns, right) => {
const orderedLeafColumns =
right
?.map(columnId => leafColumns.find(d => d.id === columnId)!)
.filter(Boolean) ?? []
return buildHeaderGroups(
allColumns,
orderedLeafColumns,
table,
'right'
)
},
{
key: process.env.NODE_ENV === 'development' && 'getRightHeaderGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
// Footer Groups
getFooterGroups: memo(
() => [table.getHeaderGroups()],
headerGroups => {
return [...headerGroups].reverse()
},
{
key: process.env.NODE_ENV === 'development' && 'getFooterGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getLeftFooterGroups: memo(
() => [table.getLeftHeaderGroups()],
headerGroups => {
return [...headerGroups].reverse()
},
{
key: process.env.NODE_ENV === 'development' && 'getLeftFooterGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getCenterFooterGroups: memo(
() => [table.getCenterHeaderGroups()],
headerGroups => {
return [...headerGroups].reverse()
},
{
key:
process.env.NODE_ENV === 'development' && 'getCenterFooterGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getRightFooterGroups: memo(
() => [table.getRightHeaderGroups()],
headerGroups => {
return [...headerGroups].reverse()
},
{
key: process.env.NODE_ENV === 'development' && 'getRightFooterGroups',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
// Flat Headers
getFlatHeaders: memo(
() => [table.getHeaderGroups()],
headerGroups => {
return headerGroups
.map(headerGroup => {
return headerGroup.headers
})
.flat()
},
{
key: process.env.NODE_ENV === 'development' && 'getFlatHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getLeftFlatHeaders: memo(
() => [table.getLeftHeaderGroups()],
left => {
return left
.map(headerGroup => {
return headerGroup.headers
})
.flat()
},
{
key: process.env.NODE_ENV === 'development' && 'getLeftFlatHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getCenterFlatHeaders: memo(
() => [table.getCenterHeaderGroups()],
left => {
return left
.map(headerGroup => {
return headerGroup.headers
})
.flat()
},
{
key: process.env.NODE_ENV === 'development' && 'getCenterFlatHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getRightFlatHeaders: memo(
() => [table.getRightHeaderGroups()],
left => {
return left
.map(headerGroup => {
return headerGroup.headers
})
.flat()
},
{
key: process.env.NODE_ENV === 'development' && 'getRightFlatHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
// Leaf Headers
getCenterLeafHeaders: memo(
() => [table.getCenterFlatHeaders()],
flatHeaders => {
return flatHeaders.filter(header => !header.subHeaders?.length)
},
{
key: process.env.NODE_ENV === 'development' && 'getCenterLeafHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getLeftLeafHeaders: memo(
() => [table.getLeftFlatHeaders()],
flatHeaders => {
return flatHeaders.filter(header => !header.subHeaders?.length)
},
{
key: process.env.NODE_ENV === 'development' && 'getLeftLeafHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getRightLeafHeaders: memo(
() => [table.getRightFlatHeaders()],
flatHeaders => {
return flatHeaders.filter(header => !header.subHeaders?.length)
},
{
key: process.env.NODE_ENV === 'development' && 'getRightLeafHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
getLeafHeaders: memo(
() => [
table.getLeftHeaderGroups(),
table.getCenterHeaderGroups(),
table.getRightHeaderGroups(),
],
(left, center, right) => {
return [
...(left[0]?.headers ?? []),
...(center[0]?.headers ?? []),
...(right[0]?.headers ?? []),
]
.map(header => {
return header.getLeafHeaders()
})
.flat()
},
{
key: process.env.NODE_ENV === 'development' && 'getLeafHeaders',
debug: () => table.options.debugAll ?? table.options.debugHeaders,
}
),
}
},
}
export function buildHeaderGroups(
allColumns: Column[],
columnsToGroup: Column[],
table: Table,
headerFamily?: 'center' | 'left' | 'right'
) {
// Find the max depth of the columns:
// build the leaf column row
// build each buffer row going up
// placeholder for non-existent level
// real column for existing level
let maxDepth = 0
const findMaxDepth = (columns: Column[], depth = 1) => {
maxDepth = Math.max(maxDepth, depth)
columns
.filter(column => column.getIsVisible())
.forEach(column => {
if (column.columns?.length) {
findMaxDepth(column.columns, depth + 1)
}
}, 0)
}
findMaxDepth(allColumns)
let headerGroups: HeaderGroup[] = []
const createHeaderGroup = (
headersToGroup: Header[],
depth: number
) => {
// The header group we are creating
const headerGroup: HeaderGroup = {
depth,
id: [headerFamily, `${depth}`].filter(Boolean).join('_'),
headers: [],
}
// The parent columns we're going to scan next
const pendingParentHeaders: Header[] = []
// Scan each column for parents
headersToGroup.forEach(headerToGroup => {
// What is the latest (last) parent column?
const latestPendingParentHeader = [...pendingParentHeaders].reverse()[0]
const isLeafHeader = headerToGroup.column.depth === headerGroup.depth
let column: Column
let isPlaceholder = false
if (isLeafHeader && headerToGroup.column.parent) {
// The parent header is new
column = headerToGroup.column.parent
} else {
// The parent header is repeated
column = headerToGroup.column
isPlaceholder = true
}
if (
latestPendingParentHeader &&
latestPendingParentHeader?.column === column
) {
// This column is repeated. Add it as a sub header to the next batch
latestPendingParentHeader.subHeaders.push(headerToGroup)
} else {
// This is a new header. Let's create it
const header = createHeader(table, column, {
id: [headerFamily, depth, column.id, headerToGroup?.id]
.filter(Boolean)
.join('_'),
isPlaceholder,
placeholderId: isPlaceholder
? `${pendingParentHeaders.filter(d => d.column === column).length}`
: undefined,
depth,
index: pendingParentHeaders.length,
})
// Add the headerToGroup as a subHeader of the new header
header.subHeaders.push(headerToGroup)
// Add the new header to the pendingParentHeaders to get grouped
// in the next batch
pendingParentHeaders.push(header)
}
headerGroup.headers.push(headerToGroup)
headerToGroup.headerGroup = headerGroup
})
headerGroups.push(headerGroup)
if (depth > 0) {
createHeaderGroup(pendingParentHeaders, depth - 1)
}
}
const bottomHeaders = columnsToGroup.map((column, index) =>
createHeader(table, column, {
depth: maxDepth,
index,
})
)
createHeaderGroup(bottomHeaders, maxDepth - 1)
headerGroups.reverse()
// headerGroups = headerGroups.filter(headerGroup => {
// return !headerGroup.headers.every(header => header.isPlaceholder)
// })
const recurseHeadersForSpans = (
headers: Header[]
): { colSpan: number; rowSpan: number }[] => {
const filteredHeaders = headers.filter(header =>
header.column.getIsVisible()
)
return filteredHeaders.map(header => {
let colSpan = 0
let rowSpan = 0
let childRowSpans = [0]
if (header.subHeaders && header.subHeaders.length) {
childRowSpans = []
recurseHeadersForSpans(header.subHeaders).forEach(
({ colSpan: childColSpan, rowSpan: childRowSpan }) => {
colSpan += childColSpan
childRowSpans.push(childRowSpan)
}
)
} else {
colSpan = 1
}
const minChildRowSpan = Math.min(...childRowSpans)
rowSpan = rowSpan + minChildRowSpan
header.colSpan = colSpan
header.rowSpan = rowSpan
return { colSpan, rowSpan }
})
}
recurseHeadersForSpans(headerGroups[0]?.headers ?? [])
return headerGroups
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy