import classNames from "classnames" ; import * as React from "react" ; import { polyfill } from "react-lifecycles-compat" ; import { AbstractComponent2 , DISPLAYNAME_PREFIX , Hotkey , Hotkeys , HotkeysTarget , IRef , Utils as CoreUtils , } from "@blueprintjs/core" ; import { ICellProps } from "./cell/cell" ; import { Column , IColumnProps } from "./column" ; import { IFocusedCellCoordinates } from "./common/cell" ; import * as Classes from "./common/classes" ; import { columnInteractionBarContextTypes , IColumnInteractionBarContextTypes } from "./common/context" ; import * as Errors from "./common/errors" ; import { Grid , ICellMapper } from "./common/grid" ; import * as FocusedCellUtils from "./common/internal/focusedCellUtils" ; import * as ScrollUtils from "./common/internal/scrollUtils" ; import { Rect } from "./common/rect" ; import { RenderMode } from "./common/renderMode" ; import { Utils } from "./common/utils" ; import { ColumnHeader } from "./headers/columnHeader" ; import { ColumnHeaderCell , IColumnHeaderCellProps } from "./headers/columnHeaderCell" ; import { renderDefaultRowHeader , RowHeader } from "./headers/rowHeader" ; import { ResizeSensor } from "./interactions/resizeSensor" ; import { GuideLayer } from "./layers/guides" ; import { RegionStyler , RegionLayer } from "./layers/regions" ; import { Locator } from "./locator" ; import { QuadrantType } from "./quadrants/tableQuadrant" ; import { TableQuadrantStack } from "./quadrants/tableQuadrantStack" ; import { ColumnLoadingOption , IRegion , RegionCardinality , Regions , SelectionModes , TableLoadingOption , } from "./regions" ; import { IResizeRowsByApproximateHeightOptions , resizeRowsByApproximateHeight , resizeRowsByTallestCell , } from "./resizeRows" ; import { TableBody } from "./tableBody" ; import { TableHotkeys } from "./tableHotkeys" ; import type { TableProps } from "./tableProps" ; import type { TableState , TableSnapshot } from "./tableState" ; import { clampNumFrozenColumns , clampNumFrozenRows , hasLoadingOption } from "./tableUtils" ; deprecated @ HotkeysTarget @ polyfill export class Table extends AbstractComponent2 < TableProps , TableState , TableSnapshot > { public static displayName = `${ DISPLAYNAME_PREFIX }.Table` ; public static defaultProps : TableProps = { defaultColumnWidth : 150 , defaultRowHeight : 20 , enableFocusedCell : false , enableGhostCells : false , enableMultipleSelection : true , enableRowHeader : true , forceRerenderOnSelectionChange : false , loadingOptions : [ ] , minColumnWidth : 50 , minRowHeight : 20 , numFrozenColumns : 0 , numFrozenRows : 0 , numRows : 0 , renderMode : RenderMode . BATCH_ON_UPDATE , rowHeaderCellRenderer : renderDefaultRowHeader , selectionModes : SelectionModes . ALL , } ; public static childContextTypes : React . ValidationMap < IColumnInteractionBarContextTypes > = columnInteractionBarContextTypes ; public static getDerivedStateFromProps ( props , state ) { const { children , defaultColumnWidth , defaultRowHeight , enableFocusedCell , focusedCell , numRows , selectedRegions , selectionModes , } = props ; let { columnWidths , rowHeights } = props ; if ( columnWidths == null ) { columnWidths = state . columnWidths ; } if ( rowHeights == null ) { rowHeights = state . rowHeights ; } const newChildrenArray = React . Children . toArray ( children ) as Array < React . ReactElement < IColumnProps > > ; const didChildrenChange = newChildrenArray !== state . childrenArray ; const numCols = newChildrenArray . length ; let newColumnWidths = columnWidths ; if ( columnWidths !== state . columnWidths || didChildrenChange ) { const previousColumnWidths = newChildrenArray . map ( ( child , index ) => { const mappedIndex = state . columnIdToIndex [ child . props . id ] ; return state . columnWidths [ mappedIndex != null ? mappedIndex : index ] ; } ) ; newColumnWidths = Utils . arrayOfLength ( newColumnWidths , numCols , defaultColumnWidth ) ; newColumnWidths = Utils . assignSparseValues ( newColumnWidths , previousColumnWidths ) ; newColumnWidths = Utils . assignSparseValues ( newColumnWidths , columnWidths ) ; } let newRowHeights = rowHeights ; if ( rowHeights !== state . rowHeights || numRows !== state . rowHeights . length ) { newRowHeights = Utils . arrayOfLength ( newRowHeights , numRows , defaultRowHeight ) ; newRowHeights = Utils . assignSparseValues ( newRowHeights , rowHeights ) ; } let newSelectedRegions = selectedRegions ; if ( selectedRegions == null ) { newSelectedRegions = state . selectedRegions . filter ( region => { const regionCardinality = Regions . getRegionCardinality ( region ) ; return ( Table . isSelectionModeEnabled ( props , regionCardinality , selectionModes ) && Regions . isRegionValidForTable ( region , numRows , numCols ) ) ; } ) ; } const newFocusedCell = FocusedCellUtils . getInitialFocusedCell ( enableFocusedCell , focusedCell , state . focusedCell , newSelectedRegions ) ; const nextState = { childrenArray : newChildrenArray , columnIdToIndex : didChildrenChange ? Table . createColumnIdIndex ( newChildrenArray ) : state . columnIdToIndex , columnWidths : newColumnWidths , focusedCell : newFocusedCell , numFrozenColumnsClamped : clampNumFrozenColumns ( props ) , numFrozenRowsClamped : clampNumFrozenRows ( props ) , rowHeights : newRowHeights , selectedRegions : newSelectedRegions , } ; if ( ! CoreUtils . deepCompareKeys ( state , nextState , Table . SHALLOW_COMPARE_STATE_KEYS_DENYLIST ) ) { return nextState ; } return null ; } private static SHALLOW_COMPARE_PROP_KEYS_DENYLIST = [ "selectedRegions" , ] as Array < keyof TableProps > ; private static SHALLOW_COMPARE_STATE_KEYS_DENYLIST = [ "selectedRegions" , "viewportRect" , ] as Array < keyof TableState > ; private static createColumnIdIndex ( children ) { const columnIdToIndex = { } ; for ( let i = 0 ; i < children . length ; i ++ ) { const key = children [ i ] . props . id ; if ( key != null ) { columnIdToIndex [ String ( key ) ] = i ; } } return columnIdToIndex ; } private static isSelectionModeEnabled ( props , selectionMode , selectionModes = props . selectionModes ) { const { children , numRows } = props ; const numColumns = React . Children . count ( children ) ; return selectionModes . indexOf ( selectionMode ) >= 0 && numRows > 0 && numColumns > 0 ; } private hotkeysImpl : TableHotkeys ; public grid : Grid ; public locator : Locator ; private resizeSensorDetach : ( ) => void ; private refHandlers = { cellContainer : ( ref ) => ( this . cellContainerElement = ref ) , columnHeader : ( ref ) => ( this . columnHeaderElement = ref ) , quadrantStack : ( ref ) => ( this . quadrantStackInstance = ref ) , rootTable : ( ref ) => ( this . rootTableElement = ref ) , rowHeader : ( ref ) => ( this . rowHeaderElement = ref ) , scrollContainer : ( ref ) => ( this . scrollContainerElement = ref ) , } ; private cellContainerElement : HTMLElement ; private columnHeaderElement : HTMLElement ; private quadrantStackInstance : TableQuadrantStack ; private rootTableElement : HTMLElement ; private rowHeaderElement : HTMLElement ; private scrollContainerElement : HTMLElement ; private didCompletelyMount = false ; public constructor ( props , context ? ) { super ( props , context ) ; const { children , columnWidths , defaultRowHeight , defaultColumnWidth , numRows , rowHeights } = this . props ; const childrenArray = React . Children . toArray ( children ) as Array < React . ReactElement < IColumnProps > > ; const columnIdToIndex = Table . createColumnIdIndex ( childrenArray ) ; let newColumnWidths = childrenArray . map ( ( ) => defaultColumnWidth ) ; newColumnWidths = Utils . assignSparseValues ( newColumnWidths , columnWidths ) ; let newRowHeights = Utils . times ( numRows , ( ) => defaultRowHeight ) ; newRowHeights = Utils . assignSparseValues ( newRowHeights , rowHeights ) ; const selectedRegions = props . selectedRegions == null ? ( [ ] as IRegion [ ] ) : props . selectedRegions ; const focusedCell = FocusedCellUtils . getInitialFocusedCell ( props . enableFocusedCell , props . focusedCell , undefined , selectedRegions ) ; this . state = { childrenArray , columnIdToIndex , columnWidths : newColumnWidths , focusedCell , isLayoutLocked : false , isReordering : false , numFrozenColumnsClamped : clampNumFrozenColumns ( props ) , numFrozenRowsClamped : clampNumFrozenRows ( props ) , rowHeights : newRowHeights , selectedRegions , } ; this . hotkeysImpl = new TableHotkeys ( props , this . state , this . grid , { getEnabledSelectionHandler : this . getEnabledSelectionHandler , handleFocus : this . handleFocus , handleSelection : this . handleSelection , syncViewportPosition : this . syncViewportPosition , } ) ; } /**
     * __Experimental!__ Resizes all rows in the table to the approximate
     * maximum height of wrapped cell content in each row. Works best when each
     * cell contains plain text of a consistent font style (though font style
     * may vary between cells). Since this function uses approximate
     * measurements, results may not be perfect.
     *
     * Approximation parameters can be configured for the entire table or on a
     * per-cell basis. Default values are fine-tuned to work well with default
     * Table font styles.
     */ public resizeRowsByApproximateHeight ( getCellText , options ? ) { const rowHeights = resizeRowsByApproximateHeight ( this . props . numRows , this . state . columnWidths , getCellText , options ) ; this . invalidateGrid ( ) ; this . setState ( { rowHeights } ) ; } /**
     * Resize all rows in the table to the height of the tallest visible cell in the specified columns.
     * If no indices are provided, default to using the tallest visible cell from all columns in view.
     */ public resizeRowsByTallestCell ( columnIndices ? ) { const rowHeights = resizeRowsByTallestCell ( this . grid , this . state . viewportRect , this . locator , this . state . rowHeights . length , columnIndices ) ; this . invalidateGrid ( ) ; this . setState ( { rowHeights } ) ; } /**
     * Scrolls the table to the target region in a fashion appropriate to the target region's
     * cardinality:
     *
     * - CELLS: Scroll the top-left cell in the target region to the top-left corner of the viewport.
     * - FULL_ROWS: Scroll the top-most row in the target region to the top of the viewport.
     * - FULL_COLUMNS: Scroll the left-most column in the target region to the left side of the viewport.
     * - FULL_TABLE: Scroll the top-left cell in the table to the top-left corner of the viewport.
     *
     * If there are active frozen rows and/or columns, the target region will be positioned in the
     * top-left corner of the non-frozen area (unless the target region itself is in the frozen
     * area).
     *
     * If the target region is close to the bottom-right corner of the table, this function will
     * simply scroll the target region as close to the top-left as possible until the bottom-right
     * corner is reached.
     */ public scrollToRegion ( region ) { const { numFrozenColumnsClamped : numFrozenColumns , numFrozenRowsClamped : numFrozenRows } = this . state ; const { left : currScrollLeft , top : currScrollTop } = this . state . viewportRect ; const { scrollLeft , scrollTop } = ScrollUtils . getScrollPositionForRegion ( region , currScrollLeft , currScrollTop , this . grid . getCumulativeWidthBefore , this . grid . getCumulativeHeightBefore , numFrozenRows , numFrozenColumns ) ; const correctedScrollLeft = this . shouldDisableHorizontalScroll ( ) ? 0 : scrollLeft ; const correctedScrollTop = this . shouldDisableVerticalScroll ( ) ? 0 : scrollTop ; this . quadrantStackInstance . scrollToPosition ( correctedScrollLeft , correctedScrollTop ) ; } public getChildContext ( ) { return { enableColumnInteractionBar : this . props . enableColumnInteractionBar , } ; } public shouldComponentUpdate ( nextProps , nextState ) { const propKeysDenylist = { exclude : Table . SHALLOW_COMPARE_PROP_KEYS_DENYLIST } ; const stateKeysDenylist = { exclude : Table . SHALLOW_COMPARE_STATE_KEYS_DENYLIST } ; return ( ! CoreUtils . shallowCompareKeys ( this . props , nextProps , propKeysDenylist ) || ! CoreUtils . shallowCompareKeys ( this . state , nextState , stateKeysDenylist ) || ! CoreUtils . deepCompareKeys ( this . props , nextProps , Table . SHALLOW_COMPARE_PROP_KEYS_DENYLIST ) || ! CoreUtils . deepCompareKeys ( this . state , nextState , Table . SHALLOW_COMPARE_STATE_KEYS_DENYLIST ) ) ; } public render ( ) { const { children , className , enableRowHeader , loadingOptions , numRows , enableColumnInteractionBar , } = this . props ; const { horizontalGuides , numFrozenColumnsClamped , numFrozenRowsClamped , verticalGuides } = this . state ; if ( ! this . gridDimensionsMatchProps ( ) ) { this . invalidateGrid ( ) ; } this . validateGrid ( ) ; const classes = classNames ( Classes . TABLE_CONTAINER , { [ Classes . TABLE_REORDERING ] : this . state . isReordering , [ Classes . TABLE_NO_VERTICAL_SCROLL ] : this . shouldDisableVerticalScroll ( ) , [ Classes . TABLE_NO_HORIZONTAL_SCROLL ] : this . shouldDisableHorizontalScroll ( ) , [ Classes . TABLE_SELECTION_ENABLED ] : Table . isSelectionModeEnabled ( this . props , RegionCardinality . CELLS ) , [ Classes . TABLE_NO_ROWS ] : numRows === 0 , } , className ) ; return ( < div className = { classes } ref = { this . refHandlers . rootTable } onScroll = { this . handleRootScroll } > < TableQuadrantStack bodyRef = { this . refHandlers . cellContainer } bodyRenderer = { this . renderBody } columnHeaderCellRenderer = { this . renderColumnHeader } columnHeaderRef = { this . refHandlers . columnHeader } enableColumnInteractionBar = { enableColumnInteractionBar } enableRowHeader = { enableRowHeader } grid = { this . grid } handleColumnResizeGuide = { this . handleColumnResizeGuide } handleColumnsReordering = { this . handleColumnsReordering } handleRowResizeGuide = { this . handleRowResizeGuide } handleRowsReordering = { this . handleRowsReordering } isHorizontalScrollDisabled = { this . shouldDisableHorizontalScroll ( ) } isVerticalScrollDisabled = { this . shouldDisableVerticalScroll ( ) } loadingOptions = { loadingOptions } numColumns = { React . Children . count ( children ) } numFrozenColumns = { numFrozenColumnsClamped } numFrozenRows = { numFrozenRowsClamped } numRows = { numRows } onScroll = { this . handleBodyScroll } ref = { this . refHandlers . quadrantStack } menuRenderer = { this . renderMenu } rowHeaderCellRenderer = { this . renderRowHeader } rowHeaderRef = { this . refHandlers . rowHeader } scrollContainerRef = { this . refHandlers . scrollContainer } / > < div className = { classNames ( Classes . TABLE_OVERLAY_LAYER , Classes . TABLE_OVERLAY_REORDERING_CURSOR ) } /> < GuideLayer className = { Classes . TABLE_RESIZE_GUIDES } verticalGuides = { verticalGuides } horizontalGuides = { horizontalGuides } / > < /div>); } public renderHotkeys ( ) { const hotkeys = [ this . maybeRenderCopyHotkey ( ) , this . maybeRenderSelectAllHotkey ( ) , this . maybeRenderFocusHotkeys ( ) , this . maybeRenderSelectionResizeHotkeys ( ) , ] ; return < Hotkeys > { hotkeys . filter ( element => element !== undefined ) } < /Hotkeys>; } /**
     * When the component mounts, the HTML Element refs will be available, so
     * we constructor the Locator, which queries the elements' bounding
     * ClientRects.
     */ public componentDidMount ( ) { this . validateGrid ( ) ; this . locator = new Locator ( this . rootTableElement , this . scrollContainerElement , this . cellContainerElement ) ; this . updateLocator ( ) ; this . updateViewportRect ( this . locator . getViewportRect ( ) ) ; this . resizeSensorDetach = ResizeSensor . attach ( this . rootTableElement , ( ) => { if ( ! this . state . isLayoutLocked ) { this . updateViewportRect ( this . locator . getViewportRect ( ) ) ; } } ) ; } public componentWillUnmount ( ) { if ( this . resizeSensorDetach != null ) { this . resizeSensorDetach ( ) ; delete this . resizeSensorDetach ; } this . didCompletelyMount = false ; } public getSnapshotBeforeUpdate ( ) { const { viewportRect } = this . state ; this . validateGrid ( ) ; const tableBottom = this . grid . getCumulativeHeightAt ( this . grid . numRows - 1 ) ; const tableRight = this . grid . getCumulativeWidthAt ( this . grid . numCols - 1 ) ; const nextScrollTop = tableBottom < viewportRect . top + viewportRect . height ? Math . max ( 0 , tableBottom - viewportRect . height ) : undefined ; const nextScrollLeft = tableRight < viewportRect . left + viewportRect . width ? Math . max ( 0 , tableRight - viewportRect . width ) : undefined ; return { nextScrollLeft , nextScrollTop } ; } public componentDidUpdate ( prevProps , prevState , snapshot ) { super . componentDidUpdate ( prevProps , prevState , snapshot ) ; this . hotkeysImpl . setState ( this . state ) ; this . hotkeysImpl . setProps ( this . props ) ; const didChildrenChange = ( React . Children . toArray ( this . props . children ) as Array < React . ReactElement < IColumnProps > > ) !== this . state . childrenArray ; const shouldInvalidateGrid = didChildrenChange || this . props . columnWidths !== prevState . columnWidths || this . props . rowHeights !== prevState . rowHeights || this . props . numRows !== prevProps . numRows || ( this . props . forceRerenderOnSelectionChange && this . props . selectedRegions !== prevProps . selectedRegions ) ; if ( shouldInvalidateGrid ) { this . invalidateGrid ( ) ; } if ( this . locator != null ) { this . validateGrid ( ) ; this . updateLocator ( ) ; } const didUpdateColumnOrRowSizes = ! CoreUtils . arraysEqual ( this . state . columnWidths , prevState . columnWidths ) || ! CoreUtils . arraysEqual ( this . state . rowHeights , prevState . rowHeights ) ; if ( didUpdateColumnOrRowSizes ) { this . quadrantStackInstance . synchronizeQuadrantViews ( ) ; this . syncViewportPosition ( snapshot ) ; } } protected validateProps ( props ) { const { children , columnWidths , numFrozenColumns , numFrozenRows , numRows , rowHeights } = props ; const numColumns = React . Children . count ( children ) ; if ( numRows != null && numRows < 0 ) { throw new Error ( Errors . TABLE_NUM_ROWS_NEGATIVE ) ; } if ( numFrozenRows != null && numFrozenRows < 0 ) { throw new Error ( Errors . TABLE_NUM_FROZEN_ROWS_NEGATIVE ) ; } if ( numFrozenColumns != null && numFrozenColumns < 0 ) { throw new Error ( Errors . TABLE_NUM_FROZEN_COLUMNS_NEGATIVE ) ; } if ( numRows != null && rowHeights != null && rowHeights . length !== numRows ) { throw new Error ( Errors . TABLE_NUM_ROWS_ROW_HEIGHTS_MISMATCH ) ; } if ( numColumns != null && columnWidths != null && columnWidths . length !== numColumns ) { throw new Error ( Errors . TABLE_NUM_COLUMNS_COLUMN_WIDTHS_MISMATCH ) ; } React . Children . forEach ( children , child => { if ( ! CoreUtils . isElementOfType ( child , Column ) ) { throw new Error ( Errors . TABLE_NON_COLUMN_CHILDREN_WARNING ) ; } } ) ; if ( numFrozenRows != null && numRows != null && numFrozenRows > numRows ) { console . warn ( Errors . TABLE_NUM_FROZEN_ROWS_BOUND_WARNING ) ; } if ( numFrozenColumns != null && numFrozenColumns > numColumns ) { console . warn ( Errors . TABLE_NUM_FROZEN_COLUMNS_BOUND_WARNING ) ; } } private gridDimensionsMatchProps ( ) { const { children , numRows } = this . props ; return ( this . grid != null && this . grid . numCols === React . Children . count ( children ) && this . grid . numRows === numRows ) ; } private maybeRenderCopyHotkey ( ) { const { getCellClipboardData } = this . props ; if ( getCellClipboardData != null ) { return ( < Hotkey key = "copy-hotkey" label = "Copy selected table cells" group = "Table" combo = "mod+c" onKeyDown = { this . hotkeysImpl . handleCopy } / > ) ; } else { return undefined ; } } private maybeRenderSelectionResizeHotkeys ( ) { const { enableMultipleSelection , selectionModes } = this . props ; const isSomeSelectionModeEnabled = selectionModes . length > 0 ; if ( enableMultipleSelection && isSomeSelectionModeEnabled ) { return [ < Hotkey key = "resize-selection-up" label = "Resize selection upward" group = "Table" combo = "shift+up" onKeyDown = { this . hotkeysImpl . handleSelectionResizeUp } / > , < Hotkey key = "resize-selection-down" label = "Resize selection downward" group = "Table" combo = "shift+down" onKeyDown = { this . hotkeysImpl . handleSelectionResizeDown } / > , < Hotkey key = "resize-selection-left" label = "Resize selection leftward" group = "Table" combo = "shift+left" onKeyDown = { this . hotkeysImpl . handleSelectionResizeLeft } / > , < Hotkey key = "resize-selection-right" label = "Resize selection rightward" group = "Table" combo = "shift+right" onKeyDown = { this . hotkeysImpl . handleSelectionResizeRight } / > , ] ; } else { return undefined ; } } private maybeRenderFocusHotkeys ( ) { const { enableFocusedCell } = this . props ; if ( enableFocusedCell != null ) { return [ < Hotkey key = "move left" label = "Move focus cell left" group = "Table" combo = "left" onKeyDown = { this . hotkeysImpl . handleFocusMoveLeft } / > , < Hotkey key = "move right" label = "Move focus cell right" group = "Table" combo = "right" onKeyDown = { this . hotkeysImpl . handleFocusMoveRight } / > , < Hotkey key = "move up" label = "Move focus cell up" group = "Table" combo = "up" onKeyDown = { this . hotkeysImpl . handleFocusMoveUp } / > , < Hotkey key = "move down" label = "Move focus cell down" group = "Table" combo = "down" onKeyDown = { this . hotkeysImpl . handleFocusMoveDown } / > , < Hotkey key = "move tab" label = "Move focus cell tab" group = "Table" combo = "tab" onKeyDown = { this . hotkeysImpl . handleFocusMoveRightInternal } allowInInput = { true } / > , < Hotkey key = "move shift-tab" label = "Move focus cell shift tab" group = "Table" combo = "shift+tab" onKeyDown = { this . hotkeysImpl . handleFocusMoveLeftInternal } allowInInput = { true } / > , < Hotkey key = "move enter" label = "Move focus cell enter" group = "Table" combo = "enter" onKeyDown = { this . hotkeysImpl . handleFocusMoveDownInternal } allowInInput = { true } / > , < Hotkey key = "move shift-enter" label = "Move focus cell shift enter" group = "Table" combo = "shift+enter" onKeyDown = { this . hotkeysImpl . handleFocusMoveUpInternal } allowInInput = { true } / > , ] ; } else { return [ ] ; } } private maybeRenderSelectAllHotkey ( ) { if ( Table . isSelectionModeEnabled ( this . props , RegionCardinality . FULL_TABLE ) ) { return ( < Hotkey key = "select-all-hotkey" label = "Select all" group = "Table" combo = "mod+a" onKeyDown = { this . hotkeysImpl . handleSelectAllHotkey } / > ) ; } else { return undefined ; } } private shouldDisableVerticalScroll ( ) { const { enableGhostCells } = this . props ; const { viewportRect } = this . state ; const rowIndices = this . grid . getRowIndicesInRect ( viewportRect , enableGhostCells ) ; const isViewportUnscrolledVertically = viewportRect != null && viewportRect . top === 0 ; const areRowHeadersLoading = hasLoadingOption ( this . props . loadingOptions , TableLoadingOption . ROW_HEADERS ) ; const areGhostRowsVisible = enableGhostCells && this . grid . isGhostIndex ( rowIndices . rowIndexEnd , 0 ) ; return areGhostRowsVisible && ( isViewportUnscrolledVertically || areRowHeadersLoading ) ; } private shouldDisableHorizontalScroll ( ) { const { enableGhostCells } = this . props ; const { viewportRect } = this . state ; const columnIndices = this . grid . getColumnIndicesInRect ( viewportRect , enableGhostCells ) ; const isViewportUnscrolledHorizontally = viewportRect != null && viewportRect . left === 0 ; const areGhostColumnsVisible = enableGhostCells && this . grid . isGhostColumn ( columnIndices . columnIndexEnd ) ; const areColumnHeadersLoading = hasLoadingOption ( this . props . loadingOptions , TableLoadingOption . COLUMN_HEADERS ) ; return areGhostColumnsVisible && ( isViewportUnscrolledHorizontally || areColumnHeadersLoading ) ; } private renderMenu = ( refHandler ) => { const classes = classNames ( Classes . TABLE_MENU , { [ Classes . TABLE_SELECTION_ENABLED ] : Table . isSelectionModeEnabled ( this . props , RegionCardinality . FULL_TABLE ) , } ) ; return ( < div className = { classes } ref = { refHandler } onMouseDown = { this . handleMenuMouseDown } > { this . maybeRenderRegions ( this . styleMenuRegion ) } < /div>); } ; private handleMenuMouseDown = ( e ) => { this . selectAll ( ! e . shiftKey ) ; } ; private selectAll = ( shouldUpdateFocusedCell ) => { const selectionHandler = this . getEnabledSelectionHandler ( RegionCardinality . FULL_TABLE ) ; selectionHandler ( [ Regions . table ( ) ] ) ; if ( shouldUpdateFocusedCell ) { const newFocusedCellCoordinates = Regions . getFocusCellCoordinatesFromRegion ( Regions . table ( ) ) ; this . handleFocus ( FocusedCellUtils . toFullCoordinates ( newFocusedCellCoordinates ) ) ; } } ; private getColumnProps ( columnIndex ) { const column = this . state . childrenArray [ columnIndex ] as React . ReactElement < IColumnProps > ; return column === undefined ? undefined : column . props ; } private columnHeaderCellRenderer = ( columnIndex ) => { const props = this . getColumnProps ( columnIndex ) ; if ( props === undefined ) { return null ; } const { id , loadingOptions , cellRenderer , columnHeaderCellRenderer , ... spreadableProps } = props ; const columnLoading = hasLoadingOption ( loadingOptions , ColumnLoadingOption . HEADER ) ; if ( columnHeaderCellRenderer != null ) { const columnHeaderCell = columnHeaderCellRenderer ( columnIndex ) ; const columnHeaderCellLoading = columnHeaderCell . props . loading ; const columnHeaderCellProps = { loading : columnHeaderCellLoading != null ? columnHeaderCellLoading : columnLoading , } ; return React . cloneElement ( columnHeaderCell , columnHeaderCellProps ) ; } const baseProps = { index : columnIndex , loading : columnLoading , ... spreadableProps , } ; if ( props . name != null ) { return < ColumnHeaderCell { ... baseProps } / > ; } else { return < ColumnHeaderCell { ... baseProps } name = { Utils . toBase26Alpha ( columnIndex ) } / > ; } } ; private renderColumnHeader = ( refHandler , resizeHandler , reorderingHandler , showFrozenColumnsOnly = false ) => { const { focusedCell , selectedRegions , viewportRect } = this . state ; const { enableMultipleSelection , enableGhostCells , enableColumnReordering , enableColumnResizing , loadingOptions , maxColumnWidth , minColumnWidth , selectedRegionTransform , } = this . props ; const classes = classNames ( Classes . TABLE_COLUMN_HEADERS , { [ Classes . TABLE_SELECTION_ENABLED ] : Table . isSelectionModeEnabled ( this . props , RegionCardinality . FULL_COLUMNS ) , } ) ; const columnIndices = this . grid . getColumnIndicesInRect ( viewportRect , enableGhostCells ) ; const columnIndexStart = showFrozenColumnsOnly ? 0 : columnIndices . columnIndexStart ; const columnIndexEnd = showFrozenColumnsOnly ? this . getMaxFrozenColumnIndex ( ) : columnIndices . columnIndexEnd ; return ( < div className = { classes } > < ColumnHeader enableMultipleSelection = { enableMultipleSelection } cellRenderer = { this . columnHeaderCellRenderer } focusedCell = { focusedCell } grid = { this . grid } isReorderable = { enableColumnReordering } isResizable = { enableColumnResizing } loading = { hasLoadingOption ( loadingOptions , TableLoadingOption . COLUMN_HEADERS ) } locator = { this . locator } maxColumnWidth = { maxColumnWidth } measurableElementRef = { refHandler } minColumnWidth = { minColumnWidth } onColumnWidthChanged = { this . handleColumnWidthChanged } onFocusedCell = { this . handleFocus } onLayoutLock = { this . handleLayoutLock } onReordered = { this . handleColumnsReordered } onReordering = { reorderingHandler } onResizeGuide = { resizeHandler } onSelection = { this . getEnabledSelectionHandler ( RegionCardinality . FULL_COLUMNS ) } selectedRegions = { selectedRegions } selectedRegionTransform = { selectedRegionTransform } columnIndexStart = { columnIndexStart } columnIndexEnd = { columnIndexEnd } > { this . props . children } < /ColumnHeader> { this . maybeRenderRegions ( this . styleColumnHeaderRegion ) } < /div>); } ; private renderRowHeader = ( refHandler , resizeHandler , reorderingHandler , showFrozenRowsOnly = false ) => { const { focusedCell , selectedRegions , viewportRect } = this . state ; const { enableMultipleSelection , enableGhostCells , enableRowReordering , enableRowResizing , loadingOptions , maxRowHeight , minRowHeight , rowHeaderCellRenderer , selectedRegionTransform , } = this . props ; const classes = classNames ( Classes . TABLE_ROW_HEADERS , { [ Classes . TABLE_SELECTION_ENABLED ] : Table . isSelectionModeEnabled ( this . props , RegionCardinality . FULL_ROWS ) , } ) ; const rowIndices = this . grid . getRowIndicesInRect ( viewportRect , enableGhostCells ) ; const rowIndexStart = showFrozenRowsOnly ? 0 : rowIndices . rowIndexStart ; const rowIndexEnd = showFrozenRowsOnly ? this . getMaxFrozenRowIndex ( ) : rowIndices . rowIndexEnd ; return ( < div className = { classes } ref = { refHandler } > < RowHeader enableMultipleSelection = { enableMultipleSelection } focusedCell = { focusedCell } grid = { this . grid } locator = { this . locator } isReorderable = { enableRowReordering } isResizable = { enableRowResizing } loading = { hasLoadingOption ( loadingOptions , TableLoadingOption . ROW_HEADERS ) } maxRowHeight = { maxRowHeight } minRowHeight = { minRowHeight } onFocusedCell = { this . handleFocus } onLayoutLock = { this . handleLayoutLock } onResizeGuide = { resizeHandler } onReordered = { this . handleRowsReordered } onReordering = { reorderingHandler } onRowHeightChanged = { this . handleRowHeightChanged } onSelection = { this . getEnabledSelectionHandler ( RegionCardinality . FULL_ROWS ) } rowHeaderCellRenderer = { rowHeaderCellRenderer } selectedRegions = { selectedRegions } selectedRegionTransform = { selectedRegionTransform } rowIndexStart = { rowIndexStart } rowIndexEnd = { rowIndexEnd } / > { this . maybeRenderRegions ( this . styleRowHeaderRegion ) } < /div>); } ; private bodyCellRenderer = ( rowIndex , columnIndex ) => { const columnProps = this . getColumnProps ( columnIndex ) ; if ( columnProps === undefined ) { return null ; } const { id , loadingOptions , cellRenderer , columnHeaderCellRenderer , name , nameRenderer , ... restColumnProps } = columnProps ; const cell = cellRenderer ( rowIndex , columnIndex ) ; const { loading = hasLoadingOption ( loadingOptions , ColumnLoadingOption . CELLS ) } = cell . props ; const cellProps = { ... restColumnProps , loading , } ; return React . cloneElement ( cell , cellProps ) ; } ; private renderBody = ( quadrantType , showFrozenRowsOnly = false , showFrozenColumnsOnly = false ) => { const { focusedCell , numFrozenColumnsClamped : numFrozenColumns , numFrozenRowsClamped : numFrozenRows , selectedRegions , viewportRect , } = this . state ; const { enableMultipleSelection , enableGhostCells , loadingOptions , bodyContextMenuRenderer , selectedRegionTransform , } = this . props ; const rowIndices = this . grid . getRowIndicesInRect ( viewportRect , enableGhostCells ) ; const columnIndices = this . grid . getColumnIndicesInRect ( viewportRect , enableGhostCells ) ; const columnIndexStart = showFrozenColumnsOnly ? 0 : columnIndices . columnIndexStart + numFrozenColumns ; const rowIndexStart = showFrozenRowsOnly ? 0 : rowIndices . rowIndexStart + numFrozenRows ; const columnIndexEnd = showFrozenColumnsOnly ? numFrozenColumns - 1 : columnIndices . columnIndexEnd ; const rowIndexEnd = showFrozenRowsOnly ? numFrozenRows - 1 : rowIndices . rowIndexEnd ; const onCompleteRender = quadrantType === QuadrantType . MAIN ? this . handleCompleteRender : undefined ; return ( < div > < TableBody enableMultipleSelection = { enableMultipleSelection } cellRenderer = { this . bodyCellRenderer } focusedCell = { focusedCell } grid = { this . grid } loading = { hasLoadingOption ( loadingOptions , TableLoadingOption . CELLS ) } locator = { this . locator } onCompleteRender = { onCompleteRender } onFocusedCell = { this . handleFocus } onSelection = { this . getEnabledSelectionHandler ( RegionCardinality . CELLS ) } bodyContextMenuRenderer = { bodyContextMenuRenderer } renderMode = { this . getNormalizedRenderMode ( ) } selectedRegions = { selectedRegions } selectedRegionTransform = { selectedRegionTransform } viewportRect = { viewportRect } columnIndexStart = { columnIndexStart } columnIndexEnd = { columnIndexEnd } rowIndexStart = { rowIndexStart } rowIndexEnd = { rowIndexEnd } numFrozenColumns = { showFrozenColumnsOnly ? numFrozenColumns : undefined } numFrozenRows = { showFrozenRowsOnly ? numFrozenRows : undefined } / > { this . maybeRenderRegions ( this . styleBodyRegion , quadrantType ) } < /div>); } ; private isGuidesShowing ( ) { return this . state . verticalGuides != null || this . state . horizontalGuides != null ; } private getEnabledSelectionHandler = ( selectionMode ) => { if ( ! Table . isSelectionModeEnabled ( this . props , selectionMode ) ) { return this . clearSelection ; } else { return this . handleSelection ; } } ; private invalidateGrid ( ) { this . grid = null ; } private validateGrid ( ) { if ( this . grid == null ) { const { defaultRowHeight , defaultColumnWidth } = this . props ; const { rowHeights , columnWidths } = this . state ; this . grid = new Grid ( rowHeights , columnWidths , Grid . DEFAULT_BLEED , defaultRowHeight , defaultColumnWidth ) ; this . invokeOnVisibleCellsChangeCallback ( this . state . viewportRect ) ; this . hotkeysImpl . setGrid ( this . grid ) ; } } /**
     * Renders a `RegionLayer`, applying styles to the regions using the
     * supplied `RegionStyler`. `RegionLayer` is a `PureRender` component, so
     * the `RegionStyler` should be a new instance on every render if we
     * intend to redraw the region layer.
     */ private maybeRenderRegions ( getRegionStyle , quadrantType ? ) { if ( this . isGuidesShowing ( ) && ! this . state . isReordering ) { return undefined ; } const regionGroups = Regions . joinStyledRegionGroups ( this . state . selectedRegions , this . props . styledRegionGroups , this . state . focusedCell ) ; return regionGroups . map ( ( regionGroup , index ) => { const regionStyles = regionGroup . regions . map ( region => getRegionStyle ( region , quadrantType ) ) ; return ( < RegionLayer className = { classNames ( regionGroup . className ) } key = { index } regions = { regionGroup . regions } regionStyles = { regionStyles } / > ) ; } ) ; } private handleCompleteRender = ( ) => { if ( this . state . viewportRect != null ) { this . props . onCompleteRender ?. ( ) ; this . didCompletelyMount = true ; } } ; private styleBodyRegion = ( region , quadrantType ) : React . CSSProperties => { const { numFrozenColumns } = this . props ; const cardinality = Regions . getRegionCardinality ( region ) ; const style = this . grid . getRegionStyle ( region ) ; const canHideRightBorder = ( quadrantType === QuadrantType . TOP_LEFT || quadrantType === QuadrantType . LEFT ) && numFrozenColumns != null && numFrozenColumns > 0 ; const fixedHeight = this . grid . getHeight ( ) ; const fixedWidth = this . grid . getWidth ( ) ; const alignmentCorrection = 1 ; const alignmentCorrectionString = `-${ alignmentCorrection }px` ; switch ( cardinality ) { case RegionCardinality . CELLS : return style ; case RegionCardinality . FULL_COLUMNS : style . top = alignmentCorrectionString ; style . height = fixedHeight + alignmentCorrection ; return style ; case RegionCardinality . FULL_ROWS : style . left = alignmentCorrectionString ; style . width = fixedWidth + alignmentCorrection ; if ( canHideRightBorder ) { style . right = alignmentCorrectionString ; } return style ; case RegionCardinality . FULL_TABLE : style . left = alignmentCorrectionString ; style . top = alignmentCorrectionString ; style . width = fixedWidth + alignmentCorrection ; style . height = fixedHeight + alignmentCorrection ; if ( canHideRightBorder ) { style . right = alignmentCorrectionString ; } return style ; default : return { display : "none" } ; } } ; private styleMenuRegion = ( region ) : React . CSSProperties => { const { viewportRect } = this . state ; if ( viewportRect == null ) { return { } ; } const cardinality = Regions . getRegionCardinality ( region ) ; const style = this . grid . getRegionStyle ( region ) ; switch ( cardinality ) { case RegionCardinality . FULL_TABLE : style . right = "0px" ; style . bottom = "0px" ; style . top = "0px" ; style . left = "0px" ; style . borderBottom = "none" ; style . borderRight = "none" ; return style ; default : return { display : "none" } ; } } ; private styleColumnHeaderRegion = ( region ) : React . CSSProperties => { const { viewportRect } = this . state ; if ( viewportRect == null ) { return { } ; } const cardinality = Regions . getRegionCardinality ( region ) ; const style = this . grid . getRegionStyle ( region ) ; switch ( cardinality ) { case RegionCardinality . FULL_TABLE : style . left = "-1px" ; style . borderLeft = "none" ; style . bottom = "-1px" ; return style ; case RegionCardinality . FULL_COLUMNS : style . bottom = "-1px" ; return style ; default : return { display : "none" } ; } } ; private styleRowHeaderRegion = ( region ) : React . CSSProperties => { const { viewportRect } = this . state ; if ( viewportRect == null ) { return { } ; } const cardinality = Regions . getRegionCardinality ( region ) ; const style = this . grid . getRegionStyle ( region ) ; switch ( cardinality ) { case RegionCardinality . FULL_TABLE : style . top = "-1px" ; style . borderTop = "none" ; style . right = "-1px" ; return style ; case RegionCardinality . FULL_ROWS : style . right = "-1px" ; return style ; default : return { display : "none" } ; } } ; private handleColumnWidthChanged = ( columnIndex , width ) => { const selectedRegions = this . state . selectedRegions ; const columnWidths = this . state . columnWidths . slice ( ) ; if ( Regions . hasFullTable ( selectedRegions ) ) { for ( let col = 0 ; col < columnWidths . length ; col ++ ) { columnWidths [ col ] = width ; } } if ( Regions . hasFullColumn ( selectedRegions , columnIndex ) ) { Regions . eachUniqueFullColumn ( selectedRegions , ( col ) => { columnWidths [ col ] = width ; } ) ; } else { columnWidths [ columnIndex ] = width ; } this . invalidateGrid ( ) ; this . setState ( { columnWidths } ) ; const { onColumnWidthChanged } = this . props ; if ( onColumnWidthChanged != null ) { onColumnWidthChanged ( columnIndex , width ) ; } } ; private handleRowHeightChanged = ( rowIndex , height ) => { const selectedRegions = this . state . selectedRegions ; const rowHeights = this . state . rowHeights . slice ( ) ; if ( Regions . hasFullTable ( selectedRegions ) ) { for ( let row = 0 ; row < rowHeights . length ; row ++ ) { rowHeights [ row ] = height ; } } if ( Regions . hasFullRow ( selectedRegions , rowIndex ) ) { Regions . eachUniqueFullRow ( selectedRegions , ( row ) => { rowHeights [ row ] = height ; } ) ; } else { rowHeights [ rowIndex ] = height ; } this . invalidateGrid ( ) ; this . setState ( { rowHeights } ) ; const { onRowHeightChanged } = this . props ; if ( onRowHeightChanged != null ) { onRowHeightChanged ( rowIndex , height ) ; } } ; private handleRootScroll = ( _event ) => { if ( this . rootTableElement != null ) { this . rootTableElement . scrollLeft = 0 ; this . rootTableElement . scrollTop = 0 ; } } ; private handleBodyScroll = ( event ) => { event . stopPropagation ( ) ; if ( this . locator != null && ! this . state . isLayoutLocked ) { const viewportRect = this . locator . getViewportRect ( ) ; this . updateViewportRect ( viewportRect ) ; } } ; private clearSelection = ( _selectedRegions ) => { this . handleSelection ( [ ] ) ; } ; private syncViewportPosition = ( { nextScrollLeft , nextScrollTop } ) => { const { viewportRect } = this . state ; if ( nextScrollLeft !== undefined || nextScrollTop !== undefined ) { if ( nextScrollTop !== undefined ) { const topCorrection = this . shouldDisableVerticalScroll ( ) ? 0 : this . columnHeaderElement . clientHeight ; this . scrollContainerElement . scrollTop = nextScrollTop + topCorrection ; } if ( nextScrollLeft !== undefined ) { const leftCorrection = this . shouldDisableHorizontalScroll ( ) || this . rowHeaderElement == null ? 0 : this . rowHeaderElement . clientWidth ; this . scrollContainerElement . scrollLeft = nextScrollLeft + leftCorrection ; } const nextViewportRect = new Rect ( nextScrollLeft , nextScrollTop , viewportRect . width , viewportRect . height ) ; this . updateViewportRect ( nextViewportRect ) ; } } ; private handleFocus = ( focusedCell ) => { if ( ! this . props . enableFocusedCell ) { return ; } if ( this . props . focusedCell == null ) { this . setState ( { focusedCell } ) ; } this . props . onFocusedCell ?. ( focusedCell ) ; } ; private handleSelection = ( selectedRegions ) => { if ( this . props . selectedRegions == null ) { this . setState ( { selectedRegions } ) ; } const { onSelection } = this . props ; if ( onSelection != null ) { onSelection ( selectedRegions ) ; } } ; private handleColumnsReordering = ( verticalGuides ) => { this . setState ( { isReordering : true , verticalGuides } ) ; } ; private handleColumnsReordered = ( oldIndex , newIndex , length ) => { this . setState ( { isReordering : false , verticalGuides : undefined } ) ; this . props . onColumnsReordered ?. ( oldIndex , newIndex , length ) ; } ; private handleRowsReordering = ( horizontalGuides ) => { this . setState ( { isReordering : true , horizontalGuides } ) ; } ; private handleRowsReordered = ( oldIndex , newIndex , length ) => { this . setState ( { isReordering : false , horizontalGuides : undefined } ) ; this . props . onRowsReordered ?. ( oldIndex , newIndex , length ) ; } ; private handleLayoutLock = ( isLayoutLocked = false ) => { this . setState ( { isLayoutLocked } ) ; } ; private updateLocator ( ) { this . locator . setGrid ( this . grid ) . setNumFrozenRows ( this . state . numFrozenRowsClamped ) . setNumFrozenColumns ( this . state . numFrozenColumnsClamped ) ; } private updateViewportRect = ( nextViewportRect ) => { const { viewportRect } = this . state ; this . setState ( { viewportRect : nextViewportRect } ) ; const didViewportChange = ( viewportRect != null && ! viewportRect . equals ( nextViewportRect ) ) || ( viewportRect == null && nextViewportRect != null ) ; if ( didViewportChange ) { this . invokeOnVisibleCellsChangeCallback ( nextViewportRect ) ; } } ; private invokeOnVisibleCellsChangeCallback ( viewportRect ) { const columnIndices = this . grid . getColumnIndicesInRect ( viewportRect ) ; const rowIndices = this . grid . getRowIndicesInRect ( viewportRect ) ; this . props . onVisibleCellsChange ?. ( rowIndices , columnIndices ) ; } private getMaxFrozenColumnIndex = ( ) => { const { numFrozenColumnsClamped : numFrozenColumns } = this . state ; return numFrozenColumns != null ? numFrozenColumns - 1 : undefined ; } ; private getMaxFrozenRowIndex = ( ) => { const { numFrozenRowsClamped : numFrozenRows } = this . state ; return numFrozenRows != null ? numFrozenRows - 1 : undefined ; } ; private getNormalizedRenderMode ( ) { const { renderMode } = this . props ; const shouldBatchRender = renderMode === RenderMode . BATCH || ( renderMode === RenderMode . BATCH_ON_UPDATE && this . didCompletelyMount ) ; return shouldBatchRender ? RenderMode . BATCH : RenderMode . NONE ; } private handleColumnResizeGuide = ( verticalGuides ) => { this . setState ( { verticalGuides } ) ; } ; private handleRowResizeGuide = ( horizontalGuides ) => { this . setState ( { horizontalGuides } ) ; } ; }