UNPKG

@blueprintjs/table

Version:

Scalable interactive table component

653 lines 38.9 kB
/* * 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. */ import { __assign, __extends } from "tslib"; import * as React from "react"; import { AbstractComponent, Utils as CoreUtils, setRef } from "@blueprintjs/core"; import * as Classes from "../common/classes"; import { Grid } from "../common/grid"; import * as ScrollUtils from "../common/internal/scrollUtils"; import { Utils } from "../common/utils"; import { QuadrantType, TableQuadrant } from "./tableQuadrant"; import { TableQuadrantStackCache } from "./tableQuadrantStackCache"; // the debounce delay for updating the view on scroll. elements will be resized // and rejiggered once scroll has ceased for at least this long, but not before. var DEFAULT_VIEW_SYNC_DELAY = 500; // if there are no frozen rows or columns, we still want the quadrant to be 1px // bigger to reveal the header border. this border leaks into the cell grid to // ensure that selection overlay borders (e.g.) will be perfectly flush with it. var QUADRANT_MIN_SIZE = 1; // a list of props that trigger layout changes. when these props change, // quadrant views need to be explicitly resynchronized. var SYNC_TRIGGER_PROP_KEYS = [ "enableRowHeader", "loadingOptions", "numFrozenColumns", "numFrozenRows", "numColumns", "numRows", "enableColumnInteractionBar", "didHeadersMount", "enableColumnHeader", ]; var TableQuadrantStack = /** @class */ (function (_super) { __extends(TableQuadrantStack, _super); // Public // ====== function TableQuadrantStack(props) { var _a, _b; var _this = _super.call(this, props) || this; // Instance variables // ================== _this.quadrantRefs = (_a = {}, _a[QuadrantType.MAIN] = {}, _a[QuadrantType.TOP] = {}, _a[QuadrantType.LEFT] = {}, _a[QuadrantType.TOP_LEFT] = {}, _a); _this.quadrantRefHandlers = (_b = {}, _b[QuadrantType.MAIN] = _this.generateQuadrantRefHandlers(QuadrantType.MAIN), _b[QuadrantType.TOP] = _this.generateQuadrantRefHandlers(QuadrantType.TOP), _b[QuadrantType.LEFT] = _this.generateQuadrantRefHandlers(QuadrantType.LEFT), _b[QuadrantType.TOP_LEFT] = _this.generateQuadrantRefHandlers(QuadrantType.TOP_LEFT), _b); // this flag helps us avoid redundant work in the MAIN quadrant's onScroll callback, if the // callback was triggered from a manual scrollTop/scrollLeft update within an onWheel. _this.wasMainQuadrantScrollTriggeredByWheelEvent = false; // Scrolling overlay renderer // =========================== _this.renderTableOverlay = function () { var _a, _b; var columnHeaderHeight = _this.cache.getColumnHeaderHeight(); var mainScrollContainer = _this.quadrantRefs[QuadrantType.MAIN].scrollContainer; var scrollBarWidth = ScrollUtils.measureScrollBarThickness(mainScrollContainer, "vertical"); return (_b = (_a = _this.props).renderScrollIndicatorOverlay) === null || _b === void 0 ? void 0 : _b.call(_a, scrollBarWidth, columnHeaderHeight); }; // Quadrant-specific renderers // =========================== // Menu _this.renderMainQuadrantMenu = function () { var _a, _b; return (_b = (_a = _this.props).menuRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, _this.quadrantRefHandlers[QuadrantType.MAIN].menu); }; _this.renderTopQuadrantMenu = function () { var _a, _b; return (_b = (_a = _this.props).menuRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, _this.quadrantRefHandlers[QuadrantType.TOP].menu); }; _this.renderLeftQuadrantMenu = function () { var _a, _b; return (_b = (_a = _this.props).menuRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, _this.quadrantRefHandlers[QuadrantType.LEFT].menu); }; _this.renderTopLeftQuadrantMenu = function () { var _a, _b; return (_b = (_a = _this.props).menuRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, _this.quadrantRefHandlers[QuadrantType.TOP_LEFT].menu); }; // Column header _this.renderMainQuadrantColumnHeader = function (showFrozenColumnsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.MAIN].columnHeader; var resizeHandler = _this.handleColumnResizeGuideMain; var reorderingHandler = _this.handleColumnsReordering; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).columnHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly); }; _this.renderTopQuadrantColumnHeader = function (showFrozenColumnsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.TOP].columnHeader; var resizeHandler = _this.handleColumnResizeGuideTop; var reorderingHandler = _this.handleColumnsReordering; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).columnHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly); }; _this.renderLeftQuadrantColumnHeader = function (showFrozenColumnsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.LEFT].columnHeader; var resizeHandler = _this.handleColumnResizeGuideLeft; var reorderingHandler = _this.handleColumnsReordering; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).columnHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly); }; _this.renderTopLeftQuadrantColumnHeader = function (showFrozenColumnsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.TOP_LEFT].columnHeader; var resizeHandler = _this.handleColumnResizeGuideTopLeft; var reorderingHandler = _this.handleColumnsReordering; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).columnHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, resizeHandler, reorderingHandler, showFrozenColumnsOnly); }; // Row header _this.renderMainQuadrantRowHeader = function (showFrozenRowsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.MAIN].rowHeader; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).rowHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, _this.handleRowResizeGuideMain, _this.handleRowsReordering, showFrozenRowsOnly); }; _this.renderTopQuadrantRowHeader = function (showFrozenRowsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.TOP].rowHeader; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).rowHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, _this.handleRowResizeGuideTop, _this.handleRowsReordering, showFrozenRowsOnly); }; _this.renderLeftQuadrantRowHeader = function (showFrozenRowsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.LEFT].rowHeader; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).rowHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, _this.handleRowResizeGuideLeft, _this.handleRowsReordering, showFrozenRowsOnly); }; _this.renderTopLeftQuadrantRowHeader = function (showFrozenRowsOnly) { var _a, _b; var refHandler = _this.quadrantRefHandlers[QuadrantType.TOP_LEFT].rowHeader; if (refHandler === undefined) { return undefined; } return (_b = (_a = _this.props).rowHeaderRenderer) === null || _b === void 0 ? void 0 : _b.call(_a, refHandler, _this.handleRowResizeGuideTopLeft, _this.handleRowsReordering, showFrozenRowsOnly); }; // Event handlers // ============== // Scrolling // --------- _this.handleMainQuadrantScroll = function (event) { var _a, _b; if (_this.wasMainQuadrantScrollTriggeredByWheelEvent) { _this.wasMainQuadrantScrollTriggeredByWheelEvent = false; return; } var mainScrollContainer = _this.quadrantRefs[QuadrantType.MAIN].scrollContainer; if (mainScrollContainer == null) { return; } // invoke onScroll - which may read current scroll position - before // forcing a reflow with upcoming .scroll{Top,Left} setters. (_b = (_a = _this.props).onScroll) === null || _b === void 0 ? void 0 : _b.call(_a, event); // batch DOM reads here. note that onScroll events don't include deltas // like onWheel events do, so we have to read from the DOM directly. var nextScrollLeft = mainScrollContainer.scrollLeft; var nextScrollTop = mainScrollContainer.scrollTop; // with the "scroll" event, scroll offsets are updated prior to the // event's firing, so no explicit update needed. _this.handleScrollOffsetChange("scrollLeft", nextScrollLeft); _this.handleScrollOffsetChange("scrollTop", nextScrollTop); // sync less important view stuff when scrolling/wheeling stops. _this.syncQuadrantViewsDebounced(); }; _this.handleWheel = function (event) { var _a, _b; // again, let the listener read the current scroll position before we // force a reflow by resizing or repositioning stuff. (_b = (_a = _this.props).onScroll) === null || _b === void 0 ? void 0 : _b.call(_a, event); // this helper performs DOM reads, so do them together before the writes below. var nextScrollLeft = _this.getNextScrollOffset("horizontal", event.deltaX); var nextScrollTop = _this.getNextScrollOffset("vertical", event.deltaY); // update this flag before updating the main quadrant scroll offsets, // since we need this set before onScroll fires. if (nextScrollLeft != null || nextScrollTop != null) { _this.wasMainQuadrantScrollTriggeredByWheelEvent = true; } // manually update the affected quadrant's scroll position to make sure // it stays perfectly in sync with dependent quadrants in each frame. // note: these DOM writes are batched together after the reads above. if (nextScrollLeft !== undefined) { _this.quadrantRefs[QuadrantType.MAIN].scrollContainer.scrollLeft = nextScrollLeft; _this.handleScrollOffsetChange("scrollLeft", nextScrollLeft); } if (nextScrollTop !== undefined) { _this.quadrantRefs[QuadrantType.MAIN].scrollContainer.scrollTop = nextScrollTop; _this.handleScrollOffsetChange("scrollTop", nextScrollTop); } // sync less important view stuff when scrolling/wheeling stops. _this.syncQuadrantViewsDebounced(); }; _this.getNextScrollOffset = function (direction, delta) { var _a = _this.props, grid = _a.grid, isHorizontalScrollDisabled = _a.isHorizontalScrollDisabled, isVerticalScrollDisabled = _a.isVerticalScrollDisabled; var isHorizontal = direction === "horizontal"; var scrollKey = isHorizontal ? "scrollLeft" : "scrollTop"; var isScrollDisabled = isHorizontal ? isHorizontalScrollDisabled : isVerticalScrollDisabled; if (isScrollDisabled) { return undefined; } // measure client size on the first event of the current wheel gesture, // then grab cached values on successive events to eliminate DOM reads. // requires clearing the cached values in the debounced view-update at // the end of the wheel event. // ASSUMPTION: the client size won't change during the wheel event. var clientSize = isHorizontal ? _this.cache.getScrollContainerClientWidth() : _this.cache.getScrollContainerClientHeight(); if (clientSize == null) { // should trigger only on the first scroll of the wheel gesture. // will save client width and height sizes in the cache. clientSize = _this.updateScrollContainerClientSize(isHorizontal); } // By now, the client width and height will have been saved in cache, so // they can't be undefined anymore. Also, events can only happen after // mount, so we're guaranteed to have measured the header sizes in // syncQuadrantViews() by now too, as it's invoked on mount. var containerSize = isHorizontal ? _this.cache.getScrollContainerClientWidth() - _this.cache.getRowHeaderWidth() : _this.cache.getScrollContainerClientHeight() - _this.cache.getColumnHeaderHeight(); var gridSize = isHorizontal ? grid.getWidth() : grid.getHeight(); var maxScrollOffset = Math.max(0, gridSize - containerSize); var currScrollOffset = _this.cache.getScrollOffset(scrollKey); var nextScrollOffset = CoreUtils.clamp(currScrollOffset + delta, 0, maxScrollOffset); return nextScrollOffset; }; // Resizing // -------- // Columns _this.handleColumnResizeGuideMain = function (verticalGuides) { _this.invokeColumnResizeHandler(verticalGuides, QuadrantType.MAIN); }; _this.handleColumnResizeGuideTop = function (verticalGuides) { _this.invokeColumnResizeHandler(verticalGuides, QuadrantType.TOP); }; _this.handleColumnResizeGuideLeft = function (verticalGuides) { _this.invokeColumnResizeHandler(verticalGuides, QuadrantType.LEFT); }; _this.handleColumnResizeGuideTopLeft = function (verticalGuides) { _this.invokeColumnResizeHandler(verticalGuides, QuadrantType.TOP_LEFT); }; _this.invokeColumnResizeHandler = function (verticalGuides, quadrantType) { var _a, _b; var adjustedGuides = _this.adjustVerticalGuides(verticalGuides, quadrantType); (_b = (_a = _this.props).handleColumnResizeGuide) === null || _b === void 0 ? void 0 : _b.call(_a, adjustedGuides); }; // Rows _this.handleRowResizeGuideMain = function (horizontalGuides) { _this.invokeRowResizeHandler(horizontalGuides, QuadrantType.MAIN); }; _this.handleRowResizeGuideTop = function (horizontalGuides) { _this.invokeRowResizeHandler(horizontalGuides, QuadrantType.TOP); }; _this.handleRowResizeGuideLeft = function (horizontalGuides) { _this.invokeRowResizeHandler(horizontalGuides, QuadrantType.LEFT); }; _this.handleRowResizeGuideTopLeft = function (horizontalGuides) { _this.invokeRowResizeHandler(horizontalGuides, QuadrantType.TOP_LEFT); }; _this.invokeRowResizeHandler = function (horizontalGuides, quadrantType) { var _a, _b; var adjustedGuides = _this.adjustHorizontalGuides(horizontalGuides, quadrantType); (_b = (_a = _this.props).handleRowResizeGuide) === null || _b === void 0 ? void 0 : _b.call(_a, adjustedGuides); }; // Reordering // ---------- // Columns _this.handleColumnsReordering = function (oldIndex, newIndex, length) { var _a, _b; var guideIndex = Utils.reorderedIndexToGuideIndex(oldIndex, newIndex, length); var leftOffset = _this.props.grid.getCumulativeWidthBefore(guideIndex); var _c = _this.props.numFrozenColumns, numFrozenColumns = _c === void 0 ? 0 : _c; var quadrantType = guideIndex <= numFrozenColumns ? QuadrantType.TOP_LEFT : QuadrantType.TOP; var verticalGuides = _this.adjustVerticalGuides([leftOffset], quadrantType); (_b = (_a = _this.props).handleColumnsReordering) === null || _b === void 0 ? void 0 : _b.call(_a, verticalGuides); }; // Rows _this.handleRowsReordering = function (oldIndex, newIndex, length) { var _a, _b; var guideIndex = Utils.reorderedIndexToGuideIndex(oldIndex, newIndex, length); var topOffset = _this.props.grid.getCumulativeHeightBefore(guideIndex); var _c = _this.props.numFrozenRows, numFrozenRows = _c === void 0 ? 0 : _c; var quadrantType = guideIndex <= numFrozenRows ? QuadrantType.TOP_LEFT : QuadrantType.LEFT; var horizontalGuides = _this.adjustHorizontalGuides([topOffset], quadrantType); (_b = (_a = _this.props).handleRowsReordering) === null || _b === void 0 ? void 0 : _b.call(_a, horizontalGuides); }; // Size syncing // ============ _this.syncQuadrantViewsDebounced = function () { var _a; var viewSyncDelay = _this.props.viewSyncDelay; if (viewSyncDelay < 0) { // update synchronously _this.syncQuadrantViews(); } else { // update asynchronously after a debounced delay (_a = _this.cancelPendingViewSync) === null || _a === void 0 ? void 0 : _a.call(_this); _this.cancelPendingViewSync = _this.setTimeout(_this.syncQuadrantViews, viewSyncDelay); } }; _this.syncQuadrantViews = function () { var mainRefs = _this.quadrantRefs[QuadrantType.MAIN]; var mainScrollContainer = mainRefs.scrollContainer; // // Reads (batched to avoid DOM thrashing) // var rowHeaderWidth = _this.measureDesiredRowHeaderWidth(); var columnHeaderHeight = _this.measureDesiredColumnHeaderHeight(); var leftQuadrantGridWidth = _this.getSecondaryQuadrantGridSize("width"); var topQuadrantGridHeight = _this.getSecondaryQuadrantGridSize("height"); var leftQuadrantWidth = rowHeaderWidth + leftQuadrantGridWidth; var topQuadrantHeight = columnHeaderHeight + topQuadrantGridHeight; var rightScrollBarWidth = ScrollUtils.measureScrollBarThickness(mainScrollContainer, "vertical"); var bottomScrollBarHeight = ScrollUtils.measureScrollBarThickness(mainScrollContainer, "horizontal"); // if columnHeader is enabled, ensure neither of these measurements confusingly clamps to zero height. var adjustedColumnHeaderHeight = _this.maybeIncreaseToMinColHeaderHeight(columnHeaderHeight); var adjustedTopQuadrantHeight = _this.maybeIncreaseToMinColHeaderHeight(topQuadrantHeight); // Update cache: let's read now whatever values we might need later. // prevents unnecessary reflows in the future. _this.cache.setRowHeaderWidth(rowHeaderWidth); _this.cache.setColumnHeaderHeight(columnHeaderHeight); // ...however, we also clear the cached client size, so we can read it // again when a new scroll begins. not safe to assume this won't change. // TODO: maybe use the ResizeSensor? _this.cache.setScrollContainerClientWidth(undefined); _this.cache.setScrollContainerClientHeight(undefined); // // Writes (batched to avoid DOM thrashing) // // Quadrant-size sync'ing: make the quadrants precisely as big as they // need to be to fit their variable-sized headers and/or frozen areas. _this.maybesSetQuadrantRowHeaderSizes(rowHeaderWidth); _this.maybeSetQuadrantMenuElementSizes(rowHeaderWidth, adjustedColumnHeaderHeight); _this.maybeSetQuadrantSizes(leftQuadrantWidth, adjustedTopQuadrantHeight); // Scrollbar clearance: tweak the quadrant bottom/right offsets to // reveal the MAIN-quadrant scrollbars if they're visible. _this.maybeSetQuadrantPositionOffset(QuadrantType.TOP, "right", rightScrollBarWidth); _this.maybeSetQuadrantPositionOffset(QuadrantType.LEFT, "bottom", bottomScrollBarHeight); // Scroll syncing: sync the scroll offsets of quadrants that may or may // not have been around prior to this update. _this.maybeSetQuadrantScrollOffset(QuadrantType.LEFT, "scrollTop"); _this.maybeSetQuadrantScrollOffset(QuadrantType.TOP, "scrollLeft"); }; _this.maybeSetQuadrantSizes = function (width, height) { var leftWidth = Utils.clamp(width, _this.props.enableRowHeader ? Grid.MIN_ROW_HEADER_WIDTH : 0); var topHeight = Utils.clamp(height, _this.props.enableColumnHeader ? Grid.MIN_COLUMN_HEADER_HEIGHT : 0); _this.maybesSetQuadrantSize(QuadrantType.LEFT, "width", leftWidth); _this.maybesSetQuadrantSize(QuadrantType.TOP, "height", topHeight); _this.maybesSetQuadrantSize(QuadrantType.TOP_LEFT, "width", leftWidth); _this.maybesSetQuadrantSize(QuadrantType.TOP_LEFT, "height", topHeight); }; _this.maybesSetQuadrantSize = function (quadrantType, dimension, value) { var quadrant = _this.quadrantRefs[quadrantType].quadrant; if (quadrant != null) { quadrant.style[dimension] = "".concat(value, "px"); } }; _this.maybeSetQuadrantPositionOffset = function (quadrantType, side, value) { var quadrant = _this.quadrantRefs[quadrantType].quadrant; if (quadrant != null) { quadrant.style[side] = "".concat(value, "px"); } }; _this.maybesSetQuadrantRowHeaderSizes = function (width) { var rowHeaderWidth = Utils.clamp(width, _this.props.enableRowHeader ? Grid.MIN_ROW_HEADER_WIDTH : 0); _this.maybeSetQuadrantRowHeaderSize(QuadrantType.MAIN, rowHeaderWidth); _this.maybeSetQuadrantRowHeaderSize(QuadrantType.TOP, rowHeaderWidth); _this.maybeSetQuadrantRowHeaderSize(QuadrantType.LEFT, rowHeaderWidth); _this.maybeSetQuadrantRowHeaderSize(QuadrantType.TOP_LEFT, rowHeaderWidth); }; _this.maybeSetQuadrantRowHeaderSize = function (quadrantType, width) { var rowHeader = _this.quadrantRefs[quadrantType].rowHeader; if (rowHeader != null) { rowHeader.style.width = "".concat(width, "px"); } }; _this.maybeSetQuadrantMenuElementSizes = function (width, height) { var rowHeaderWidth = Utils.clamp(width, _this.props.enableRowHeader ? Grid.MIN_ROW_HEADER_WIDTH : 0); _this.maybeSetQuadrantMenuElementSize(QuadrantType.MAIN, rowHeaderWidth, height); _this.maybeSetQuadrantMenuElementSize(QuadrantType.TOP, rowHeaderWidth, height); _this.maybeSetQuadrantMenuElementSize(QuadrantType.LEFT, rowHeaderWidth, height); _this.maybeSetQuadrantMenuElementSize(QuadrantType.TOP_LEFT, rowHeaderWidth, height); }; _this.maybeSetQuadrantMenuElementSize = function (quadrantType, width, height) { var menu = _this.quadrantRefs[quadrantType].menu; if (menu != null) { menu.style.width = "".concat(width, "px"); menu.style.height = "".concat(height, "px"); } }; _this.maybeSetQuadrantScrollOffset = function (quadrantType, scrollKey, newOffset) { var scrollContainer = _this.quadrantRefs[quadrantType].scrollContainer; var scrollOffset = newOffset != null ? newOffset : _this.cache.getScrollOffset(scrollKey); if (scrollContainer != null) { scrollContainer[scrollKey] = scrollOffset; } }; _this.handleScrollOffsetChange = function (scrollKey, offset) { _this.cache.setScrollOffset(scrollKey, offset); var dependentQuadrantType = scrollKey === "scrollLeft" ? QuadrantType.TOP : QuadrantType.LEFT; _this.maybeSetQuadrantScrollOffset(dependentQuadrantType, scrollKey); }; // callbacks trigger too frequently unless we throttle scroll and wheel // events. declare these functions on the component instance since // they're stateful. _this.throttledHandleMainQuadrantScroll = CoreUtils.throttleReactEventCallback(_this.handleMainQuadrantScroll); _this.throttledHandleWheel = CoreUtils.throttleReactEventCallback(_this.handleWheel); _this.cache = new TableQuadrantStackCache(); return _this; } /** * Scroll the main quadrant to the specified scroll offset, keeping all other quadrants in sync. */ TableQuadrantStack.prototype.scrollToPosition = function (scrollLeft, scrollTop) { var scrollContainer = this.quadrantRefs[QuadrantType.MAIN].scrollContainer; if (scrollContainer == null) { return; } this.wasMainQuadrantScrollTriggeredByWheelEvent = false; // this will trigger the main quadrant's scroll callback below scrollContainer.scrollLeft = scrollLeft; scrollContainer.scrollTop = scrollTop; this.syncQuadrantViews(); }; /** * Synchronizes quadrant sizes and scroll offsets based on the current * column, row, and header sizes. Useful for correcting quadrant sizes after * explicitly resizing columns and rows, for instance. * * Invoking this method imperatively is cheaper than providing columnWidths * or rowHeights array props to TableQuadrantStack and forcing it to run * expensive array diffs upon every update. */ TableQuadrantStack.prototype.synchronizeQuadrantViews = function () { this.syncQuadrantViews(); }; TableQuadrantStack.prototype.componentDidMount = function () { this.emitRefs(); this.syncQuadrantViews(); }; TableQuadrantStack.prototype.componentDidUpdate = function (prevProps) { if ( // sync'ing quadrant views triggers expensive reflows, so we only call // it when layout-affecting props change. !CoreUtils.shallowCompareKeys(this.props, prevProps, { include: SYNC_TRIGGER_PROP_KEYS, }) || // in addition to those props, we also care about frozen parts of the grid // which may cause the top / left quadrants to change height / width this.didFrozenColumnWidthsChange(prevProps) || this.didFrozenRowHeightsChange(prevProps)) { this.emitRefs(); this.syncQuadrantViews(); } }; TableQuadrantStack.prototype.render = function () { var _a = this.props, grid = _a.grid, enableRowHeader = _a.enableRowHeader, bodyRenderer = _a.bodyRenderer, throttleScrolling = _a.throttleScrolling, enableColumnHeader = _a.enableColumnHeader; // use the more generic "scroll" event for the main quadrant to capture // *both* scrollbar interactions and trackpad/mousewheel gestures. var onMainQuadrantScroll = throttleScrolling ? this.throttledHandleMainQuadrantScroll : this.handleMainQuadrantScroll; var onWheel = throttleScrolling ? this.throttledHandleWheel : this.handleWheel; var baseProps = { bodyRenderer: bodyRenderer, enableColumnHeader: enableColumnHeader, enableRowHeader: enableRowHeader, grid: grid, onWheel: onWheel, }; var shouldRenderLeftQuadrants = this.shouldRenderLeftQuadrants(); var maybeLeftQuadrant = shouldRenderLeftQuadrants ? (React.createElement(TableQuadrant, __assign({}, baseProps, { quadrantRef: this.quadrantRefHandlers[QuadrantType.LEFT].quadrant, quadrantType: QuadrantType.LEFT, columnHeaderCellRenderer: this.renderLeftQuadrantColumnHeader, menuRenderer: this.renderLeftQuadrantMenu, rowHeaderCellRenderer: this.renderLeftQuadrantRowHeader, scrollContainerRef: this.quadrantRefHandlers[QuadrantType.LEFT].scrollContainer }))) : undefined; var maybeTopLeftQuadrant = shouldRenderLeftQuadrants ? (React.createElement(TableQuadrant, __assign({}, baseProps, { quadrantRef: this.quadrantRefHandlers[QuadrantType.TOP_LEFT].quadrant, quadrantType: QuadrantType.TOP_LEFT, columnHeaderCellRenderer: this.renderTopLeftQuadrantColumnHeader, menuRenderer: this.renderTopLeftQuadrantMenu, rowHeaderCellRenderer: this.renderTopLeftQuadrantRowHeader, scrollContainerRef: this.quadrantRefHandlers[QuadrantType.TOP_LEFT].scrollContainer }))) : undefined; return (React.createElement("div", { className: Classes.TABLE_QUADRANT_STACK }, this.renderTableOverlay(), React.createElement(TableQuadrant, __assign({}, baseProps, { bodyRef: this.props.bodyRef, onScroll: onMainQuadrantScroll, quadrantRef: this.quadrantRefHandlers[QuadrantType.MAIN].quadrant, quadrantType: QuadrantType.MAIN, columnHeaderCellRenderer: this.renderMainQuadrantColumnHeader, menuRenderer: this.renderMainQuadrantMenu, rowHeaderCellRenderer: this.renderMainQuadrantRowHeader, scrollContainerRef: this.quadrantRefHandlers[QuadrantType.MAIN].scrollContainer })), React.createElement(TableQuadrant, __assign({}, baseProps, { quadrantRef: this.quadrantRefHandlers[QuadrantType.TOP].quadrant, quadrantType: QuadrantType.TOP, columnHeaderCellRenderer: this.renderTopQuadrantColumnHeader, menuRenderer: this.renderTopQuadrantMenu, rowHeaderCellRenderer: this.renderTopQuadrantRowHeader, scrollContainerRef: this.quadrantRefHandlers[QuadrantType.TOP].scrollContainer })), maybeLeftQuadrant, maybeTopLeftQuadrant)); }; // Ref handlers // ============ TableQuadrantStack.prototype.generateQuadrantRefHandlers = function (quadrantType) { var _this = this; var reducer = function (agg, key) { agg[key] = function (ref) { return (_this.quadrantRefs[quadrantType][key] = ref); }; return agg; }; var refHandlers = [ "columnHeader", "menu", "quadrant", "rowHeader", "scrollContainer", ]; return refHandlers.reduce(reducer, {}); }; // Emitters // ======== TableQuadrantStack.prototype.emitRefs = function () { setRef(this.props.quadrantRef, this.quadrantRefs[QuadrantType.MAIN].quadrant); setRef(this.props.rowHeaderRef, this.quadrantRefs[QuadrantType.MAIN].rowHeader); setRef(this.props.columnHeaderRef, this.quadrantRefs[QuadrantType.MAIN].columnHeader); setRef(this.props.scrollContainerRef, this.quadrantRefs[QuadrantType.MAIN].scrollContainer); }; // this function is named 'update' instead of 'set', because a 'set' // function typically takes the new value as a parameter. we avoid that to // keep the isHorizontal logic tree contained within this function. TableQuadrantStack.prototype.updateScrollContainerClientSize = function (isHorizontal) { var mainScrollContainer = this.quadrantRefs[QuadrantType.MAIN].scrollContainer; if (isHorizontal) { this.cache.setScrollContainerClientWidth(mainScrollContainer === null || mainScrollContainer === void 0 ? void 0 : mainScrollContainer.clientWidth); return this.cache.getScrollContainerClientWidth(); } else { this.cache.setScrollContainerClientHeight(mainScrollContainer === null || mainScrollContainer === void 0 ? void 0 : mainScrollContainer.clientHeight); return this.cache.getScrollContainerClientHeight(); } }; TableQuadrantStack.prototype.maybeIncreaseToMinColHeaderHeight = function (height) { if (this.props.enableColumnHeader) { return height <= QUADRANT_MIN_SIZE ? Grid.MIN_COLUMN_HEADER_HEIGHT : height; } else { return height; } }; // Helpers // ======= /** Returns true the cumulative width of all frozen columns in the grid changed. */ TableQuadrantStack.prototype.didFrozenColumnWidthsChange = function (prevProps) { return (this.props.numFrozenColumns > 0 && this.props.grid !== prevProps.grid && this.props.grid.getCumulativeWidthAt(this.props.numFrozenColumns - 1) !== prevProps.grid.getCumulativeWidthAt(prevProps.numFrozenColumns - 1)); }; /** Returns true the cumulative height of all frozen rows in the grid changed. */ TableQuadrantStack.prototype.didFrozenRowHeightsChange = function (prevProps) { return (this.props.numFrozenRows > 0 && this.props.grid !== prevProps.grid && this.props.grid.getCumulativeHeightAt(this.props.numFrozenRows - 1) !== prevProps.grid.getCumulativeHeightAt(prevProps.numFrozenRows - 1)); }; /** * Returns the width or height of *only the grid* in the secondary quadrants * (TOP, LEFT, TOP_LEFT), based on the number of frozen rows and columns. */ TableQuadrantStack.prototype.getSecondaryQuadrantGridSize = function (dimension) { var _a; var _b = this.props, grid = _b.grid, numFrozenColumns = _b.numFrozenColumns, numFrozenRows = _b.numFrozenRows; var numFrozen = (_a = (dimension === "width" ? numFrozenColumns : numFrozenRows)) !== null && _a !== void 0 ? _a : 0; var getterFn = dimension === "width" ? grid.getCumulativeWidthAt : grid.getCumulativeHeightAt; // both getter functions do O(1) lookups. return numFrozen > 0 ? getterFn(numFrozen - 1) : QUADRANT_MIN_SIZE; }; /** * Measures the desired width of the row header based on its tallest * contents. */ TableQuadrantStack.prototype.measureDesiredRowHeaderWidth = function () { // the MAIN row header serves as the source of truth var mainRowHeader = this.quadrantRefs[QuadrantType.MAIN].rowHeader; if (mainRowHeader == null) { return 0; } else { // (alas, we must force a reflow to measure the row header's "desired" width) mainRowHeader.style.width = "auto"; return Utils.clamp(mainRowHeader.clientWidth, Grid.MIN_ROW_HEADER_WIDTH); } }; /** * Measures the desired height of the column header based on its tallest * contents. */ TableQuadrantStack.prototype.measureDesiredColumnHeaderHeight = function () { // unlike the row headers, the column headers are in a display-flex // layout and are not actually bound by any fixed `height` that we set, // so they'll grow freely to their necessary size. makes measuring easy! var mainColumnHeader = this.quadrantRefs[QuadrantType.MAIN].columnHeader; return mainColumnHeader == null ? 0 : Utils.clamp(mainColumnHeader.clientHeight, Grid.MIN_COLUMN_HEADER_HEIGHT); }; TableQuadrantStack.prototype.shouldRenderLeftQuadrants = function (props) { if (props === void 0) { props = this.props; } var enableRowHeader = props.enableRowHeader, numFrozenColumns = props.numFrozenColumns; return enableRowHeader || (numFrozenColumns != null && numFrozenColumns > 0); }; // Resizing // should return empty array [] if we just finished resizing TableQuadrantStack.prototype.adjustVerticalGuides = function (verticalGuides, quadrantType) { var _a; var isFrozenQuadrant = quadrantType === QuadrantType.LEFT || quadrantType === QuadrantType.TOP_LEFT; var scrollAmount = isFrozenQuadrant ? 0 : this.cache.getScrollOffset("scrollLeft"); var rowHeaderWidth = this.cache.getRowHeaderWidth(); return (_a = verticalGuides === null || verticalGuides === void 0 ? void 0 : verticalGuides.map(function (verticalGuide) { return verticalGuide - scrollAmount + rowHeaderWidth; })) !== null && _a !== void 0 ? _a : []; }; // should return empty array [] if we just finished resizing TableQuadrantStack.prototype.adjustHorizontalGuides = function (horizontalGuides, quadrantType) { var _a; var isFrozenQuadrant = quadrantType === QuadrantType.TOP || quadrantType === QuadrantType.TOP_LEFT; var scrollAmount = isFrozenQuadrant ? 0 : this.cache.getScrollOffset("scrollTop"); var columnHeaderHeight = this.cache.getColumnHeaderHeight(); return (_a = horizontalGuides === null || horizontalGuides === void 0 ? void 0 : horizontalGuides.map(function (horizontalGuide) { return horizontalGuide - scrollAmount + columnHeaderHeight; })) !== null && _a !== void 0 ? _a : []; }; // we want the user to explicitly pass a quadrantType. define defaultProps as a Partial to avoid // declaring that and other required props here. TableQuadrantStack.defaultProps = { enableColumnHeader: true, enableColumnInteractionBar: undefined, enableRowHeader: true, isHorizontalScrollDisabled: false, isVerticalScrollDisabled: false, throttleScrolling: true, viewSyncDelay: DEFAULT_VIEW_SYNC_DELAY, }; return TableQuadrantStack; }(AbstractComponent)); export { TableQuadrantStack }; //# sourceMappingURL=tableQuadrantStack.js.map