UNPKG

@blueprintjs/table

Version:

Scalable interactive table component

751 lines 68.9 kB
"use strict"; /* * Copyright 2021 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Table2 = void 0; var tslib_1 = require("tslib"); var classnames_1 = tslib_1.__importDefault(require("classnames")); var React = tslib_1.__importStar(require("react")); var react_innertext_1 = tslib_1.__importDefault(require("react-innertext")); var core_1 = require("@blueprintjs/core"); var column_1 = require("./column"); var cellTypes_1 = require("./common/cellTypes"); var Classes = tslib_1.__importStar(require("./common/classes")); var Errors = tslib_1.__importStar(require("./common/errors")); var grid_1 = require("./common/grid"); var FocusedCellUtils = tslib_1.__importStar(require("./common/internal/focusedCellUtils")); var ScrollUtils = tslib_1.__importStar(require("./common/internal/scrollUtils")); var rect_1 = require("./common/rect"); var renderMode_1 = require("./common/renderMode"); var scrollDirection_1 = require("./common/scrollDirection"); var utils_1 = require("./common/utils"); var columnHeader_1 = require("./headers/columnHeader"); var columnHeaderCell_1 = require("./headers/columnHeaderCell"); var rowHeader_1 = require("./headers/rowHeader"); var resizeSensor_1 = require("./interactions/resizeSensor"); var guides_1 = require("./layers/guides"); var regions_1 = require("./layers/regions"); var locator_1 = require("./locator"); var tableQuadrant_1 = require("./quadrants/tableQuadrant"); var tableQuadrantStack_1 = require("./quadrants/tableQuadrantStack"); var regions_2 = require("./regions"); var resizeRows_1 = require("./resizeRows"); var table2Utils_1 = require("./table2Utils"); var tableBody2_1 = require("./tableBody2"); var tableHotkeys_1 = require("./tableHotkeys"); var tableUtils_1 = require("./tableUtils"); /** * Table (v2) component. * * @see https://blueprintjs.com/docs/#table/table2 */ var Table2 = /** @class */ (function (_super) { tslib_1.__extends(Table2, _super); function Table2(props) { var _this = _super.call(this, props) || this; _this.hotkeys = []; _this.grid = null; _this.refHandlers = { cellContainer: function (ref) { return (_this.cellContainerElement = ref); }, columnHeader: function (ref) { if (ref != null) { _this.columnHeaderHeight = Math.max(ref.clientHeight, grid_1.Grid.MIN_COLUMN_HEADER_HEIGHT); } }, quadrantStack: function (ref) { return (_this.quadrantStackInstance = ref); }, rootTable: function (ref) { return (_this.rootTableElement = ref); }, rowHeader: function (ref) { if (ref != null) { _this.rowHeaderWidth = ref.clientWidth; } }, scrollContainer: function (ref) { return (_this.scrollContainerElement = ref); }, }; _this.columnHeaderHeight = grid_1.Grid.MIN_COLUMN_HEADER_HEIGHT; _this.rowHeaderWidth = grid_1.Grid.MIN_ROW_HEADER_WIDTH; _this.didColumnHeaderMount = false; _this.didRowHeaderMount = false; /* * This value is set to `true` when all cells finish mounting for the first * time. It serves as a signal that we can switch to batch rendering. */ _this.didCompletelyMount = false; _this.renderTableContents = function (_a) { var _b; var handleKeyDown = _a.handleKeyDown, handleKeyUp = _a.handleKeyUp; var _c = _this.props, children = _c.children, className = _c.className, enableRowHeader = _c.enableRowHeader, loadingOptions = _c.loadingOptions, numRows = _c.numRows, enableColumnInteractionBar = _c.enableColumnInteractionBar, enableColumnHeader = _c.enableColumnHeader; var _d = _this.state, horizontalGuides = _d.horizontalGuides, numFrozenColumnsClamped = _d.numFrozenColumnsClamped, numFrozenRowsClamped = _d.numFrozenRowsClamped, verticalGuides = _d.verticalGuides; if (!_this.gridDimensionsMatchProps()) { // Ensure we're rendering the correct number of rows & columns _this.invalidateGrid(); } var grid = _this.validateGrid(); var classes = (0, classnames_1.default)(Classes.TABLE_CONTAINER, (_b = {}, _b[Classes.TABLE_REORDERING] = _this.state.isReordering, _b[Classes.TABLE_NO_VERTICAL_SCROLL] = _this.shouldDisableVerticalScroll(), _b[Classes.TABLE_NO_HORIZONTAL_SCROLL] = _this.shouldDisableHorizontalScroll(), _b[Classes.TABLE_SELECTION_ENABLED] = (0, table2Utils_1.isSelectionModeEnabled)(_this.props, regions_2.RegionCardinality.CELLS), _b[Classes.TABLE_NO_ROWS] = numRows === 0, _b), className); return (React.createElement("div", { className: classes, ref: _this.refHandlers.rootTable, onScroll: _this.handleRootScroll, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, tabIndex: 0 }, React.createElement(tableQuadrantStack_1.TableQuadrantStack, { bodyRef: _this.refHandlers.cellContainer, bodyRenderer: _this.renderBody, columnHeaderRenderer: _this.renderColumnHeader, columnHeaderRef: _this.refHandlers.columnHeader, didHeadersMount: _this.state.didHeadersMount, enableColumnInteractionBar: enableColumnInteractionBar, enableRowHeader: enableRowHeader, grid: 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, rowHeaderRenderer: _this.renderRowHeader, rowHeaderRef: _this.refHandlers.rowHeader, scrollContainerRef: _this.refHandlers.scrollContainer, enableColumnHeader: enableColumnHeader, renderScrollIndicatorOverlay: _this.renderScrollIndicatorOverlay }), React.createElement("div", { className: (0, classnames_1.default)(Classes.TABLE_OVERLAY_LAYER, Classes.TABLE_OVERLAY_REORDERING_CURSOR) }), React.createElement(guides_1.GuideLayer, { className: Classes.TABLE_RESIZE_GUIDES, verticalGuides: verticalGuides, horizontalGuides: horizontalGuides }))); }; _this.renderMenu = function (refHandler) { var _a; var classes = (0, classnames_1.default)(Classes.TABLE_MENU, (_a = {}, _a[Classes.TABLE_SELECTION_ENABLED] = (0, table2Utils_1.isSelectionModeEnabled)(_this.props, regions_2.RegionCardinality.FULL_TABLE), _a)); return (React.createElement("div", { className: classes, ref: refHandler, onMouseDown: _this.handleMenuMouseDown }, _this.maybeRenderRegions(_this.styleMenuRegion))); }; _this.handleMenuMouseDown = function (e) { // the shift+click interaction expands the region from the focused cell. // thus, if shift is pressed we shouldn't move the focused cell. _this.selectAll(!e.shiftKey); }; _this.selectAll = function (shouldUpdateFocusedCell) { var selectionHandler = _this.getEnabledSelectionHandler(regions_2.RegionCardinality.FULL_TABLE); // clicking on upper left hand corner sets selection to "all" // regardless of current selection state (clicking twice does not deselect table) selectionHandler([regions_2.Regions.table()]); if (shouldUpdateFocusedCell) { var focusMode = FocusedCellUtils.getFocusModeFromProps(_this.props); var newFocusedCellCoordinates = regions_2.Regions.getFocusCellCoordinatesFromRegion(regions_2.Regions.table()); var newFocusedRegion = FocusedCellUtils.toFocusedRegion(focusMode, newFocusedCellCoordinates); if (newFocusedRegion != null) { _this.handleFocus(newFocusedRegion); } } }; _this.columnHeaderCellRenderer = function (columnIndex) { var _a; var columnProps = _this.getColumnProps(columnIndex); if (columnProps === undefined) { return null; } var id = columnProps.id, cellRenderer = columnProps.cellRenderer, columnHeaderCellRenderer = columnProps.columnHeaderCellRenderer, spreadableProps = tslib_1.__rest(columnProps, ["id", "cellRenderer", "columnHeaderCellRenderer"]); var columnLoading = (0, tableUtils_1.hasLoadingOption)(columnProps.loadingOptions, regions_2.ColumnLoadingOption.HEADER) || (0, tableUtils_1.hasLoadingOption)(_this.props.loadingOptions, regions_2.TableLoadingOption.COLUMN_HEADERS); if (columnHeaderCellRenderer != null) { var columnHeaderCell = columnHeaderCellRenderer(columnIndex); if (columnHeaderCell != null) { return React.cloneElement(columnHeaderCell, { enableColumnInteractionBar: _this.props.enableColumnInteractionBar, loading: (_a = columnHeaderCell.props.loading) !== null && _a !== void 0 ? _a : columnLoading, }); } } var baseProps = tslib_1.__assign({ enableColumnInteractionBar: _this.props.enableColumnInteractionBar, index: columnIndex, loading: columnLoading }, spreadableProps); if (columnProps.name != null) { return React.createElement(columnHeaderCell_1.ColumnHeaderCell, tslib_1.__assign({}, baseProps)); } else { return React.createElement(columnHeaderCell_1.ColumnHeaderCell, tslib_1.__assign({}, baseProps, { name: utils_1.Utils.toBase26Alpha(columnIndex) })); } }; _this.renderColumnHeader = function (refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly) { var _a; if (showFrozenColumnsOnly === void 0) { showFrozenColumnsOnly = false; } var _b = _this.state, focusedRegion = _b.focusedRegion, selectedRegions = _b.selectedRegions, viewportRect = _b.viewportRect; var _c = _this.props, defaultColumnWidth = _c.defaultColumnWidth, enableMultipleSelection = _c.enableMultipleSelection, enableGhostCells = _c.enableGhostCells, enableColumnReordering = _c.enableColumnReordering, enableColumnResizing = _c.enableColumnResizing, loadingOptions = _c.loadingOptions, maxColumnWidth = _c.maxColumnWidth, minColumnWidth = _c.minColumnWidth, selectedRegionTransform = _c.selectedRegionTransform; var classes = (0, classnames_1.default)(Classes.TABLE_COLUMN_HEADERS, (_a = {}, _a[Classes.TABLE_SELECTION_ENABLED] = (0, table2Utils_1.isSelectionModeEnabled)(_this.props, regions_2.RegionCardinality.FULL_COLUMNS), _a)); if (_this.grid === null || _this.locator === undefined || viewportRect === undefined) { // if we haven't mounted yet (which we need in order for grid/viewport calculations), // we still want to hand a DOM ref over to TableQuadrantStack for later return React.createElement("div", { className: classes, ref: refHandler }); } // if we have horizontal overflow or exact fit, no need to render ghost columns // (this avoids problems like https://github.com/palantir/blueprint/issues/5027) var hasHorizontalOverflowOrExactFit = _this.locator.hasHorizontalOverflowOrExactFit(_this.getRowHeaderWidth(), viewportRect); var columnIndices = _this.grid.getColumnIndicesInRect(viewportRect, hasHorizontalOverflowOrExactFit ? false : enableGhostCells); var columnIndexStart = showFrozenColumnsOnly ? 0 : columnIndices.columnIndexStart; var columnIndexEnd = showFrozenColumnsOnly ? _this.getMaxFrozenColumnIndex() : columnIndices.columnIndexEnd; return (React.createElement("div", { className: classes, ref: refHandler }, React.createElement(columnHeader_1.ColumnHeader, { defaultColumnWidth: defaultColumnWidth, enableMultipleSelection: enableMultipleSelection, cellRenderer: _this.columnHeaderCellRenderer, focusedRegion: focusedRegion, focusMode: FocusedCellUtils.getFocusModeFromProps(_this.props), grid: _this.grid, isReorderable: enableColumnReordering, isResizable: enableColumnResizing, loading: (0, tableUtils_1.hasLoadingOption)(loadingOptions, regions_2.TableLoadingOption.COLUMN_HEADERS), locator: _this.locator, maxColumnWidth: maxColumnWidth, minColumnWidth: minColumnWidth, onColumnWidthChanged: _this.handleColumnWidthChanged, onFocusedRegion: _this.handleFocus, onMount: _this.handleHeaderMounted, onLayoutLock: _this.handleLayoutLock, onReordered: _this.handleColumnsReordered, onReordering: reorderingHandler, onResizeGuide: resizeHandler, onSelection: _this.getEnabledSelectionHandler(regions_2.RegionCardinality.FULL_COLUMNS), selectedRegions: selectedRegions, selectedRegionTransform: selectedRegionTransform, columnIndexStart: columnIndexStart, columnIndexEnd: columnIndexEnd }, _this.props.children), _this.maybeRenderRegions(_this.styleColumnHeaderRegion))); }; _this.renderRowHeader = function (refHandler, resizeHandler, reorderingHandler, showFrozenRowsOnly) { var _a; if (showFrozenRowsOnly === void 0) { showFrozenRowsOnly = false; } var _b = _this.state, focusedRegion = _b.focusedRegion, selectedRegions = _b.selectedRegions, viewportRect = _b.viewportRect; var _c = _this.props, defaultRowHeight = _c.defaultRowHeight, enableMultipleSelection = _c.enableMultipleSelection, enableGhostCells = _c.enableGhostCells, enableRowReordering = _c.enableRowReordering, enableRowResizing = _c.enableRowResizing, loadingOptions = _c.loadingOptions, maxRowHeight = _c.maxRowHeight, minRowHeight = _c.minRowHeight, rowHeaderCellRenderer = _c.rowHeaderCellRenderer, selectedRegionTransform = _c.selectedRegionTransform; var classes = (0, classnames_1.default)(Classes.TABLE_ROW_HEADERS, (_a = {}, _a[Classes.TABLE_SELECTION_ENABLED] = (0, table2Utils_1.isSelectionModeEnabled)(_this.props, regions_2.RegionCardinality.FULL_ROWS), _a)); if (_this.grid === null || _this.locator === undefined || viewportRect === undefined) { // if we haven't mounted yet (which we need in order for grid/viewport calculations), // we still want to hand a DOM ref over to TableQuadrantStack for later return React.createElement("div", { className: classes, ref: refHandler }); } // if we have vertical overflow or exact fit, no need to render ghost rows // (this avoids problems like https://github.com/palantir/blueprint/issues/5027) var hasVerticalOverflowOrExactFit = _this.locator.hasVerticalOverflowOrExactFit(_this.getColumnHeaderHeight(), viewportRect); var rowIndices = _this.grid.getRowIndicesInRect({ includeGhostCells: hasVerticalOverflowOrExactFit ? false : enableGhostCells, rect: viewportRect, }); var rowIndexStart = showFrozenRowsOnly ? 0 : rowIndices.rowIndexStart; var rowIndexEnd = showFrozenRowsOnly ? _this.getMaxFrozenRowIndex() : rowIndices.rowIndexEnd; return (React.createElement("div", { className: classes, ref: refHandler }, React.createElement(rowHeader_1.RowHeader, { defaultRowHeight: defaultRowHeight, enableMultipleSelection: enableMultipleSelection, focusedRegion: focusedRegion, focusMode: FocusedCellUtils.getFocusModeFromProps(_this.props), grid: _this.grid, locator: _this.locator, isReorderable: enableRowReordering, isResizable: enableRowResizing, loading: (0, tableUtils_1.hasLoadingOption)(loadingOptions, regions_2.TableLoadingOption.ROW_HEADERS), maxRowHeight: maxRowHeight, minRowHeight: minRowHeight, onFocusedRegion: _this.handleFocus, onLayoutLock: _this.handleLayoutLock, onMount: _this.handleHeaderMounted, onResizeGuide: resizeHandler, onReordered: _this.handleRowsReordered, onReordering: reorderingHandler, onRowHeightChanged: _this.handleRowHeightChanged, onSelection: _this.getEnabledSelectionHandler(regions_2.RegionCardinality.FULL_ROWS), rowHeaderCellRenderer: rowHeaderCellRenderer, selectedRegions: selectedRegions, selectedRegionTransform: selectedRegionTransform, rowIndexStart: rowIndexStart, rowIndexEnd: rowIndexEnd }), _this.maybeRenderRegions(_this.styleRowHeaderRegion))); }; _this.bodyCellRenderer = function (rowIndex, columnIndex) { var _a; var columnProps = _this.getColumnProps(columnIndex); if (columnProps === undefined) { return undefined; } var id = columnProps.id, cellRenderer = columnProps.cellRenderer, columnHeaderCellRenderer = columnProps.columnHeaderCellRenderer, name = columnProps.name, nameRenderer = columnProps.nameRenderer, restColumnProps = tslib_1.__rest(columnProps, ["id", "cellRenderer", "columnHeaderCellRenderer", "name", "nameRenderer"]); // HACKHACK: cellRenderer prop has a default value, so we can assert non-null var cell = cellRenderer(rowIndex, columnIndex); if (cell === undefined) { return undefined; } var inheritedIsLoading = (0, tableUtils_1.hasLoadingOption)(columnProps.loadingOptions, regions_2.ColumnLoadingOption.CELLS) || (0, tableUtils_1.hasLoadingOption)(_this.props.loadingOptions, regions_2.TableLoadingOption.CELLS); return React.cloneElement(cell, tslib_1.__assign(tslib_1.__assign({}, restColumnProps), { loading: (_a = cell.props.loading) !== null && _a !== void 0 ? _a : inheritedIsLoading })); }; _this.renderBody = function (quadrantType, showFrozenRowsOnly, showFrozenColumnsOnly) { if (showFrozenRowsOnly === void 0) { showFrozenRowsOnly = false; } if (showFrozenColumnsOnly === void 0) { showFrozenColumnsOnly = false; } var _a = _this.state, focusedRegion = _a.focusedRegion, numFrozenColumns = _a.numFrozenColumnsClamped, numFrozenRows = _a.numFrozenRowsClamped, selectedRegions = _a.selectedRegions, viewportRect = _a.viewportRect; var _b = _this.props, enableMultipleSelection = _b.enableMultipleSelection, enableColumnHeader = _b.enableColumnHeader, enableGhostCells = _b.enableGhostCells, loadingOptions = _b.loadingOptions, bodyContextMenuRenderer = _b.bodyContextMenuRenderer, selectedRegionTransform = _b.selectedRegionTransform; if (_this.grid === null || _this.locator === undefined || viewportRect === undefined) { return undefined; } // if we have vertical/horizontal overflow or exact fit, no need to render ghost rows/columns (respectively) // (this avoids problems like https://github.com/palantir/blueprint/issues/5027) var hasVerticalOverflowOrExactFit = _this.locator.hasVerticalOverflowOrExactFit(enableColumnHeader ? _this.columnHeaderHeight : 0, viewportRect); var hasHorizontalOverflowOrExactFit = _this.locator.hasHorizontalOverflowOrExactFit(_this.getRowHeaderWidth(), viewportRect); var rowIndices = _this.grid.getRowIndicesInRect({ includeGhostCells: hasVerticalOverflowOrExactFit ? false : enableGhostCells, rect: viewportRect, }); var columnIndices = _this.grid.getColumnIndicesInRect(viewportRect, hasHorizontalOverflowOrExactFit ? false : enableGhostCells); // start beyond the frozen area if rendering unrelated quadrants, so we // don't render duplicate cells underneath the frozen ones. var columnIndexStart = showFrozenColumnsOnly ? 0 : columnIndices.columnIndexStart + numFrozenColumns; var rowIndexStart = showFrozenRowsOnly ? 0 : rowIndices.rowIndexStart + numFrozenRows; // if rendering frozen rows/columns, subtract one to convert to // 0-indexing. if the 1-indexed value is 0, this sets the end index // to -1, which avoids rendering absent frozen rows/columns at all. var columnIndexEnd = showFrozenColumnsOnly ? numFrozenColumns - 1 : columnIndices.columnIndexEnd; var rowIndexEnd = showFrozenRowsOnly ? numFrozenRows - 1 : rowIndices.rowIndexEnd; // the main quadrant contains all cells in the table, so listen only to that quadrant var onCompleteRender = quadrantType === tableQuadrant_1.QuadrantType.MAIN ? _this.handleCompleteRender : undefined; return (React.createElement("div", null, React.createElement(tableBody2_1.TableBody2, { enableMultipleSelection: enableMultipleSelection, cellRenderer: _this.bodyCellRenderer, focusedRegion: focusedRegion, focusMode: FocusedCellUtils.getFocusModeFromProps(_this.props), grid: _this.grid, loading: (0, tableUtils_1.hasLoadingOption)(loadingOptions, regions_2.TableLoadingOption.CELLS), locator: _this.locator, onCompleteRender: onCompleteRender, onFocusedRegion: _this.handleFocus, onSelection: _this.getEnabledSelectionHandler(regions_2.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))); }; _this.getEnabledSelectionHandler = function (selectionMode) { if (!(0, table2Utils_1.isSelectionModeEnabled)(_this.props, selectionMode)) { // If the selection mode isn't enabled, return a callback that // will clear the selection. For example, if row selection is // disabled, clicking on the row header will clear the table's // selection. If all selection modes are enabled, clicking on the // same region twice will clear the selection. return _this.clearSelection; } else { return _this.handleSelection; } }; /** * Renders a scroll indicator overlay on top of the table body inside the quadrant stack. * This component is offset by the headers and scrollbar, and it provides the overlay which * we use to render automatic scrolling indicator linear gradients. * * @param scrollBarWidth the calculated scroll bar width to be passed in by the quadrant stack * @param columnHeaderHeight the calculated column header height to be passed in by the quadrant stack * @returns A jsx element which will render a linear gradient with smooth transitions based on * state of the scroll (will not render if we are already at the top/left/right/bottom) * and the state of "scroll direction" */ _this.renderScrollIndicatorOverlay = function (scrollBarWidth, columnHeaderHeight) { var scrollDirection = _this.state.scrollDirection; var getStyle = function (direction, compare) { return { marginRight: scrollBarWidth, marginTop: columnHeaderHeight, opacity: direction === compare ? 1 : 0, }; }; var baseClass = Classes.TABLE_BODY_SCROLLING_INDICATOR_OVERLAY; return (React.createElement(React.Fragment, null, React.createElement("div", { className: (0, classnames_1.default)(baseClass, Classes.TABLE_BODY_IS_SCROLLING_TOP), style: getStyle(scrollDirection, scrollDirection_1.ScrollDirection.TOP) }), React.createElement("div", { className: (0, classnames_1.default)(baseClass, Classes.TABLE_BODY_IS_SCROLLING_BOTTOM), style: getStyle(scrollDirection, scrollDirection_1.ScrollDirection.BOTTOM) }), React.createElement("div", { className: (0, classnames_1.default)(baseClass, Classes.TABLE_BODY_IS_SCROLLING_RIGHT), style: getStyle(scrollDirection, scrollDirection_1.ScrollDirection.RIGHT) }), React.createElement("div", { className: (0, classnames_1.default)(baseClass, Classes.TABLE_BODY_IS_SCROLLING_LEFT), style: getStyle(scrollDirection, scrollDirection_1.ScrollDirection.LEFT) }))); }; _this.handleHeaderMounted = function (whichHeader) { var didHeadersMount = _this.state.didHeadersMount; if (didHeadersMount) { return; } if (whichHeader === "column") { _this.didColumnHeaderMount = true; } else { _this.didRowHeaderMount = true; } if (_this.didColumnHeaderMount && _this.didRowHeaderMount) { _this.setState({ didHeadersMount: true }); } }; _this.handleCompleteRender = function () { // The first onCompleteRender is triggered before the viewportRect is // defined and the second after the viewportRect has been set. The cells // will only actually render once the viewportRect is defined though, so // we defer invoking onCompleteRender until that check passes. var _a, _b; // Additional note: we run into an unfortunate race condition between the order of execution // of this callback and this.handleHeaderMounted(...). The setState() call in the latter // does not update this.state quickly enough for us to query for the new state here, so instead // we read the private member variables which are the dependent parts of that "didHeadersMount" // state. var didHeadersMount = _this.didColumnHeaderMount && _this.didRowHeaderMount; if (_this.state.viewportRect != null && didHeadersMount) { (_b = (_a = _this.props).onCompleteRender) === null || _b === void 0 ? void 0 : _b.call(_a); _this.didCompletelyMount = true; } }; _this.styleBodyRegion = function (region, quadrantType) { var numFrozenColumns = _this.props.numFrozenColumns; if (_this.grid == null) { return {}; } var cardinality = regions_2.Regions.getRegionCardinality(region); var style = _this.grid.getRegionStyle(region); // ensure we're not showing borders at the boundary of the frozen-columns area var canHideRightBorder = (quadrantType === tableQuadrant_1.QuadrantType.TOP_LEFT || quadrantType === tableQuadrant_1.QuadrantType.LEFT) && numFrozenColumns != null && numFrozenColumns > 0; var fixedHeight = _this.grid.getHeight(); var fixedWidth = _this.grid.getWidth(); // include a correction in some cases to hide borders along quadrant boundaries var alignmentCorrection = 1; var alignmentCorrectionString = "-".concat(alignmentCorrection, "px"); switch (cardinality) { case regions_2.RegionCardinality.CELLS: return style; case regions_2.RegionCardinality.FULL_COLUMNS: style.top = alignmentCorrectionString; style.height = fixedHeight + alignmentCorrection; return style; case regions_2.RegionCardinality.FULL_ROWS: style.left = alignmentCorrectionString; style.width = fixedWidth + alignmentCorrection; if (canHideRightBorder) { style.right = alignmentCorrectionString; } return style; case regions_2.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" }; } }; _this.styleMenuRegion = function (region) { var viewportRect = _this.state.viewportRect; if (_this.grid == null || viewportRect == null) { return {}; } var cardinality = regions_2.Regions.getRegionCardinality(region); var style = _this.grid.getRegionStyle(region); switch (cardinality) { case regions_2.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" }; } }; _this.styleColumnHeaderRegion = function (region) { var viewportRect = _this.state.viewportRect; if (_this.grid == null || viewportRect == null) { return {}; } var cardinality = regions_2.Regions.getRegionCardinality(region); var style = _this.grid.getRegionStyle(region); switch (cardinality) { case regions_2.RegionCardinality.FULL_TABLE: style.left = "-1px"; style.borderLeft = "none"; style.bottom = "-1px"; return style; case regions_2.RegionCardinality.FULL_COLUMNS: style.bottom = "-1px"; return style; default: return { display: "none" }; } }; _this.styleRowHeaderRegion = function (region) { var viewportRect = _this.state.viewportRect; if (_this.grid == null || viewportRect == null) { return {}; } var cardinality = regions_2.Regions.getRegionCardinality(region); var style = _this.grid.getRegionStyle(region); switch (cardinality) { case regions_2.RegionCardinality.FULL_TABLE: style.top = "-1px"; style.borderTop = "none"; style.right = "-1px"; return style; case regions_2.RegionCardinality.FULL_ROWS: style.right = "-1px"; return style; default: return { display: "none" }; } }; _this.handleColumnWidthChanged = function (columnIndex, width) { var _a, _b; var selectedRegions = _this.state.selectedRegions; var columnWidths = _this.state.columnWidths.slice(); if (regions_2.Regions.hasFullTable(selectedRegions)) { for (var col = 0; col < columnWidths.length; col++) { columnWidths[col] = width; } } if (regions_2.Regions.hasFullColumn(selectedRegions, columnIndex)) { regions_2.Regions.eachUniqueFullColumn(selectedRegions, function (col) { columnWidths[col] = width; }); } else { columnWidths[columnIndex] = width; } _this.validateGrid({ columnWidths: columnWidths }); _this.setState({ columnWidths: columnWidths }); (_b = (_a = _this.props).onColumnWidthChanged) === null || _b === void 0 ? void 0 : _b.call(_a, columnIndex, width); }; _this.handleRowHeightChanged = function (rowIndex, height) { var _a, _b; var selectedRegions = _this.state.selectedRegions; var rowHeights = _this.state.rowHeights.slice(); if (regions_2.Regions.hasFullTable(selectedRegions)) { for (var row = 0; row < rowHeights.length; row++) { rowHeights[row] = height; } } if (regions_2.Regions.hasFullRow(selectedRegions, rowIndex)) { regions_2.Regions.eachUniqueFullRow(selectedRegions, function (row) { rowHeights[row] = height; }); } else { rowHeights[rowIndex] = height; } _this.validateGrid({ rowHeights: rowHeights }); _this.setState({ rowHeights: rowHeights }); (_b = (_a = _this.props).onRowHeightChanged) === null || _b === void 0 ? void 0 : _b.call(_a, rowIndex, height); }; _this.handleRootScroll = function (_event) { // Bug #211 - Native browser text selection events can cause the root // element to scroll even though it has a overflow:hidden style. The // only viable solution to this is to unscroll the element after the // browser scrolls it. if (_this.rootTableElement != null) { _this.rootTableElement.scrollLeft = 0; _this.rootTableElement.scrollTop = 0; } }; _this.handleBodyScroll = function (event) { // Prevent the event from propagating to avoid a resize event on the resize sensor. event.stopPropagation(); if (_this.locator != null && !_this.state.isLayoutLocked) { var newViewportRect = _this.locator.getViewportRect(); _this.updateViewportRect(newViewportRect); } }; _this.clearSelection = function (_selectedRegions) { _this.handleSelection([]); }; _this.syncViewportPosition = function (_a) { var nextScrollLeft = _a.nextScrollLeft, nextScrollTop = _a.nextScrollTop; var viewportRect = _this.state.viewportRect; if (_this.scrollContainerElement == null || viewportRect === undefined) { return; } if (nextScrollLeft !== undefined || nextScrollTop !== undefined) { if (nextScrollTop !== undefined) { _this.scrollContainerElement.scrollTop = nextScrollTop; } if (nextScrollLeft !== undefined) { _this.scrollContainerElement.scrollLeft = nextScrollLeft; } var nextViewportRect = new rect_1.Rect(nextScrollLeft !== null && nextScrollLeft !== void 0 ? nextScrollLeft : viewportRect.left, nextScrollTop !== null && nextScrollTop !== void 0 ? nextScrollTop : viewportRect.top, viewportRect.width, viewportRect.height); _this.updateViewportRect(nextViewportRect); } }; _this.handleFocus = function (focusedRegion) { var _a, _b, _c, _d; if (FocusedCellUtils.getFocusModeFromProps(_this.props) !== (focusedRegion === null || focusedRegion === void 0 ? void 0 : focusedRegion.type)) { // don't set focus state if given focus mode is not enabled return; } // only set focused region state if not specified in props if (FocusedCellUtils.getFocusedRegionFromProps(_this.props) == null) { _this.setState({ focusedRegion: focusedRegion }); } if (focusedRegion == null) { return; } if (focusedRegion.type === cellTypes_1.FocusMode.CELL) { var type = focusedRegion.type, focusedCell = tslib_1.__rest(focusedRegion, ["type"]); // eslint-disable-next-line @typescript-eslint/no-deprecated (_b = (_a = _this.props).onFocusedCell) === null || _b === void 0 ? void 0 : _b.call(_a, focusedCell); } (_d = (_c = _this.props).onFocusedRegion) === null || _d === void 0 ? void 0 : _d.call(_c, focusedRegion); }; _this.handleSelection = function (selectedRegions) { // only set selectedRegions state if not specified in props if (_this.props.selectedRegions == null) { _this.setState({ selectedRegions: selectedRegions }); } var onSelection = _this.props.onSelection; if (onSelection != null) { onSelection(selectedRegions); } }; _this.handleColumnsReordering = function (verticalGuides) { _this.setState({ isReordering: true, verticalGuides: verticalGuides }); }; _this.handleColumnsReordered = function (oldIndex, newIndex, length) { var _a, _b; _this.setState({ isReordering: false, verticalGuides: [] }); (_b = (_a = _this.props).onColumnsReordered) === null || _b === void 0 ? void 0 : _b.call(_a, oldIndex, newIndex, length); }; _this.handleRowsReordering = function (horizontalGuides) { _this.setState({ horizontalGuides: horizontalGuides, isReordering: true }); }; _this.handleRowsReordered = function (oldIndex, newIndex, length) { var _a, _b; _this.setState({ horizontalGuides: [], isReordering: false }); (_b = (_a = _this.props).onRowsReordered) === null || _b === void 0 ? void 0 : _b.call(_a, oldIndex, newIndex, length); }; _this.handleLayoutLock = function (isLayoutLocked) { if (isLayoutLocked === void 0) { isLayoutLocked = false; } _this.setState({ isLayoutLocked: isLayoutLocked }); }; _this.updateViewportRect = function (nextViewportRect) { if (nextViewportRect === undefined) { return; } var viewportRect = _this.state.viewportRect; _this.setState({ viewportRect: nextViewportRect }); var didViewportChange = (viewportRect != null && !viewportRect.equals(nextViewportRect)) || (viewportRect == null && nextViewportRect != null); if (didViewportChange) { _this.invokeOnVisibleCellsChangeCallback(nextViewportRect); } }; _this.getMaxFrozenColumnIndex = function () { return _this.state.numFrozenColumnsClamped - 1; }; _this.getMaxFrozenRowIndex = function () { return _this.state.numFrozenRowsClamped - 1; }; _this.handleColumnResizeGuide = function (verticalGuides) { _this.setState({ verticalGuides: verticalGuides }); }; _this.handleRowResizeGuide = function (horizontalGuides) { _this.setState({ horizontalGuides: horizontalGuides }); }; _this.getHeaderDimensions = function () { return { columnHeaderHeight: _this.getColumnHeaderHeight(), rowHeaderWidth: _this.getRowHeaderWidth(), }; }; _this.getColumnHeaderHeight = function () { return _this.props.enableColumnHeader ? _this.columnHeaderHeight : 0; }; _this.getRowHeaderWidth = function () { return _this.props.enableRowHeader ? _this.rowHeaderWidth : 0; }; var children = props.children, columnWidths = props.columnWidths, defaultRowHeight = props.defaultRowHeight, defaultColumnWidth = props.defaultColumnWidth, enableRowHeader = props.enableRowHeader, numRows = props.numRows, rowHeights = props.rowHeights, _a = props.selectedRegions, selectedRegions = _a === void 0 ? [] : _a, enableColumnHeader = props.enableColumnHeader; var childrenArray = React.Children.toArray(children); var columnIdToIndex = Table2.createColumnIdIndex(childrenArray); // Create height/width arrays using the lengths from props and // children, the default values from props, and finally any sparse // arrays passed into props. var newColumnWidths = childrenArray.map(function () { return defaultColumnWidth; }); if (columnWidths !== undefined) { newColumnWidths = utils_1.Utils.assignSparseValues(newColumnWidths, columnWidths); } var newRowHeights = utils_1.Utils.times(numRows, function () { return defaultRowHeight; }); if (rowHeights !== undefined) { newRowHeights = utils_1.Utils.assignSparseValues(newRowHeights, rowHeights); } var focusedRegion = FocusedCellUtils.getInitialFocusedRegion(FocusedCellUtils.getFocusModeFromProps(props), FocusedCellUtils.getFocusedRegionFromProps(props), undefined, selectedRegions); _this.state = { childrenArray: childrenArray, columnIdToIndex: columnIdToIndex, columnWidths: newColumnWidths, didHeadersMount: false, focusedRegion: focusedRegion, horizontalGuides: [], isLayoutLocked: false, isReordering: false, numFrozenColumnsClamped: (0, tableUtils_1.clampNumFrozenColumns)(props), numFrozenRowsClamped: (0, tableUtils_1.clampNumFrozenRows)(props), rowHeights: newRowHeights, selectedRegions: selectedRegions, verticalGuides: [], }; _this.hotkeysImpl = new tableHotkeys_1.TableHotkeys(props, _this.state, { getEnabledSelectionHandler: _this.getEnabledSelectionHandler, getHeaderDimensions: _this.getHeaderDimensions, handleFocus: _this.handleFocus, handleSelection: _this.handleSelection, syncViewportPosition: _this.syncViewportPosition, }); _this.hotkeys = (0, table2Utils_1.getHotkeysFromProps)(props, _this.hotkeysImpl); if (enableRowHeader === false) { _this.didRowHeaderMount = true; } if (enableColumnHeader === false) { _this.didColumnHeaderMount = true; } return _this; } Table2.getDerivedStateFromProps = function (props, state) { var children = props.children, defaultColumnWidth = props.defaultColumnWidth, defaultRowHeight = props.defaultRowHeight, numRows = props.numRows, selectedRegions = props.selectedRegions, selectionModes = props.selectionModes; // assign values from state if uncontrolled var columnWidths = props.columnWidths, rowHeights = props.rowHeights; if (columnWidths == null) { columnWidths = state.columnWidths; } if (rowHeights == null) { rowHeights = state.rowHeights; } var newChildrenArray = React.Children.toArray(children); var didChildrenChange = !(0, table2Utils_1.compareChildren)(newChildrenArray, state.childrenArray); var numCols = newChildrenArray.length; var newColumnWidths = columnWidths; if (columnWidths !== state.columnWidths || didChildrenChange) { // Try to maintain widths of columns by looking up the width of the // column that had the same `ID` prop. If none is found, use the // previous width at the same index. var previousColumnWidths = newChildrenArray.map(function (child, index) { var mappedIndex = child.props.id === undefined ? undefined : state.columnIdToIndex[child.props.id]; return state.columnWidths[mappedIndex !== null && mappedIndex !== void 0 ? mappedIndex : index]; }); // Make sure the width/height arrays have the correct length, but keep // as many existing widths/heights as possible. Also, apply the // sparse width/heights from props. newColumnWidths = Array(numCols).fill(defaultColumnWidth); newColumnWidths = utils_1.Utils.assignSparseValues(newColumnWidths, previousColumnWidths); newColumnWidths = utils_1.Utils.assignSparseValues(newColumnWidths, columnWidths); } var newRowHeights = rowHeights; if (rowHeights !== state.rowHeights || numRows !== state.rowHeights.length) { newRowHeights = Array(numRows).fill(defaultRowHeight); newRowHeights = utils_1.Utils.assignSparseValues(newRowHeights, rowHeights); } var newSelectedRegions = selectedRegions !== null && selectedRegions !== void 0 ? selectedRegions : state.selectedRegions.filter(function (region) { // if we're in uncontrolled mode, filter out all selected regions that don't // fit in the current new table dimensions var regionCardinality = regions_2.Regions.getRegionCardinality(region); return ((0, table2Utils_1.isSelectionModeEnabled)(props, regionCardinality, selectionModes) && regions_2.Regions.isRegionValidForTable(region, numRows, numCols)); }); var newFocusedRegion = FocusedCellUtils.getInitialFocusedRegion(FocusedCellUtils.getFocusModeFromProps(props), FocusedCellUtils.getFocusedRegionFromProps(props), state.focusedRegion, newSelectedRegions); var nextState = { childrenArray: newChildrenArray, columnIdToIndex: didChildrenChange ? Table2.createColumnIdIndex(newChildrenArray) : state.columnIdToIndex, columnWidths: newColumnWidths, focusedRegion: newFocusedRegion, numFrozenColumnsClamped: (0, tableUtils_1.clampNumFrozenColumns)(props), numFrozenRowsClamped: (0, tableUtils_1.clampNumFrozenRows)(props), rowHeights: newRowHeights, selectedRegions: newSelectedRegions, }; if (!core_1.Utils.deepCompareKeys(state, nextState, Table2.SHALLOW_COMPARE_STATE_KEYS_DENYLIST)) { return nextState; } return null; }; Table2.createColumnIdIndex = function (children) { var columnIdToIndex = {}; for (var i = 0; i < children.length; i++) { var key = children[i].props.id; if (key != null) { columnIdToIndex[String(key)] = i; } } return columnIdToIndex; }; // Instance methods // ================ /** * __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. */ Table2.prototype.resizeRowsByApproximateHeight = function (getCellText, options) { var rowHeights = (0, resizeRows_1.resizeRowsByApproximateHeight)(this.props.numRows, this.state.columnWidths, getCellText, options); this.invalidateGrid(); this.setState({ rowHeights: 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. */ Table2.prototype.resizeRowsByTallestCell = function (columnIndices) { if (this.grid == null || this.state.viewportRect === undefined || this.locator === undefined) { console.warn(Errors.TABLE_UNMOUNTED_RESIZE_WARNING); return; } var rowHeights = (0, resizeRows_1.resizeRowsByTallestCell)(this.grid, this.state.viewportRect, this.locator, this.state.rowHeights.length, columnIndices); this.invalidateGrid(); this.setState({ rowHeights: 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 f