@blueprintjs/table
Version:
Scalable interactive table component
653 lines • 38.9 kB
JavaScript
/*
* 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