UNPKG

@blueprintjs/table

Version:

Scalable interactive table component

198 lines 8.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TableBodyCells = void 0; exports.cellClassNames = cellClassNames; const tslib_1 = require("tslib"); const jsx_runtime_1 = require("react/jsx-runtime"); /* * Copyright 2017 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const classnames_1 = tslib_1.__importDefault(require("classnames")); const react_1 = require("react"); const core_1 = require("@blueprintjs/core"); const cell_1 = require("./cell/cell"); const batcher_1 = require("./common/batcher"); const cellTypes_1 = require("./common/cellTypes"); const Classes = tslib_1.__importStar(require("./common/classes")); const rect_1 = require("./common/rect"); const renderMode_1 = require("./common/renderMode"); 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", ]; class TableBodyCells extends core_1.AbstractComponent { static defaultProps = { renderMode: renderMode_1.RenderMode.BATCH, }; static cellReactKey(rowIndex, columnIndex) { return `cell-${rowIndex}-${columnIndex}`; } batcher = new batcher_1.Batcher(); /** * Set this flag to true in componentDidUpdate() when we call forceUpdate() to avoid an extra * unnecessary update cycle. */ didForceUpdate = false; componentDidMount() { this.maybeInvokeOnCompleteRender(); } shouldComponentUpdate(nextProps) { return (!core_1.Utils.shallowCompareKeys(nextProps, this.props, { exclude: SHALLOW_COMPARE_DENYLIST, }) || // "viewportRect" is not a plain object, so we can't just deep // compare; we need custom logic. this.didViewportRectChange(nextProps.viewportRect, this.props.viewportRect)); } componentDidUpdate(prevProps) { if (this.didForceUpdate) { this.didForceUpdate = false; return; } const shouldResetBatcher = !core_1.Utils.shallowCompareKeys(prevProps, this.props, { exclude: BATCHER_RESET_PROP_KEYS_DENYLIST, }); if (shouldResetBatcher) { this.batcher.reset(); // At this point, the batcher is reset, but it doesn't have a chance to re-run since render() is not called // by default after this lifecycle method. This causes issues like https://github.com/palantir/blueprint/issues/5193. // We can run forceUpdate() to re-render, but must take care to set a local flag indicating that we are doing so, // so that this lifecycle method doesn't get re-run as well within the same forced update cycle. this.didForceUpdate = true; this.forceUpdate(); } this.maybeInvokeOnCompleteRender(); } componentWillUnmount() { this.batcher.cancelOutstandingCallback(); } render() { const { renderMode } = this.props; const cells = renderMode === renderMode_1.RenderMode.BATCH ? this.renderBatchedCells() : this.renderAllCells(); return (0, jsx_runtime_1.jsx)("div", { className: Classes.TABLE_BODY_CELLS, children: cells }); } // Render modes // ============ renderBatchedCells() { const { columnIndexEnd, columnIndexStart, rowIndexEnd, rowIndexStart } = this.props; // render cells in batches 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(); } 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]); } } // pretend we did an entire rendering pass using the batcher. that way, // if we switch from `RenderMode.NONE` to `RenderMode.BATCH`, we don't // have to re-paint every cell still in view. this.batcher.setList(cellsArgs, cells); return cells; } // Cell renderers // ============== 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); }; renderCell = (rowIndex, columnIndex, extremaClasses, isGhost) => { const { cellRenderer, loading, grid } = this.props; let baseCell = isGhost ? (0, cell_1.emptyCellRenderer)() : cellRenderer(rowIndex, columnIndex); // cellRenderer still may return null baseCell = baseCell == null ? (0, cell_1.emptyCellRenderer)() : baseCell; const className = (0, classnames_1.default)(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_1.Rect.style(rect) }; const isFocused = this.isCellFocused(rowIndex, columnIndex); return (0, react_1.cloneElement)(baseCell, { className, isFocused, key, loading: cellLoading, style, }); }; isCellFocused(rowIndex, columnIndex) { const { focusedRegion } = this.props; switch (focusedRegion?.type) { case cellTypes_1.FocusMode.CELL: return focusedRegion.row === rowIndex && focusedRegion.col === columnIndex; case cellTypes_1.FocusMode.ROW: return focusedRegion.row === rowIndex; case undefined: return false; } } // Callbacks // ========= maybeInvokeOnCompleteRender() { const { onCompleteRender, renderMode } = this.props; if (renderMode === renderMode_1.RenderMode.NONE || (renderMode === renderMode_1.RenderMode.BATCH && this.batcher.isDone())) { onCompleteRender?.(); } } // Other // ===== didViewportRectChange = (nextViewportRect, currViewportRect) => { if (nextViewportRect == null && currViewportRect == null) { return false; } else if (nextViewportRect == null || currViewportRect == null) { return true; } else { return !nextViewportRect.equals(currViewportRect); } }; } exports.TableBodyCells = TableBodyCells; /** * 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. */ function cellClassNames(rowIndex, columnIndex) { return [Classes.rowCellIndexClass(rowIndex), Classes.columnCellIndexClass(columnIndex)]; } //# sourceMappingURL=tableBodyCells.js.map