Files
oranguru/src/Griddy/core/columnMapper.ts
Hein e776844588 feat(core): add column pinning and grouping features to Griddy table
- Implement column pinning functionality allowing users to pin columns to the left or right.
- Introduce data grouping capabilities for better data organization.
- Enhance the theming guide with new styles for pinned columns and loading indicators.
- Add infinite scroll support with loading indicators for improved user experience.
- Update CSS styles to accommodate new features and improve visual feedback.
2026-02-14 21:18:04 +02:00

117 lines
3.5 KiB
TypeScript

import type { ColumnDef } from '@tanstack/react-table'
import type { GriddyColumn, SelectionConfig } from './types'
import { createOperatorFilter } from '../features/filtering'
import { DEFAULTS, SELECTION_COLUMN_ID, SELECTION_COLUMN_SIZE } from './constants'
/**
* Retrieves the original GriddyColumn from a TanStack column's meta.
*/
export function getGriddyColumn<T>(column: { columnDef: ColumnDef<T> }): GriddyColumn<T> | undefined {
return (column.columnDef.meta as { griddy?: GriddyColumn<T> })?.griddy
}
/**
* Converts a single GriddyColumn to a TanStack ColumnDef
*/
function mapSingleColumn<T>(col: GriddyColumn<T>): ColumnDef<T> {
const isStringAccessor = typeof col.accessor !== 'function'
const def: ColumnDef<T> = {
id: col.id,
// Use accessorKey for string keys (enables TanStack auto-detection of sort/filter),
// accessorFn for function accessors
...(isStringAccessor
? { accessorKey: col.accessor as string }
: { accessorFn: col.accessor as (row: T) => unknown }),
aggregationFn: col.aggregationFn,
enableColumnFilter: col.filterable ?? false,
enableGrouping: col.groupable ?? false,
enableHiding: true,
enablePinning: true,
enableResizing: true,
enableSorting: col.sortable ?? true,
header: () => col.header,
maxSize: col.maxWidth ?? DEFAULTS.maxColumnWidth,
meta: { griddy: col },
minSize: col.minWidth ?? DEFAULTS.minColumnWidth,
size: col.width,
}
// For function accessors, TanStack can't auto-detect the sort type, so provide a default
if (col.sortFn) {
def.sortingFn = col.sortFn
} else if (!isStringAccessor && col.sortable !== false) {
// Use alphanumeric sorting for function accessors
def.sortingFn = 'alphanumeric'
}
if (col.filterFn) {
def.filterFn = col.filterFn
} else if (col.filterable) {
def.filterFn = createOperatorFilter()
}
return def
}
/**
* Maps Griddy's user-facing GriddyColumn<T> definitions to TanStack Table ColumnDef<T>[].
* Supports header grouping and optionally prepends a selection checkbox column.
*/
export function mapColumns<T>(
columns: GriddyColumn<T>[],
selection?: SelectionConfig,
): ColumnDef<T>[] {
// Group columns by headerGroup
const grouped = new Map<string, GriddyColumn<T>[]>()
const ungrouped: GriddyColumn<T>[] = []
columns.forEach(col => {
if (col.headerGroup) {
const existing = grouped.get(col.headerGroup) || []
existing.push(col)
grouped.set(col.headerGroup, existing)
} else {
ungrouped.push(col)
}
})
// Build column definitions
const mapped: ColumnDef<T>[] = []
// Add ungrouped columns first
ungrouped.forEach(col => {
mapped.push(mapSingleColumn(col))
})
// Add grouped columns
grouped.forEach((groupColumns, groupName) => {
const groupDef: ColumnDef<T> = {
header: groupName,
id: `group-${groupName}`,
columns: groupColumns.map(col => mapSingleColumn(col)),
}
mapped.push(groupDef)
})
// Prepend checkbox column if selection is enabled
if (selection && selection.mode !== 'none' && selection.showCheckbox !== false) {
const checkboxCol: ColumnDef<T> = {
cell: 'select-row', // Rendered by TableCell with actual checkbox
enableColumnFilter: false,
enableHiding: false,
enableResizing: false,
enableSorting: false,
header: selection.mode === 'multi'
? 'select-all' // Rendered by TableHeader with actual checkbox
: '',
id: SELECTION_COLUMN_ID,
size: SELECTION_COLUMN_SIZE,
}
mapped.unshift(checkboxCol)
}
return mapped
}