@humanmade/react-flame-graph
Version:
React component for visualizing profiling data
1,297 lines (1,082 loc) • 79.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var simpleIsEqual = function simpleIsEqual(a, b) {
return a === b;
};
function memoize (resultFn) {
var isEqual = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : simpleIsEqual;
var lastThis = void 0;
var lastArgs = [];
var lastResult = void 0;
var calledOnce = false;
var isNewArgEqualToLast = function isNewArgEqualToLast(newArg, index) {
return isEqual(newArg, lastArgs[index]);
};
var result = function result() {
for (var _len = arguments.length, newArgs = Array(_len), _key = 0; _key < _len; _key++) {
newArgs[_key] = arguments[_key];
}
if (calledOnce && lastThis === this && newArgs.length === lastArgs.length && newArgs.every(isNewArgEqualToLast)) {
return lastResult;
}
calledOnce = true;
lastThis = this;
lastArgs = newArgs;
lastResult = resultFn.apply(this, newArgs);
return lastResult;
};
return result;
}
var simpleIsEqual$1 = function simpleIsEqual(a, b) {
return a === b;
};
function memoizeOne (resultFn) {
var isEqual = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : simpleIsEqual$1;
var lastThis = void 0;
var lastArgs = [];
var lastResult = void 0;
var calledOnce = false;
var isNewArgEqualToLast = function isNewArgEqualToLast(newArg, index) {
return isEqual(newArg, lastArgs[index]);
};
var result = function result() {
for (var _len = arguments.length, newArgs = Array(_len), _key = 0; _key < _len; _key++) {
newArgs[_key] = arguments[_key];
}
if (calledOnce && lastThis === this && newArgs.length === lastArgs.length && newArgs.every(isNewArgEqualToLast)) {
return lastResult;
}
calledOnce = true;
lastThis = this;
lastArgs = newArgs;
lastResult = resultFn.apply(this, newArgs);
return lastResult;
};
return result;
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
var IS_SCROLLING_DEBOUNCE_INTERVAL = 150;
var defaultItemKey = function defaultItemKey(_ref) {
var columnIndex = _ref.columnIndex,
rowIndex = _ref.rowIndex;
return rowIndex + ':' + columnIndex;
};
function createGridComponent(_ref2) {
var _class, _temp;
var getColumnOffset = _ref2.getColumnOffset,
getColumnStartIndexForOffset = _ref2.getColumnStartIndexForOffset,
getColumnStopIndexForStartIndex = _ref2.getColumnStopIndexForStartIndex,
getColumnWidth = _ref2.getColumnWidth,
getEstimatedTotalHeight = _ref2.getEstimatedTotalHeight,
getEstimatedTotalWidth = _ref2.getEstimatedTotalWidth,
getOffsetForColumnAndAlignment = _ref2.getOffsetForColumnAndAlignment,
getOffsetForRowAndAlignment = _ref2.getOffsetForRowAndAlignment,
getRowHeight = _ref2.getRowHeight,
getRowOffset = _ref2.getRowOffset,
getRowStartIndexForOffset = _ref2.getRowStartIndexForOffset,
getRowStopIndexForStartIndex = _ref2.getRowStopIndexForStartIndex,
initInstanceProps = _ref2.initInstanceProps,
validateProps = _ref2.validateProps;
return _temp = _class = function (_PureComponent) {
inherits(Grid, _PureComponent);
function Grid(props) {
classCallCheck(this, Grid);
var _this = possibleConstructorReturn(this, (Grid.__proto__ || Object.getPrototypeOf(Grid)).call(this, props));
_this._itemStyleCache = {};
_this._resetIsScrollingTimeoutId = null;
_this.state = {
isScrolling: false,
horizontalScrollDirection: 'forward',
scrollLeft: typeof _this.props.initialScrollLeft === 'number' ? _this.props.initialScrollLeft : 0,
scrollTop: typeof _this.props.initialScrollTop === 'number' ? _this.props.initialScrollTop : 0,
scrollUpdateWasRequested: false,
verticalScrollDirection: 'forward'
};
_this._callOnItemsRendered = memoizeOne(function (overscanColumnStartIndex, overscanColumnStopIndex, overscanRowStartIndex, overscanRowStopIndex, visibleColumnStartIndex, visibleColumnStopIndex, visibleRowStartIndex, visibleRowStopIndex) {
return _this.props.onItemsRendered({
overscanColumnStartIndex: overscanColumnStartIndex,
overscanColumnStopIndex: overscanColumnStopIndex,
overscanRowStartIndex: overscanRowStartIndex,
overscanRowStopIndex: overscanRowStopIndex,
visibleColumnStartIndex: visibleColumnStartIndex,
visibleColumnStopIndex: visibleColumnStopIndex,
visibleRowStartIndex: visibleRowStartIndex,
visibleRowStopIndex: visibleRowStopIndex
});
});
_this._callOnScroll = memoizeOne(function (scrollLeft, scrollTop, horizontalScrollDirection, verticalScrollDirection, scrollUpdateWasRequested) {
return _this.props.onScroll({
horizontalScrollDirection: horizontalScrollDirection,
scrollLeft: scrollLeft,
scrollTop: scrollTop,
verticalScrollDirection: verticalScrollDirection,
scrollUpdateWasRequested: scrollUpdateWasRequested
});
});
_this._getItemStyle = function (rowIndex, columnIndex) {
var key = rowIndex + ':' + columnIndex;
var style = void 0;
if (_this._itemStyleCache.hasOwnProperty(key)) {
style = _this._itemStyleCache[key];
} else {
_this._itemStyleCache[key] = style = {
position: 'absolute',
left: getColumnOffset(_this.props, columnIndex, _this._instanceProps),
top: getRowOffset(_this.props, rowIndex, _this._instanceProps),
height: getRowHeight(_this.props, rowIndex, _this._instanceProps),
width: getColumnWidth(_this.props, columnIndex, _this._instanceProps)
};
}
return style;
};
_this._onScroll = function (event) {
var _event$currentTarget = event.currentTarget,
scrollLeft = _event$currentTarget.scrollLeft,
scrollTop = _event$currentTarget.scrollTop;
_this.setState(function (prevState) {
if (prevState.scrollLeft === scrollLeft && prevState.scrollTop === scrollTop) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
return {
isScrolling: true,
horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
scrollLeft: scrollLeft,
scrollTop: scrollTop,
verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward',
scrollUpdateWasRequested: false
};
}, _this._resetIsScrollingDebounced);
};
_this._outerRefSetter = function (ref) {
var outerRef = _this.props.outerRef;
_this._outerRef = ref;
if (typeof outerRef === 'function') {
outerRef(ref);
} else if (outerRef != null && (typeof outerRef === 'undefined' ? 'undefined' : _typeof(outerRef)) === 'object' && outerRef.hasOwnProperty('current')) {
outerRef.current = ref;
}
};
_this._resetIsScrollingDebounced = function () {
if (_this._resetIsScrollingTimeoutId !== null) {
clearTimeout(_this._resetIsScrollingTimeoutId);
}
_this._resetIsScrollingTimeoutId = setTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL);
};
_this._resetIsScrollingDebounced = function () {
if (_this._resetIsScrollingTimeoutId !== null) {
clearTimeout(_this._resetIsScrollingTimeoutId);
}
_this._resetIsScrollingTimeoutId = setTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL);
};
_this._resetIsScrolling = function () {
_this._resetIsScrollingTimeoutId = null;
_this.setState({ isScrolling: false }, function () {
// Clear style cache after state update has been committed.
// This way we don't break pure sCU for items that don't use isScrolling param.
_this._itemStyleCache = {};
});
};
_this._instanceProps = initInstanceProps(props, _this);
return _this;
}
createClass(Grid, [{
key: 'scrollTo',
value: function scrollTo(_ref3) {
var scrollLeft = _ref3.scrollLeft,
scrollTop = _ref3.scrollTop;
this.setState(function (prevState) {
return {
horizontalScrollDirection: prevState.scrollLeft < scrollLeft ? 'forward' : 'backward',
scrollLeft: scrollLeft,
scrollTop: scrollTop,
scrollUpdateWasRequested: true,
verticalScrollDirection: prevState.scrollTop < scrollTop ? 'forward' : 'backward'
};
}, this._resetIsScrollingDebounced);
}
}, {
key: 'scrollToItem',
value: function scrollToItem(_ref4) {
var _ref4$align = _ref4.align,
align = _ref4$align === undefined ? 'auto' : _ref4$align,
columnIndex = _ref4.columnIndex,
rowIndex = _ref4.rowIndex;
var _state = this.state,
scrollLeft = _state.scrollLeft,
scrollTop = _state.scrollTop;
this.scrollTo({
scrollLeft: getOffsetForColumnAndAlignment(this.props, columnIndex, align, scrollLeft, this._instanceProps),
scrollTop: getOffsetForRowAndAlignment(this.props, rowIndex, align, scrollTop, this._instanceProps)
});
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props,
initialScrollLeft = _props.initialScrollLeft,
initialScrollTop = _props.initialScrollTop;
if (typeof initialScrollLeft === 'number' && this._outerRef != null) {
this._outerRef.scrollLeft = initialScrollLeft;
}
if (typeof initialScrollTop === 'number' && this._outerRef != null) {
this._outerRef.scrollTop = initialScrollTop;
}
this._callPropsCallbacks();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
var _state2 = this.state,
scrollLeft = _state2.scrollLeft,
scrollTop = _state2.scrollTop,
scrollUpdateWasRequested = _state2.scrollUpdateWasRequested;
if (scrollUpdateWasRequested && this._outerRef !== null) {
this._outerRef.scrollLeft = scrollLeft;
this._outerRef.scrollTop = scrollTop;
}
this._callPropsCallbacks();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (this._resetIsScrollingTimeoutId !== null) {
clearTimeout(this._resetIsScrollingTimeoutId);
}
}
}, {
key: 'render',
value: function render() {
var _props2 = this.props,
children = _props2.children,
className = _props2.className,
columnCount = _props2.columnCount,
height = _props2.height,
innerRef = _props2.innerRef,
innerTagName = _props2.innerTagName,
itemData = _props2.itemData,
_props2$itemKey = _props2.itemKey,
itemKey = _props2$itemKey === undefined ? defaultItemKey : _props2$itemKey,
outerTagName = _props2.outerTagName,
rowCount = _props2.rowCount,
style = _props2.style,
useIsScrolling = _props2.useIsScrolling,
width = _props2.width;
var isScrolling = this.state.isScrolling;
var _getHorizontalRangeTo = this._getHorizontalRangeToRender(),
_getHorizontalRangeTo2 = slicedToArray(_getHorizontalRangeTo, 2),
columnStartIndex = _getHorizontalRangeTo2[0],
columnStopIndex = _getHorizontalRangeTo2[1];
var _getVerticalRangeToRe = this._getVerticalRangeToRender(),
_getVerticalRangeToRe2 = slicedToArray(_getVerticalRangeToRe, 2),
rowStartIndex = _getVerticalRangeToRe2[0],
rowStopIndex = _getVerticalRangeToRe2[1];
var items = [];
if (columnCount > 0 && rowCount) {
for (var _rowIndex = rowStartIndex; _rowIndex <= rowStopIndex; _rowIndex++) {
for (var _columnIndex = columnStartIndex; _columnIndex <= columnStopIndex; _columnIndex++) {
items.push(React.createElement(children, {
columnIndex: _columnIndex,
data: itemData,
isScrolling: useIsScrolling ? isScrolling : undefined,
key: itemKey({ columnIndex: _columnIndex, rowIndex: _rowIndex }),
rowIndex: _rowIndex,
style: this._getItemStyle(_rowIndex, _columnIndex)
}));
}
}
}
// Read this value AFTER items have been created,
// So their actual sizes (if variable) are taken into consideration.
var estimatedTotalHeight = getEstimatedTotalHeight(this.props, this._instanceProps);
var estimatedTotalWidth = getEstimatedTotalWidth(this.props, this._instanceProps);
return React.createElement(outerTagName, {
className: className,
onScroll: this._onScroll,
ref: this._outerRefSetter,
style: _extends({
position: 'relative',
height: height,
width: width,
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
willChange: 'transform'
}, style)
}, React.createElement(innerTagName, {
children: items,
ref: innerRef,
style: {
height: estimatedTotalHeight,
overflow: 'hidden',
pointerEvents: isScrolling ? 'none' : '',
width: estimatedTotalWidth
}
}));
}
}, {
key: '_callPropsCallbacks',
value: function _callPropsCallbacks() {
var _props3 = this.props,
columnCount = _props3.columnCount,
onItemsRendered = _props3.onItemsRendered,
onScroll = _props3.onScroll,
rowCount = _props3.rowCount;
if (typeof onItemsRendered === 'function') {
if (columnCount > 0 && rowCount > 0) {
var _getHorizontalRangeTo3 = this._getHorizontalRangeToRender(),
_getHorizontalRangeTo4 = slicedToArray(_getHorizontalRangeTo3, 4),
_overscanColumnStartIndex = _getHorizontalRangeTo4[0],
_overscanColumnStopIndex = _getHorizontalRangeTo4[1],
_visibleColumnStartIndex = _getHorizontalRangeTo4[2],
_visibleColumnStopIndex = _getHorizontalRangeTo4[3];
var _getVerticalRangeToRe3 = this._getVerticalRangeToRender(),
_getVerticalRangeToRe4 = slicedToArray(_getVerticalRangeToRe3, 4),
_overscanRowStartIndex = _getVerticalRangeToRe4[0],
_overscanRowStopIndex = _getVerticalRangeToRe4[1],
_visibleRowStartIndex = _getVerticalRangeToRe4[2],
_visibleRowStopIndex = _getVerticalRangeToRe4[3];
this._callOnItemsRendered(_overscanColumnStartIndex, _overscanColumnStopIndex, _overscanRowStartIndex, _overscanRowStopIndex, _visibleColumnStartIndex, _visibleColumnStopIndex, _visibleRowStartIndex, _visibleRowStopIndex);
}
}
if (typeof onScroll === 'function') {
var _state3 = this.state,
_horizontalScrollDirection = _state3.horizontalScrollDirection,
_scrollLeft = _state3.scrollLeft,
_scrollTop = _state3.scrollTop,
_scrollUpdateWasRequested = _state3.scrollUpdateWasRequested,
_verticalScrollDirection = _state3.verticalScrollDirection;
this._callOnScroll(_scrollLeft, _scrollTop, _horizontalScrollDirection, _verticalScrollDirection, _scrollUpdateWasRequested);
}
}
// Lazily create and cache item styles while scrolling,
// So that pure component sCU will prevent re-renders.
// We maintain this cache, and pass a style prop rather than index,
// So that List can clear cached styles and force item re-render if necessary.
}, {
key: '_getHorizontalRangeToRender',
value: function _getHorizontalRangeToRender() {
var _props4 = this.props,
columnCount = _props4.columnCount,
overscanCount = _props4.overscanCount;
var _state4 = this.state,
horizontalScrollDirection = _state4.horizontalScrollDirection,
scrollLeft = _state4.scrollLeft;
var startIndex = getColumnStartIndexForOffset(this.props, scrollLeft, this._instanceProps);
var stopIndex = getColumnStopIndexForStartIndex(this.props, startIndex, scrollLeft, this._instanceProps);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
var overscanBackward = horizontalScrollDirection === 'backward' ? Math.max(1, overscanCount) : 1;
var overscanForward = horizontalScrollDirection === 'forward' ? Math.max(1, overscanCount) : 1;
return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(columnCount - 1, stopIndex + overscanForward)), startIndex, stopIndex];
}
}, {
key: '_getVerticalRangeToRender',
value: function _getVerticalRangeToRender() {
var _props5 = this.props,
rowCount = _props5.rowCount,
overscanCount = _props5.overscanCount;
var _state5 = this.state,
verticalScrollDirection = _state5.verticalScrollDirection,
scrollTop = _state5.scrollTop;
var startIndex = getRowStartIndexForOffset(this.props, scrollTop, this._instanceProps);
var stopIndex = getRowStopIndexForStartIndex(this.props, startIndex, scrollTop, this._instanceProps);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
var overscanBackward = verticalScrollDirection === 'backward' ? Math.max(1, overscanCount) : 1;
var overscanForward = verticalScrollDirection === 'forward' ? Math.max(1, overscanCount) : 1;
return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(rowCount - 1, stopIndex + overscanForward)), startIndex, stopIndex];
}
}], [{
key: 'getDerivedStateFromProps',
value: function getDerivedStateFromProps(nextProps, prevState) {
validateSharedProps(nextProps);
validateProps(nextProps);
return null;
}
}]);
return Grid;
}(React.PureComponent), _class.defaultProps = {
innerTagName: 'div',
outerTagName: 'div',
overscanCount: 1,
useIsScrolling: false
}, _temp;
}
var validateSharedProps = function validateSharedProps(_ref5) {
var children = _ref5.children,
height = _ref5.height,
width = _ref5.width;
if (process.env.NODE_ENV !== 'production') {
if (typeof children !== 'function') {
throw Error('An invalid "children" prop has been specified. ' + 'Value should be a function that creates a React element. ' + ('"' + (children === null ? 'null' : typeof children === 'undefined' ? 'undefined' : _typeof(children)) + '" was specified.'));
}
if (typeof width !== 'number') {
throw Error('An invalid "width" prop has been specified. ' + 'Grids must specify a number for width. ' + ('"' + (width === null ? 'null' : typeof width === 'undefined' ? 'undefined' : _typeof(width)) + '" was specified.'));
}
if (typeof height !== 'number') {
throw Error('An invalid "height" prop has been specified. ' + 'Grids must specify a number for height. ' + ('"' + (height === null ? 'null' : typeof height === 'undefined' ? 'undefined' : _typeof(height)) + '" was specified.'));
}
}
};
var DEFAULT_ESTIMATED_ITEM_SIZE = 50;
var getEstimatedTotalHeight = function getEstimatedTotalHeight(_ref, _ref2) {
var rowCount = _ref.rowCount;
var rowMetadataMap = _ref2.rowMetadataMap,
estimatedRowHeight = _ref2.estimatedRowHeight,
lastMeasuredRowIndex = _ref2.lastMeasuredRowIndex;
var totalSizeOfMeasuredRows = 0;
if (lastMeasuredRowIndex >= 0) {
var itemMetadata = rowMetadataMap[lastMeasuredRowIndex];
totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size;
}
var numUnmeasuredItems = rowCount - lastMeasuredRowIndex - 1;
var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedRowHeight;
return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems;
};
var getEstimatedTotalWidth = function getEstimatedTotalWidth(_ref3, _ref4) {
var columnCount = _ref3.columnCount;
var columnMetadataMap = _ref4.columnMetadataMap,
estimatedColumnWidth = _ref4.estimatedColumnWidth,
lastMeasuredColumnIndex = _ref4.lastMeasuredColumnIndex;
var totalSizeOfMeasuredRows = 0;
if (lastMeasuredColumnIndex >= 0) {
var itemMetadata = columnMetadataMap[lastMeasuredColumnIndex];
totalSizeOfMeasuredRows = itemMetadata.offset + itemMetadata.size;
}
var numUnmeasuredItems = columnCount - lastMeasuredColumnIndex - 1;
var totalSizeOfUnmeasuredItems = numUnmeasuredItems * estimatedColumnWidth;
return totalSizeOfMeasuredRows + totalSizeOfUnmeasuredItems;
};
var getItemMetadata = function getItemMetadata(itemType, props, index, instanceProps) {
var itemMetadataMap = void 0,
itemSize = void 0,
lastMeasuredIndex = void 0;
if (itemType === 'column') {
itemMetadataMap = instanceProps.columnMetadataMap;
itemSize = props.columnWidth;
lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex;
} else {
itemMetadataMap = instanceProps.rowMetadataMap;
itemSize = props.rowHeight;
lastMeasuredIndex = instanceProps.lastMeasuredRowIndex;
}
if (index > lastMeasuredIndex) {
var _offset = 0;
if (lastMeasuredIndex >= 0) {
var itemMetadata = itemMetadataMap[lastMeasuredIndex];
_offset = itemMetadata.offset + itemMetadata.size;
}
for (var i = lastMeasuredIndex + 1; i <= index; i++) {
var _size = itemSize(i);
itemMetadataMap[i] = {
offset: _offset,
size: _size
};
_offset += _size;
}
if (itemType === 'column') {
instanceProps.lastMeasuredColumnIndex = index;
} else {
instanceProps.lastMeasuredRowIndex = index;
}
}
return itemMetadataMap[index];
};
var findNearestItem = function findNearestItem(itemType, props, instanceProps, offset) {
var itemMetadataMap = void 0,
lastMeasuredIndex = void 0;
if (itemType === 'column') {
itemMetadataMap = instanceProps.columnMetadataMap;
lastMeasuredIndex = instanceProps.lastMeasuredColumnIndex;
} else {
itemMetadataMap = instanceProps.rowMetadataMap;
lastMeasuredIndex = instanceProps.lastMeasuredRowIndex;
}
var lastMeasuredItemOffset = lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0;
if (lastMeasuredItemOffset >= offset) {
// If we've already measured items within this range just use a binary search as it's faster.
return findNearestItemBinarySearch(itemType, props, instanceProps, lastMeasuredIndex, 0, offset);
} else {
// If we haven't yet measured this high, fallback to an exponential search with an inner binary search.
// The exponential search avoids pre-computing sizes for the full set of items as a binary search would.
// The overall complexity for this approach is O(log n).
return findNearestItemExponentialSearch(itemType, props, instanceProps, Math.max(0, lastMeasuredIndex), offset);
}
};
var findNearestItemBinarySearch = function findNearestItemBinarySearch(itemType, props, instanceProps, high, low, offset) {
while (low <= high) {
var middle = low + Math.floor((high - low) / 2);
var currentOffset = getItemMetadata(itemType, props, middle, instanceProps).offset;
if (currentOffset === offset) {
return middle;
} else if (currentOffset < offset) {
low = middle + 1;
} else if (currentOffset > offset) {
high = middle - 1;
}
}
if (low > 0) {
return low - 1;
} else {
return 0;
}
};
var findNearestItemExponentialSearch = function findNearestItemExponentialSearch(itemType, props, instanceProps, index, offset) {
var itemCount = itemType === 'column' ? props.columnCount : props.rowCount;
var interval = 1;
while (index < itemCount && getItemMetadata(itemType, props, index, instanceProps).offset < offset) {
index += interval;
interval *= 2;
}
return findNearestItemBinarySearch(itemType, props, instanceProps, Math.min(index, itemCount - 1), Math.floor(index / 2), offset);
};
var getOffsetForIndexAndAlignment = function getOffsetForIndexAndAlignment(itemType, props, index, align, scrollOffset, instanceProps) {
var size = itemType === 'column' ? props.width : props.height;
var itemMetadata = getItemMetadata(itemType, props, index, instanceProps);
// Get estimated total size after ItemMetadata is computed,
// To ensure it reflects actual measurements instead of just estimates.
var estimatedTotalSize = itemType === 'column' ? getEstimatedTotalWidth(props, instanceProps) : getEstimatedTotalHeight(props, instanceProps);
var maxOffset = Math.min(estimatedTotalSize - size, itemMetadata.offset);
var minOffset = Math.max(0, itemMetadata.offset - size + itemMetadata.size);
switch (align) {
case 'start':
return maxOffset;
case 'end':
return minOffset;
case 'center':
return Math.round(minOffset + (maxOffset - minOffset) / 2);
case 'auto':
default:
if (scrollOffset >= minOffset && scrollOffset <= maxOffset) {
return scrollOffset;
} else if (scrollOffset - minOffset < maxOffset - scrollOffset) {
return minOffset;
} else {
return maxOffset;
}
}
};
var VariableSizeGrid = createGridComponent({
getColumnOffset: function getColumnOffset(props, index, instanceProps) {
return getItemMetadata('column', props, index, instanceProps).offset;
},
getColumnStartIndexForOffset: function getColumnStartIndexForOffset(props, scrollLeft, instanceProps) {
return findNearestItem('column', props, instanceProps, scrollLeft);
},
getColumnStopIndexForStartIndex: function getColumnStopIndexForStartIndex(props, startIndex, scrollLeft, instanceProps) {
var columnCount = props.columnCount,
width = props.width;
var itemMetadata = getItemMetadata('column', props, startIndex, instanceProps);
var maxOffset = scrollLeft + width;
var offset = itemMetadata.offset + itemMetadata.size;
var stopIndex = startIndex;
while (stopIndex < columnCount - 1 && offset < maxOffset) {
stopIndex++;
offset += getItemMetadata('column', props, stopIndex, instanceProps).size;
}
return stopIndex;
},
getColumnWidth: function getColumnWidth(props, index, instanceProps) {
return instanceProps.columnMetadataMap[index].size;
},
getEstimatedTotalHeight: getEstimatedTotalHeight,
getEstimatedTotalWidth: getEstimatedTotalWidth,
getOffsetForColumnAndAlignment: function getOffsetForColumnAndAlignment(props, index, align, scrollOffset, instanceProps) {
return getOffsetForIndexAndAlignment('column', props, index, align, scrollOffset, instanceProps);
},
getOffsetForRowAndAlignment: function getOffsetForRowAndAlignment(props, index, align, scrollOffset, instanceProps) {
return getOffsetForIndexAndAlignment('row', props, index, align, scrollOffset, instanceProps);
},
getRowOffset: function getRowOffset(props, index, instanceProps) {
return getItemMetadata('row', props, index, instanceProps).offset;
},
getRowHeight: function getRowHeight(props, index, instanceProps) {
return instanceProps.rowMetadataMap[index].size;
},
getRowStartIndexForOffset: function getRowStartIndexForOffset(props, scrollTop, instanceProps) {
return findNearestItem('row', props, instanceProps, scrollTop);
},
getRowStopIndexForStartIndex: function getRowStopIndexForStartIndex(props, startIndex, scrollTop, instanceProps) {
var rowCount = props.rowCount,
height = props.height;
var itemMetadata = getItemMetadata('row', props, startIndex, instanceProps);
var maxOffset = scrollTop + height;
var offset = itemMetadata.offset + itemMetadata.size;
var stopIndex = startIndex;
while (stopIndex < rowCount - 1 && offset < maxOffset) {
stopIndex++;
offset += getItemMetadata('row', props, stopIndex, instanceProps).size;
}
return stopIndex;
},
initInstanceProps: function initInstanceProps(props, instance) {
var _this = this;
var _ref5 = props,
estimatedColumnWidth = _ref5.estimatedColumnWidth,
estimatedRowHeight = _ref5.estimatedRowHeight;
var instanceProps = {
columnMetadataMap: {},
estimatedColumnWidth: estimatedColumnWidth || DEFAULT_ESTIMATED_ITEM_SIZE,
estimatedRowHeight: estimatedRowHeight || DEFAULT_ESTIMATED_ITEM_SIZE,
lastMeasuredColumnIndex: -1,
lastMeasuredRowIndex: -1,
rowMetadataMap: {}
};
instance.resetAfterColumnIndex = function (columnIndex) {
_this.resetAfterIndices({ columnIndex: columnIndex });
};
instance.resetAfterRowIndex = function (rowIndex) {
_this.resetAfterIndices({ rowIndex: rowIndex });
};
instance.resetAfterIndices = function (_ref6) {
var columnIndex = _ref6.columnIndex,
rowIndex = _ref6.rowIndex;
if (typeof columnIndex === 'number') {
instanceProps.lastMeasuredColumnIndex = Math.min(instanceProps.lastMeasuredColumnIndex, columnIndex - 1);
}
if (typeof rowIndex === 'number') {
instanceProps.lastMeasuredRowIndex = Math.min(instanceProps.lastMeasuredRowIndex, rowIndex - 1);
}
// We could potentially optimize further by only evicting styles after this index,
// But since styles are only cached while scrolling is in progress-
// It seems an unnecessary optimization.
// It's unlikely that resetAfterIndex() will be called while a user is scrolling.
instance._itemStyleCache = {};
instance.forceUpdate();
};
return instanceProps;
},
validateProps: function validateProps(_ref7) {
var columnWidth = _ref7.columnWidth,
rowHeight = _ref7.rowHeight;
if (process.env.NODE_ENV !== 'production') {
if (typeof columnWidth !== 'function') {
throw Error('An invalid "columnWidth" prop has been specified. ' + 'Value should be a function. ' + ('"' + (columnWidth === null ? 'null' : typeof columnWidth === 'undefined' ? 'undefined' : _typeof(columnWidth)) + '" was specified.'));
} else if (typeof rowHeight !== 'function') {
throw Error('An invalid "rowHeight" prop has been specified. ' + 'Value should be a function. ' + ('"' + (rowHeight === null ? 'null' : typeof rowHeight === 'undefined' ? 'undefined' : _typeof(rowHeight)) + '" was specified.'));
}
}
}
});
var IS_SCROLLING_DEBOUNCE_INTERVAL$1 = 150;
var defaultItemKey$1 = function defaultItemKey(index) {
return index;
};
function createListComponent(_ref) {
var _class, _temp2;
var getItemOffset = _ref.getItemOffset,
getEstimatedTotalSize = _ref.getEstimatedTotalSize,
getItemSize = _ref.getItemSize,
getOffsetForIndexAndAlignment = _ref.getOffsetForIndexAndAlignment,
getStartIndexForOffset = _ref.getStartIndexForOffset,
getStopIndexForStartIndex = _ref.getStopIndexForStartIndex,
initInstanceProps = _ref.initInstanceProps,
validateProps = _ref.validateProps;
return _temp2 = _class = function (_PureComponent) {
inherits(List, _PureComponent);
function List() {
var _ref2;
var _temp, _this, _ret;
classCallCheck(this, List);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = possibleConstructorReturn(this, (_ref2 = List.__proto__ || Object.getPrototypeOf(List)).call.apply(_ref2, [this].concat(args))), _this), _this._instanceProps = initInstanceProps(_this.props, _this), _this._itemStyleCache = {}, _this._resetIsScrollingTimeoutId = null, _this.state = {
isScrolling: false,
scrollDirection: 'forward',
scrollOffset: typeof _this.props.initialScrollOffset === 'number' ? _this.props.initialScrollOffset : 0,
scrollUpdateWasRequested: false
}, _this._callOnItemsRendered = memoizeOne(function (overscanStartIndex, overscanStopIndex, visibleStartIndex, visibleStopIndex) {
return _this.props.onItemsRendered({
overscanStartIndex: overscanStartIndex,
overscanStopIndex: overscanStopIndex,
visibleStartIndex: visibleStartIndex,
visibleStopIndex: visibleStopIndex
});
}), _this._callOnScroll = memoizeOne(function (scrollDirection, scrollOffset, scrollUpdateWasRequested) {
return _this.props.onScroll({
scrollDirection: scrollDirection,
scrollOffset: scrollOffset,
scrollUpdateWasRequested: scrollUpdateWasRequested
});
}), _this._getItemStyle = function (index) {
var direction = _this.props.direction;
var style = void 0;
if (_this._itemStyleCache.hasOwnProperty(index)) {
style = _this._itemStyleCache[index];
} else {
_this._itemStyleCache[index] = style = {
position: 'absolute',
left: direction === 'horizontal' ? getItemOffset(_this.props, index, _this._instanceProps) : 0,
top: direction === 'vertical' ? getItemOffset(_this.props, index, _this._instanceProps) : 0,
height: direction === 'vertical' ? getItemSize(_this.props, index, _this._instanceProps) : '100%',
width: direction === 'horizontal' ? getItemSize(_this.props, index, _this._instanceProps) : '100%'
};
}
return style;
}, _this._onScrollHorizontal = function (event) {
var scrollLeft = event.currentTarget.scrollLeft;
_this.setState(function (prevState) {
if (prevState.scrollOffset === scrollLeft) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
return {
isScrolling: true,
scrollDirection: prevState.scrollOffset < scrollLeft ? 'forward' : 'backward',
scrollOffset: scrollLeft,
scrollUpdateWasRequested: false
};
}, _this._resetIsScrollingDebounced);
}, _this._onScrollVertical = function (event) {
var scrollTop = event.currentTarget.scrollTop;
_this.setState(function (prevState) {
if (prevState.scrollOffset === scrollTop) {
// Scroll position may have been updated by cDM/cDU,
// In which case we don't need to trigger another render,
// And we don't want to update state.isScrolling.
return null;
}
return {
isScrolling: true,
scrollDirection: prevState.scrollOffset < scrollTop ? 'forward' : 'backward',
scrollOffset: scrollTop,
scrollUpdateWasRequested: false
};
}, _this._resetIsScrollingDebounced);
}, _this._outerRefSetter = function (ref) {
var outerRef = _this.props.outerRef;
_this._outerRef = ref;
if (typeof outerRef === 'function') {
outerRef(ref);
} else if (outerRef != null && (typeof outerRef === 'undefined' ? 'undefined' : _typeof(outerRef)) === 'object' && outerRef.hasOwnProperty('current')) {
outerRef.current = ref;
}
}, _this._resetIsScrollingDebounced = function () {
if (_this._resetIsScrollingTimeoutId !== null) {
clearTimeout(_this._resetIsScrollingTimeoutId);
}
_this._resetIsScrollingTimeoutId = setTimeout(_this._resetIsScrolling, IS_SCROLLING_DEBOUNCE_INTERVAL$1);
}, _this._resetIsScrolling = function () {
_this._resetIsScrollingTimeoutId = null;
_this.setState({ isScrolling: false }, function () {
// Clear style cache after state update has been committed.
// This way we don't break pure sCU for items that don't use isScrolling param.
_this._itemStyleCache = {};
});
}, _temp), possibleConstructorReturn(_this, _ret);
}
createClass(List, [{
key: 'scrollTo',
value: function scrollTo(scrollOffset) {
this.setState(function (prevState) {
return {
scrollDirection: prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
scrollOffset: scrollOffset,
scrollUpdateWasRequested: true
};
}, this._resetIsScrollingDebounced);
}
}, {
key: 'scrollToItem',
value: function scrollToItem(index) {
var align = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
var scrollOffset = this.state.scrollOffset;
this.scrollTo(getOffsetForIndexAndAlignment(this.props, index, align, scrollOffset, this._instanceProps));
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props,
initialScrollOffset = _props.initialScrollOffset,
direction = _props.direction;
if (typeof initialScrollOffset === 'number' && this._outerRef !== null) {
if (direction === 'horizontal') {
this._outerRef.scrollLeft = initialScrollOffset;
} else {
this._outerRef.scrollTop = initialScrollOffset;
}
}
this._callPropsCallbacks();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
var direction = this.props.direction;
var _state = this.state,
scrollOffset = _state.scrollOffset,
scrollUpdateWasRequested = _state.scrollUpdateWasRequested;
if (scrollUpdateWasRequested && this._outerRef !== null) {
if (direction === 'horizontal') {
this._outerRef.scrollLeft = scrollOffset;
} else {
this._outerRef.scrollTop = scrollOffset;
}
}
this._callPropsCallbacks();
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (this._resetIsScrollingTimeoutId !== null) {
clearTimeout(this._resetIsScrollingTimeoutId);
}
}
}, {
key: 'render',
value: function render() {
var _props2 = this.props,
children = _props2.children,
className = _props2.className,
direction = _props2.direction,
height = _props2.height,
innerRef = _props2.innerRef,
innerTagName = _props2.innerTagName,
itemCount = _props2.itemCount,
itemData = _props2.itemData,
_props2$itemKey = _props2.itemKey,
itemKey = _props2$itemKey === undefined ? defaultItemKey$1 : _props2$itemKey,
outerTagName = _props2.outerTagName,
style = _props2.style,
useIsScrolling = _props2.useIsScrolling,
width = _props2.width;
var isScrolling = this.state.isScrolling;
var onScroll = direction === 'vertical' ? this._onScrollVertical : this._onScrollHorizontal;
var _getRangeToRender2 = this._getRangeToRender(),
_getRangeToRender3 = slicedToArray(_getRangeToRender2, 2),
startIndex = _getRangeToRender3[0],
stopIndex = _getRangeToRender3[1];
var items = [];
if (itemCount > 0) {
for (var _index = startIndex; _index <= stopIndex; _index++) {
items.push(React.createElement(children, {
data: itemData,
key: itemKey(_index),
index: _index,
isScrolling: useIsScrolling ? isScrolling : undefined,
style: this._getItemStyle(_index)
}));
}
}
// Read this value AFTER items have been created,
// So their actual sizes (if variable) are taken into consideration.
var estimatedTotalSize = getEstimatedTotalSize(this.props, this._instanceProps);
return React.createElement(outerTagName, {
className: className,
onScroll: onScroll,
ref: this._outerRefSetter,
style: _extends({
position: 'relative',
height: height,
width: width,
overflow: 'auto',
WebkitOverflowScrolling: 'touch',
willChange: 'transform'
}, style)
}, React.createElement(innerTagName, {
children: items,
ref: innerRef,
style: {
height: direction === 'horizontal' ? height : estimatedTotalSize,
overflow: 'hidden',
pointerEvents: isScrolling ? 'none' : '',
width: direction === 'horizontal' ? estimatedTotalSize : width
}
}));
}
}, {
key: '_callPropsCallbacks',
value: function _callPropsCallbacks() {
if (typeof this.props.onItemsRendered === 'function') {
var _itemCount = this.props.itemCount;
if (_itemCount > 0) {
var _getRangeToRender4 = this._getRangeToRender(),
_getRangeToRender5 = slicedToArray(_getRangeToRender4, 4),
_overscanStartIndex = _getRangeToRender5[0],
_overscanStopIndex = _getRangeToRender5[1],
_visibleStartIndex = _getRangeToRender5[2],
_visibleStopIndex = _getRangeToRender5[3];
this._callOnItemsRendered(_overscanStartIndex, _overscanStopIndex, _visibleStartIndex, _visibleStopIndex);
}
}
if (typeof this.props.onScroll === 'function') {
var _state2 = this.state,
_scrollDirection = _state2.scrollDirection,
_scrollOffset = _state2.scrollOffset,
_scrollUpdateWasRequested = _state2.scrollUpdateWasRequested;
this._callOnScroll(_scrollDirection, _scrollOffset, _scrollUpdateWasRequested);
}
}
// Lazily create and cache item styles while scrolling,
// So that pure component sCU will prevent re-renders.
// We maintain this cache, and pass a style prop rather than index,
// So that List can clear cached styles and force item re-render if necessary.
}, {
key: '_getRangeToRender',
value: function _getRangeToRender() {
var _props3 = this.props,
itemCount = _props3.itemCount,
overscanCount = _props3.overscanCount;
var _state3 = this.state,
scrollDirection = _state3.scrollDirection,
scrollOffset = _state3.scrollOffset;
var startIndex = getStartIndexForOffset(this.props, scrollOffset, this._instanceProps);
var stopIndex = getStopIndexForStartIndex(this.props, startIndex, scrollOffset, this._instanceProps);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
var overscanBackward = scrollDirection === 'backward' ? Math.max(1, overscanCount) : 1;
var overscanForward = scrollDirection === 'forward' ? Math.max(1, overscanCount) : 1;
return [Math.max(0, startIndex - overscanBackward), Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), startIndex, stopIndex];
}
}], [{
key: 'getDerivedStateFromProps',
value: function getDerivedStateFromProps(nextProps, prevState) {
validateSharedProps$1(nextProps);
validateProps(nextProps);
return null;
}
}]);
return List;
}(React.PureComponent), _class.defaultProps = {
direction: 'vertical',
innerTagName: 'div',
outerTagName: 'div',
overscanCount: 2,
useIsScrolling: false
}, _temp2;
}
// NOTE: I considered further wrapping individual items with a pure ListItem component.
// This would avoid ever calling the render function for the same index more than once,
// But it would also add the overhead of a lot of components/fibers.
// I assume people already do this (render function returning a class component),
// So my doing it would just unnecessarily double the wrappers.
var validateSharedProps$1 = function validateSharedProps(_ref3) {
var children = _ref3.children,
direction = _ref3.direction,
height = _ref3.height,
width = _ref3.width;
if (process.env.NODE_ENV !== 'production') {
if (direction !== 'horizontal' && direction !== 'vertical') {
throw Error('An invalid "direction" prop has been specified. ' + 'Value should be either "horizontal" or "vertical". ' + ('"' + direction + '" was specified.'));
}
if (typeof children !== 'function') {
throw Error('An invalid "children" prop has been specified. ' + 'Value should be a function that creates a React element. ' + ('"' + (children === null ? 'null' : typeof children === 'undefined' ? 'undefined' : _typeof(children)) + '" was specified.'));
}
if (direction === 'horizontal' && typeof width !== 'number') {
throw Error('An invalid "width" prop has been specified. ' + 'Horizontal lists must specify a number for width. ' + ('"' + (width === null ? 'null' : typeof width === 'undefined' ? 'undefined' : _typeof(width)) + '" was specified.'));
} else if (direction === 'vertical' && typeof height !== 'number') {
throw Error('An invalid "height" prop has been specified. ' + 'Vertical lists must specify a number for height. ' + ('"' + (height === null ? 'null' : typeof height === 'undefined' ? 'undefined' : _typeof(height)) + '" was specified.'));
}
}
};
var DEFAULT_ESTIMATED_ITEM_SIZE$1 = 50;
var getItemMetadata$1 = function getItemMetadata(props, index, instanceProps) {
var _ref = props,
itemSize = _ref.itemSize;
var itemMetadataMap = instanceProps.itemMetadataMap,
lastMeasuredIndex = instanceProps.lastMeasuredIndex;
if (index > lastMeasuredIndex) {
var _offset = 0;
if (lastMeasuredIndex >= 0) {
var itemMetadata = itemMetadataMap[lastMeasuredIndex];
_offset = itemMetadata.offset + itemMetadata.size;
}
for (var i = lastMeasuredIndex + 1; i <= index; i++) {
var _size = itemSize(i);
itemMetadataMap[i] = {