react-flexigrid
Version:
A React table component designed to allow presenting millions of rows of data.
283 lines (238 loc) • 10 kB
JavaScript
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;