import type { VirtualItem } from '@tanstack/react-virtual' import type { CellColSpanParams, ColumnField, GridRowData, GridRowMeta, } from '../types' export type ColSpanConfig = Map< string, { id: string as?: string positionColumnIds?: string[] skip?: boolean } > export const getColumnSpanByRowId = ({ row, rowMeta, columnIds, stickyColumnIds, colSpanFunctions, }: { row: GridRowData rowMeta: GridRowMeta columnIds: string[] stickyColumnIds: { left: Set; right: Set } colSpanFunctions: { sourceColumn: string colSpan: | ColumnField[] | (( props: CellColSpanParams ) => ColumnField[]) }[] }) => { const colSpanConfig: ColSpanConfig = new Map() const spanIndexes: { sourceColumn: string minIndex: number maxIndex: number }[] = [] for (const { sourceColumn, colSpan } of colSpanFunctions) { let result = Array.isArray(colSpan) ? [...colSpan] : colSpan({ row, rowMeta, columnId: sourceColumn, sortedColumnIds: columnIds, }) /* If the source is sticky, all targets must be sticky too */ if (stickyColumnIds.left.has(sourceColumn)) { result = result.filter((colId) => stickyColumnIds.left.has(colId)) } else if (stickyColumnIds.right.has(sourceColumn)) { result = result.filter((colId) => stickyColumnIds.right.has(colId)) } else if ( stickyColumnIds.left.size > 0 || stickyColumnIds.right.size > 0 ) { result = result.filter( (colId) => !stickyColumnIds.left.has(colId) && !stickyColumnIds.right.has(colId) ) } if ( result.length === 0 || (result.length === 1 && result[0] === sourceColumn) ) { /* No need to map, must contain at least 2 columns */ continue } /* If the developer forgot to include the source column */ if (!result.includes(sourceColumn)) { result.push(sourceColumn) } let minIndex = columnIds.findIndex((colId) => result.includes(colId)) let maxIndex = columnIds.findLastIndex((colId) => result.includes(colId) ) if (minIndex === -1 || maxIndex === -1) { continue } for (const existing of spanIndexes) { if ( minIndex >= existing.minIndex && minIndex <= existing.maxIndex ) { minIndex = existing.maxIndex + 1 } if ( maxIndex >= existing.minIndex && maxIndex <= existing.maxIndex ) { maxIndex = existing.minIndex - 1 } } if (minIndex > maxIndex) { continue } spanIndexes.push({ sourceColumn, minIndex, maxIndex }) const firstId = columnIds[minIndex] colSpanConfig.set(firstId, { id: firstId, as: sourceColumn === firstId ? undefined : sourceColumn, positionColumnIds: columnIds.slice(minIndex, maxIndex + 1), }) for (let i = minIndex + 1; i <= maxIndex; i++) { const colId = columnIds[i] colSpanConfig.set(colId, { id: colId, skip: true, }) } } if (colSpanConfig.size !== 0) { return colSpanConfig } return null } export const healVirtualColumnsForRow = ({ virtualColumns, colSpanConfig, columnIds, columnWidths, }: { virtualColumns: VirtualItem[] colSpanConfig: ColSpanConfig columnIds: string[] columnWidths: number[] }) => { if (!colSpanConfig.size) { return virtualColumns } const widthsByColumnId = columnIds.reduce((acc, id, index) => { acc.set(id, columnWidths[index]) return acc }, new Map()) const sourceCells = [...colSpanConfig.values()].filter( (v) => !!v.positionColumnIds?.length ) const adjustedVirtualColumns = [...virtualColumns] for (const element of sourceCells) { const virtualColumnsAsIds = adjustedVirtualColumns.map( (v) => columnIds[v.index] ) const positionColumnIds = element.positionColumnIds if (!positionColumnIds?.length) { // For TS, this list is already filtered continue } const minIndex = virtualColumnsAsIds.findIndex((vc) => positionColumnIds.includes(vc) ) const maxIndex = virtualColumnsAsIds.findLastIndex((vc) => positionColumnIds.includes(vc) ) if (minIndex === -1 || maxIndex === -1) { continue } const id = virtualColumnsAsIds[minIndex] const index = positionColumnIds.indexOf(id) if (index === -1) { continue } const idsForWidth = positionColumnIds.slice(0, index) const addedWidth = idsForWidth.reduce((acc, id) => { const width = widthsByColumnId.get(id) acc += width ?? 0 return acc }, 0) // const replacedWidth = adjustedVirtualColumns.slice(minIndex, maxIndex + 1 ).reduce(( acc, item ) => acc + item.size, 0 ) const totalWidth = positionColumnIds.reduce((acc, id) => { const width = widthsByColumnId.get(id) acc += width ?? 0 return acc }, 0) const newIndexAndKey = columnIds.indexOf(element.id) const newStart = adjustedVirtualColumns[minIndex].start - addedWidth adjustedVirtualColumns.splice(minIndex, maxIndex - minIndex + 1, { index: newIndexAndKey, key: newIndexAndKey, start: newStart, size: totalWidth, end: newStart + totalWidth, lane: 0, }) } return adjustedVirtualColumns.filter((avc) => { const columnId = columnIds[avc.index] const config = colSpanConfig?.get(columnId) return !config?.skip }) }