UNPKG

react-flexigrid

Version:

A React table component designed to allow presenting millions of rows of data.

283 lines (238 loc) 10 kB
import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import PrefixIntervalTree from './struct/PrefixIntervalTree'; import { clamp } from './utils'; var BUFFER_ROWS = 5; var NO_ROWS_SCROLL_RESULT = { index: 0, offset: 0, position: 0, contentHeight: 0 }; var FlexiGridVerticalScrollHelper = function () { function FlexiGridVerticalScrollHelper(rowCount, defaultRowHeight, viewportHeight, rowHeightGetter) { var _this = this; var defaultSubRowHeight = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0; var subRowHeightGetter = arguments[5]; _classCallCheck(this, FlexiGridVerticalScrollHelper); this.getRowPosition = function (rowIndex) { _this.updateRowHeight(rowIndex); return _this.rowOffsets.sumUntil(rowIndex); }; var defaultFullRowHeight = defaultRowHeight + defaultSubRowHeight; this.rowOffsets = PrefixIntervalTree.uniform(rowCount, defaultFullRowHeight); this.cachedHeights = []; for (var i = 0; i < rowCount; i += 1) { this.cachedHeights[i] = defaultFullRowHeight; } this.position = 0; this.rowCount = rowCount; this.viewportHeight = viewportHeight; this.contentHeight = rowCount * defaultFullRowHeight; this.rowHeightGetter = rowHeightGetter; this.subRowHeightGetter = subRowHeightGetter; this.fullRowHeightGetter = function (rowIndex) { var rowHeight = _this.rowHeightGetter ? _this.rowHeightGetter(rowIndex) : defaultRowHeight; var subRowHeight = _this.subRowHeightGetter ? _this.subRowHeightGetter(rowIndex) : defaultSubRowHeight; return rowHeight + subRowHeight; }; this.updateHeightsInViewport(0, 0); } FlexiGridVerticalScrollHelper.prototype.setRowHeightGetter = function setRowHeightGetter(rowHeightGetter) { this.rowHeightGetter = rowHeightGetter; }; FlexiGridVerticalScrollHelper.prototype.setSubRowHeightGetter = function setSubRowHeightGetter(subRowHeightGetter) { this.subRowHeightGetter = subRowHeightGetter; }; FlexiGridVerticalScrollHelper.prototype.setViewportHeight = function setViewportHeight(viewportHeight) { this.viewportHeight = viewportHeight; }; FlexiGridVerticalScrollHelper.prototype.getContentHeight = function getContentHeight() { return this.contentHeight; }; FlexiGridVerticalScrollHelper.prototype.updateHeightsInViewport = function updateHeightsInViewport(firstRowIndex, firstRowOffset) { var top = firstRowOffset; var index = firstRowIndex; while (top <= this.viewportHeight && index < this.rowCount) { this.updateRowHeight(index); top += this.cachedHeights[index]; index += 1; } }; FlexiGridVerticalScrollHelper.prototype.updateHeightsAboveViewport = function updateHeightsAboveViewport(firstRowIndex) { var index = firstRowIndex - 1; var endRowIndex = firstRowIndex - BUFFER_ROWS; while (index >= 0 && index >= endRowIndex) { var delta = this.updateRowHeight(index); this.position += delta; index -= 1; } }; FlexiGridVerticalScrollHelper.prototype.updateRowHeight = function updateRowHeight(rowIndex) { if (rowIndex < 0 || rowIndex >= this.rowCount) { return 0; } var oldHeight = this.cachedHeights[rowIndex]; var newHeight = this.fullRowHeightGetter(rowIndex); if (newHeight !== oldHeight) { var delta = newHeight - oldHeight; this.rowOffsets.set(rowIndex, newHeight); this.cachedHeights[rowIndex] = newHeight; this.contentHeight += delta; return delta; } return 0; }; FlexiGridVerticalScrollHelper.prototype.getRowAtEndPosition = function getRowAtEndPosition(rowIndex) { // We need to update enough rows above the selected one to be sure that when // we scroll to selected position all rows between first shown and selected // one have most recent heights computed and will not resize. // 获取 rowIndex 为可视范围的最后一行时的 position this.updateRowHeight(rowIndex); var currentRowIndex = rowIndex; var sum = this.cachedHeights[currentRowIndex]; while (sum < this.viewportHeight && currentRowIndex >= 0) { currentRowIndex -= 1; if (currentRowIndex >= 0) { this.updateRowHeight(currentRowIndex); sum += this.cachedHeights[currentRowIndex]; } } var position = this.rowOffsets.sumTo(rowIndex) - this.viewportHeight; if (position < 0) { position = 0; } return position; }; FlexiGridVerticalScrollHelper.prototype.scrollTo = function scrollTo(position) { if (this.rowCount === 0) { return NO_ROWS_SCROLL_RESULT; } if (position <= 0) { // If position less than or equal to 0 first row // should be fully visible on top. this.position = 0; this.updateHeightsInViewport(0, 0); return { index: 0, offset: 0, position: this.position, contentHeight: this.contentHeight }; } else if (position >= this.contentHeight - this.viewportHeight) { // If position is equal to or greater than max scroll value, we need // to make sure to have bottom border of last row visible. var rowIndex = this.rowCount - 1; position = this.getRowAtEndPosition(rowIndex); // eslint-disable-line } this.position = position; var firstRowIndex = this.rowOffsets.greatestLowerBound(position); firstRowIndex = clamp(firstRowIndex, 0, Math.max(this.rowCount - 1, 0)); var firstRowPosition = this.rowOffsets.sumUntil(firstRowIndex); var firstRowOffset = firstRowPosition - position; this.updateHeightsInViewport(firstRowIndex, firstRowOffset); this.updateHeightsAboveViewport(firstRowIndex); return { index: firstRowIndex, offset: firstRowOffset, position: this.position, contentHeight: this.contentHeight }; }; FlexiGridVerticalScrollHelper.prototype.scrollBy = function scrollBy(delta) { if (this.rowCount === 0) { return NO_ROWS_SCROLL_RESULT; } var firstRow = this.rowOffsets.greatestLowerBound(this.position); firstRow = clamp(firstRow, 0, Math.max(this.rowCount - 1, 0)); var firstRowPosition = this.rowOffsets.sumUntil(firstRow); var rowIndex = firstRow; var position = this.position; var rowHeightChange = this.updateRowHeight(rowIndex); if (firstRowPosition !== 0) { position += rowHeightChange; } var visibleRowHeight = this.cachedHeights[rowIndex] - (position - firstRowPosition); if (delta >= 0) { while (delta > 0 && rowIndex < this.rowCount) { if (delta < visibleRowHeight) { position += delta; delta = 0; // eslint-disable-line } else { delta -= visibleRowHeight; // eslint-disable-line position += visibleRowHeight; rowIndex += 1; } if (rowIndex < this.rowCount) { this.updateRowHeight(rowIndex); visibleRowHeight = this.cachedHeights[rowIndex]; } } } else if (delta < 0) { delta = -delta; // eslint-disable-line var invisibleRowHeight = this.cachedHeights[rowIndex] - visibleRowHeight; while (delta > 0 && rowIndex >= 0) { if (delta < invisibleRowHeight) { position -= delta; delta = 0; // eslint-disable-line } else { position -= invisibleRowHeight; delta -= invisibleRowHeight; // eslint-disable-line rowIndex -= 1; } if (rowIndex >= 0) { var change = this.updateRowHeight(rowIndex); invisibleRowHeight = this.cachedHeights[rowIndex]; position += change; } } } var maxPosition = this.contentHeight - this.viewportHeight; position = clamp(position, 0, maxPosition); this.position = position; var firstRowIndex = this.rowOffsets.greatestLowerBound(position); firstRowIndex = clamp(firstRowIndex, 0, Math.max(this.rowCount - 1, 0)); firstRowPosition = this.rowOffsets.sumUntil(firstRowIndex); var firstRowOffset = firstRowPosition - position; this.updateHeightsInViewport(firstRowIndex, firstRowOffset); this.updateHeightsAboveViewport(firstRowIndex); return { index: firstRowIndex, offset: firstRowOffset, position: this.position, contentHeight: this.contentHeight }; }; /** * Allows to scroll to selected row with specified offset. It always * brings that row to top of viewport with that offset */ FlexiGridVerticalScrollHelper.prototype.scrollToRow = function scrollToRow(rowIndex, offset) { rowIndex = clamp(rowIndex, 0, Math.max(this.rowCount - 1, 0)); // eslint-disable-line offset = clamp(offset, -this.cachedHeights[rowIndex], 0); // eslint-disable-line var firstRow = this.rowOffsets.sumUntil(rowIndex); return this.scrollTo(firstRow - offset); }; /** * Allows to scroll to selected row by bringing it to viewport with minimal * scrolling. This that if row is fully visible, scroll will not be changed. * If top border of row is above top of viewport it will be scrolled to be * fully visible on the top of viewport. If the bottom border of row is * below end of viewport, it will be scrolled up to be fully visible on the * bottom of viewport. */ FlexiGridVerticalScrollHelper.prototype.scrollRowIntoView = function scrollRowIntoView(rowIndex) { rowIndex = clamp(rowIndex, 0, Math.max(this.rowCount - 1, 0)); // eslint-disable-line this.updateRowHeight(rowIndex); var rowBegin = this.rowOffsets.sumUntil(rowIndex); var rowEnd = rowBegin + this.cachedHeights[rowIndex]; if (rowBegin < this.position) { return this.scrollTo(rowBegin); } else if (this.position + this.viewportHeight < rowEnd) { var position = this.getRowAtEndPosition(rowIndex); return this.scrollTo(position); } return this.scrollTo(this.position); }; return FlexiGridVerticalScrollHelper; }(); export default FlexiGridVerticalScrollHelper;