import { Component, Fragment } from 'react'; import { Body as BaseBody } from './base'; import { RowType, RowKeyType } from '../../../components/Table/base/types'; import { IRow, IRowCell, IExtraRowData, isRowExpanded } from '../../../components'; import { TableContext } from './TableContext'; export interface IComputedData { isInput: boolean; isButton: boolean; } export type OnRowClick = ( event: React.KeyboardEvent | React.MouseEvent, row: IRow, rowProps: IExtraRowData, computedData: IComputedData ) => void; export interface TableBodyProps { /** Additional classes added to the TableBody */ className?: string; /** @hide This prop should not be set manually */ children?: React.ReactNode; /** @hide This prop should not be set manually */ headerData?: IRow[]; /** @hide This prop should not be set manually */ rows?: IRow[]; /** @hide This prop should not be set manually */ rowKey?: RowKeyType; /** A click handler for the row */ onRowClick?: OnRowClick; /** @hide This prop should not be set manually */ onRow?: Function; /** Flag indicating the contains oddly striped rows. */ isOddStriped?: boolean; /** Flag indicating the contains evenly striped rows. */ isEvenStriped?: boolean; } const flagVisibility = (rows: IRow[]) => { const visibleRows = (rows as IRow[]).filter((oneRow: IRow) => !oneRow.parent || oneRow.isExpanded) as IRow[]; if (visibleRows.length > 0) { visibleRows[0].isFirstVisible = true; visibleRows[visibleRows.length - 1].isLastVisible = true; } }; interface IMappedCell { [name: string]: IRowCell; } class ContextBody extends Component { onRow = (row: IRow, rowProps: any) => { const { onRowClick, onRow } = this.props; const extendedRowProps = { ...rowProps, ...(onRow ? onRow(row, rowProps) : {}) }; return { row, rowProps: extendedRowProps, onClick: (event: React.MouseEvent) => { const tagName = (event.target as HTMLElement).tagName; const computedData = { isInput: tagName === 'INPUT', isButton: tagName === 'BUTTON' }; onRowClick(event, row, rowProps, computedData); }, onKeyDown: (event: React.KeyboardEvent) => { const targetElement = event.target as HTMLElement; const tagName = targetElement.tagName; const computedData = { isInput: tagName === 'INPUT', isButton: tagName === 'BUTTON' }; if (event.key === 'Enter' || event.key === ' ') { onRowClick(event, row, rowProps, computedData); // prevent event default if space is typed while focusing on a hoverable row // so that the page does not scroll when trying to use spacebar to select a row if (event.key === ' ' && !!targetElement.closest('.pf-m-hoverable')) { event.preventDefault(); } } } }; }; mapCells = (headerData: IRow[], row: IRow, rowKey: number) => { // column indexes start after generated optional columns like collapsible or select column(s) const { firstUserColumnIndex } = headerData[0].extraParams; const isFullWidth = row && row.fullWidth; // typically you'd want to map each cell to its column header, but in the case of fullWidth // the first column could be the Select and/or Expandable column let additionalColsIndexShift = isFullWidth ? 0 : firstUserColumnIndex; return { ...(row && (row.cells || row).reduce( (acc: object, cell: IRowCell, cellIndex: number) => { const isCellObject = cell === Object(cell); const isCellFunction = cell && typeof cell.title === 'function'; let formatters: any = []; if (isCellObject && cell.formatters) { // give priority to formatters specified on the cell object // expandable example: // rows: [{ parent: 0, fullWidth: true, cells: [{ title: 'fullWidth, child - a', formatters: [expandable]}] }] formatters = cell.formatters; } else if (isFullWidth && cellIndex < firstUserColumnIndex) { // for backwards compatibility, map the cells that are not under user columns (like Select/Expandable) // to the first user column's header formatters formatters = headerData[firstUserColumnIndex].cell.formatters; } let mappedCellTitle: IRowCell | Function | IRowCell['title'] = cell; if (isCellObject && isCellFunction) { mappedCellTitle = (cell.title as Function)(cell.props.value, rowKey, cellIndex, cell.props); } else if (isCellObject) { mappedCellTitle = cell.title; } const mappedCell: IMappedCell = { [headerData[cellIndex + additionalColsIndexShift].property]: { title: mappedCellTitle as React.ReactNode, formatters, props: { isVisible: true, ...(isCellObject ? cell.props : null) } } }; // increment the shift index when a cell spans multiple columns if (isCellObject && cell.props && cell.props.colSpan) { additionalColsIndexShift += cell.props.colSpan - 1; } return { ...acc, ...mappedCell }; }, { secretTableRowKeyId: row.id !== undefined ? row.id : rowKey } )) }; }; render() { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { className, headerData, rows, rowKey, children, onRowClick, ...props } = this.props; let mappedRows: IRow[]; if (headerData.length > 0) { mappedRows = (rows as IRow[]).map((oneRow: IRow, oneRowKey: number) => ({ ...oneRow, ...this.mapCells(headerData, oneRow, oneRowKey), isExpanded: isRowExpanded(oneRow, rows), isHeightAuto: oneRow.heightAuto || false, isFirst: oneRowKey === 0, isLast: oneRowKey === rows.length - 1, isFirstVisible: false, isLastVisible: false })) as IRow[]; flagVisibility(mappedRows); } return ( {mappedRows && ( )} ); } } export const TableBody = ({ className = '' as string, children = null as React.ReactNode, rowKey = 'secretTableRowKeyId' as string, /* eslint-disable @typescript-eslint/no-unused-vars */ onRow = (...args: any) => ({}), onRowClick = ( event: React.MouseEvent | React.KeyboardEvent, row: IRow, rowProps: IExtraRowData, computedData: IComputedData ) => /* eslint-enable @typescript-eslint/no-unused-vars */ undefined as OnRowClick, ...props }: TableBodyProps) => ( {({ headerData = [], rows = [], ...rest }) => ( {children} )} );