UNPKG

@humanmade/react-flame-graph

Version:

React component for visualizing profiling data

1,297 lines (1,082 loc) 79.1 kB
'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] = {