import classNames from "classnames" ; import * as React from "react" ; import { AbstractComponent2 , Props , Utils as CoreUtils } from "@blueprintjs/core" ; import { emptyCellRenderer , CellRenderer } from "./cell/cell" ; import { Batcher } from "./common/batcher" ; import { IFocusedCellCoordinates } from "./common/cell" ; import * as Classes from "./common/classes" ; import { Grid , IColumnIndices , IRowIndices } from "./common/grid" ; import { Rect } from "./common/rect" ; import { RenderMode } from "./common/renderMode" ; export interface ITableBodyCellsProps extends IRowIndices , IColumnIndices , Props { /**
     * A cell renderer for the cells in the body.
     */ cellRenderer : CellRenderer ; /**
     * The coordinates of the currently focused cell, for setting the "isFocused" prop on cells.
     */ focusedCell ? : IFocusedCellCoordinates ; /**
     * The grid computes sizes of cells, rows, or columns from the
     * configurable `columnWidths` and `rowHeights`.
     */ grid : Grid ; /**
     * If true, all `Cell`s render their loading state except for those who have
     * their `loading` prop explicitly set to false.
     */ loading : boolean ; /**
     * An optional callback invoked when all cells in view have completely rendered.
     */ onCompleteRender ? : ( ) => void ; default renderMode ? : RenderMode . BATCH | RenderMode . NONE ; /**
     * The `Rect` bounds of the visible viewport with respect to its parent
     * scrollable pane. While not directly used by the component, this prop is
     * necessary for shouldComponentUpdate logic to run properly.
     */ viewportRect : Rect ; } const SHALLOW_COMPARE_DENYLIST = [ "viewportRect" ] ; /**
 * We don't want to reset the batcher when this set of keys changes. Any other
 * changes should reset the batcher's internal cache.
 */ const BATCHER_RESET_PROP_KEYS_DENYLIST = [ "columnIndexEnd" , "columnIndexStart" , "rowIndexEnd" , "rowIndexStart" , ] ; export class TableBodyCells extends AbstractComponent2 < ITableBodyCellsProps > { public static defaultProps = { renderMode : RenderMode . BATCH , } ; private static cellReactKey ( rowIndex , columnIndex ) { return `cell-${ rowIndex }-${ columnIndex }` ; } private batcher = new Batcher < JSX . Element > ( ) ; public componentDidMount ( ) { this . maybeInvokeOnCompleteRender ( ) ; } public shouldComponentUpdate ( nextProps ? ) { return ( ! CoreUtils . shallowCompareKeys ( nextProps , this . props , { exclude : SHALLOW_COMPARE_DENYLIST , } ) || this . didViewportRectChange ( nextProps . viewportRect , this . props . viewportRect ) ) ; } public componentDidUpdate ( prevProps ) { const shouldResetBatcher = ! CoreUtils . shallowCompareKeys ( prevProps , this . props , { exclude : BATCHER_RESET_PROP_KEYS_DENYLIST , } ) ; if ( shouldResetBatcher ) { this . batcher . reset ( ) ; } this . maybeInvokeOnCompleteRender ( ) ; } public componentWillUnmount ( ) { this . batcher . cancelOutstandingCallback ( ) ; } public render ( ) { const { renderMode } = this . props ; const cells = renderMode === RenderMode . BATCH ? this . renderBatchedCells ( ) : this . renderAllCells ( ) ; return < div className = { Classes . TABLE_BODY_CELLS } > { cells } < /div>; } private renderBatchedCells ( ) { const { columnIndexEnd , columnIndexStart , rowIndexEnd , rowIndexStart } = this . props ; this . batcher . startNewBatch ( ) ; for ( let rowIndex = rowIndexStart ; rowIndex <= rowIndexEnd ; rowIndex ++ ) { for ( let columnIndex = columnIndexStart ; columnIndex <= columnIndexEnd ; columnIndex ++ ) { this . batcher . addArgsToBatch ( rowIndex , columnIndex ) ; } } this . batcher . removeOldAddNew ( this . renderNewCell ) ; if ( ! this . batcher . isDone ( ) ) { this . batcher . idleCallback ( ( ) => this . forceUpdate ( ) ) ; } return this . batcher . getList ( ) ; } private renderAllCells ( ) { const { columnIndexEnd , columnIndexStart , rowIndexEnd , rowIndexStart } = this . props ; const cells = [ ] ; const cellsArgs = [ ] ; for ( let rowIndex = rowIndexStart ; rowIndex <= rowIndexEnd ; rowIndex ++ ) { for ( let columnIndex = columnIndexStart ; columnIndex <= columnIndexEnd ; columnIndex ++ ) { cells . push ( this . renderNewCell ( rowIndex , columnIndex ) ) ; cellsArgs . push ( [ rowIndex , columnIndex ] ) ; } } this . batcher . setList ( cellsArgs , cells ) ; return cells ; } private renderNewCell = ( rowIndex , columnIndex ) => { const { columnIndexEnd , grid , rowIndexEnd } = this . props ; const extremaClasses = grid . getExtremaClasses ( rowIndex , columnIndex , rowIndexEnd , columnIndexEnd ) ; const isGhost = grid . isGhostIndex ( rowIndex , columnIndex ) ; return this . renderCell ( rowIndex , columnIndex , extremaClasses , isGhost ) ; } ; private renderCell = ( rowIndex , columnIndex , extremaClasses , isGhost ) => { const { cellRenderer , focusedCell , loading , grid } = this . props ; let baseCell = isGhost ? emptyCellRenderer ( ) : cellRenderer ( rowIndex , columnIndex ) ; baseCell = baseCell == null ? emptyCellRenderer ( ) : baseCell ; const className = classNames ( cellClassNames ( rowIndex , columnIndex ) , extremaClasses , { [ Classes . TABLE_CELL_GHOST ] : isGhost , [ Classes . TABLE_CELL_LEDGER_ODD ] : rowIndex % 2 === 1 , [ Classes . TABLE_CELL_LEDGER_EVEN ] : rowIndex % 2 === 0 , } , baseCell . props . className ) ; const key = TableBodyCells . cellReactKey ( rowIndex , columnIndex ) ; const rect = isGhost ? grid . getGhostCellRect ( rowIndex , columnIndex ) : grid . getCellRect ( rowIndex , columnIndex ) ; const cellLoading = baseCell . props . loading != null ? baseCell . props . loading : loading ; const style = { ... baseCell . props . style , ... Rect . style ( rect ) } ; const isFocused = focusedCell != null && focusedCell . row === rowIndex && focusedCell . col === columnIndex ; return React . cloneElement ( baseCell , { className , isFocused , key , loading : cellLoading , style , } ) ; } ; private maybeInvokeOnCompleteRender ( ) { const { onCompleteRender , renderMode } = this . props ; if ( renderMode === RenderMode . NONE || ( renderMode === RenderMode . BATCH && this . batcher . isDone ( ) ) ) { onCompleteRender ?. ( ) ; } } private didViewportRectChange = ( nextViewportRect , currViewportRect ) => { if ( nextViewportRect == null && currViewportRect == null ) { return false ; } else if ( nextViewportRect == null || currViewportRect == null ) { return true ; } else { return ! nextViewportRect . equals ( currViewportRect ) ; } } ; } /**
 * Returns the array of class names that must be applied to each table
 * cell so that we can locate any cell based on its coordinate.
 */ export function cellClassNames ( rowIndex , columnIndex ) { return [ Classes . rowCellIndexClass ( rowIndex ) , Classes . columnCellIndexClass ( columnIndex ) ] ; }