react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
307 lines (245 loc) • 10.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _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 _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _CollectionView = require('./CollectionView');
var _CollectionView2 = _interopRequireDefault(_CollectionView);
var _calculateSizeAndPositionData2 = require('./utils/calculateSizeAndPositionData');
var _calculateSizeAndPositionData3 = _interopRequireDefault(_calculateSizeAndPositionData2);
var _getUpdatedOffsetForIndex = require('../utils/getUpdatedOffsetForIndex');
var _getUpdatedOffsetForIndex2 = _interopRequireDefault(_getUpdatedOffsetForIndex);
var _reactAddonsShallowCompare = require('react-addons-shallow-compare');
var _reactAddonsShallowCompare2 = _interopRequireDefault(_reactAddonsShallowCompare);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(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; }
function _inherits(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; }
/**
* Renders scattered or non-linear data.
* Unlike Grid, which renders checkerboard data, Collection can render arbitrarily positioned- even overlapping- data.
*/
var Collection = function (_Component) {
_inherits(Collection, _Component);
function Collection(props, context) {
_classCallCheck(this, Collection);
var _this = _possibleConstructorReturn(this, (Collection.__proto__ || Object.getPrototypeOf(Collection)).call(this, props, context));
_this._cellMetadata = [];
_this._lastRenderedCellIndices = [];
// Cell cache during scroll (for perforamnce)
_this._cellCache = [];
_this._isScrollingChange = _this._isScrollingChange.bind(_this);
return _this;
}
/** See Collection#recomputeCellSizesAndPositions */
_createClass(Collection, [{
key: 'recomputeCellSizesAndPositions',
value: function recomputeCellSizesAndPositions() {
this._cellCache = [];
this._collectionView.recomputeCellSizesAndPositions();
}
/** React lifecycle methods */
}, {
key: 'render',
value: function render() {
var _this2 = this;
var props = _objectWithoutProperties(this.props, []);
return _react2.default.createElement(_CollectionView2.default, _extends({
cellLayoutManager: this,
isScrollingChange: this._isScrollingChange,
ref: function ref(_ref) {
_this2._collectionView = _ref;
}
}, props));
}
}, {
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate(nextProps, nextState) {
return (0, _reactAddonsShallowCompare2.default)(this, nextProps, nextState);
}
/** CellLayoutManager interface */
}, {
key: 'calculateSizeAndPositionData',
value: function calculateSizeAndPositionData() {
var _props = this.props;
var cellCount = _props.cellCount;
var cellSizeAndPositionGetter = _props.cellSizeAndPositionGetter;
var sectionSize = _props.sectionSize;
var data = (0, _calculateSizeAndPositionData3.default)({
cellCount: cellCount,
cellSizeAndPositionGetter: cellSizeAndPositionGetter,
sectionSize: sectionSize
});
this._cellMetadata = data.cellMetadata;
this._sectionManager = data.sectionManager;
this._height = data.height;
this._width = data.width;
}
/**
* Returns the most recently rendered set of cell indices.
*/
}, {
key: 'getLastRenderedIndices',
value: function getLastRenderedIndices() {
return this._lastRenderedCellIndices;
}
/**
* Calculates the minimum amount of change from the current scroll position to ensure the specified cell is (fully) visible.
*/
}, {
key: 'getScrollPositionForCell',
value: function getScrollPositionForCell(_ref2) {
var align = _ref2.align;
var cellIndex = _ref2.cellIndex;
var height = _ref2.height;
var scrollLeft = _ref2.scrollLeft;
var scrollTop = _ref2.scrollTop;
var width = _ref2.width;
var cellCount = this.props.cellCount;
if (cellIndex >= 0 && cellIndex < cellCount) {
var cellMetadata = this._cellMetadata[cellIndex];
scrollLeft = (0, _getUpdatedOffsetForIndex2.default)({
align: align,
cellOffset: cellMetadata.x,
cellSize: cellMetadata.width,
containerSize: width,
currentOffset: scrollLeft,
targetIndex: cellIndex
});
scrollTop = (0, _getUpdatedOffsetForIndex2.default)({
align: align,
cellOffset: cellMetadata.y,
cellSize: cellMetadata.height,
containerSize: height,
currentOffset: scrollTop,
targetIndex: cellIndex
});
}
return {
scrollLeft: scrollLeft,
scrollTop: scrollTop
};
}
}, {
key: 'getTotalSize',
value: function getTotalSize() {
return {
height: this._height,
width: this._width
};
}
}, {
key: 'cellRenderers',
value: function cellRenderers(_ref3) {
var _this3 = this;
var height = _ref3.height;
var isScrolling = _ref3.isScrolling;
var width = _ref3.width;
var x = _ref3.x;
var y = _ref3.y;
var _props2 = this.props;
var cellGroupRenderer = _props2.cellGroupRenderer;
var cellRenderer = _props2.cellRenderer;
// Store for later calls to getLastRenderedIndices()
this._lastRenderedCellIndices = this._sectionManager.getCellIndices({
height: height,
width: width,
x: x,
y: y
});
return cellGroupRenderer({
cellCache: this._cellCache,
cellRenderer: cellRenderer,
cellSizeAndPositionGetter: function cellSizeAndPositionGetter(_ref4) {
var index = _ref4.index;
return _this3._sectionManager.getCellMetadata({ index: index });
},
indices: this._lastRenderedCellIndices,
isScrolling: isScrolling
});
}
}, {
key: '_isScrollingChange',
value: function _isScrollingChange(isScrolling) {
if (!isScrolling) {
this._cellCache = [];
}
}
}]);
return Collection;
}(_react.Component);
Collection.propTypes = {
'aria-label': _react.PropTypes.string,
/**
* Number of cells in Collection.
*/
cellCount: _react.PropTypes.number.isRequired,
/**
* Responsible for rendering a group of cells given their indices.
* Should implement the following interface: ({
* cellSizeAndPositionGetter:Function,
* indices: Array<number>,
* cellRenderer: Function
* }): Array<PropTypes.node>
*/
cellGroupRenderer: _react.PropTypes.func.isRequired,
/**
* Responsible for rendering a cell given an row and column index.
* Should implement the following interface: ({ index: number, key: string, style: object }): PropTypes.element
*/
cellRenderer: _react.PropTypes.func.isRequired,
/**
* Callback responsible for returning size and offset/position information for a given cell (index).
* ({ index: number }): { height: number, width: number, x: number, y: number }
*/
cellSizeAndPositionGetter: _react.PropTypes.func.isRequired,
/**
* Optionally override the size of the sections a Collection's cells are split into.
*/
sectionSize: _react.PropTypes.number
};
Collection.defaultProps = {
'aria-label': 'grid',
cellGroupRenderer: defaultCellGroupRenderer
};
exports.default = Collection;
function defaultCellGroupRenderer(_ref5) {
var cellCache = _ref5.cellCache;
var cellRenderer = _ref5.cellRenderer;
var cellSizeAndPositionGetter = _ref5.cellSizeAndPositionGetter;
var indices = _ref5.indices;
var isScrolling = _ref5.isScrolling;
return indices.map(function (index) {
var cellMetadata = cellSizeAndPositionGetter({ index: index });
var cellRendererProps = {
index: index,
isScrolling: isScrolling,
key: index,
style: {
height: cellMetadata.height,
left: cellMetadata.x,
position: 'absolute',
top: cellMetadata.y,
width: cellMetadata.width
}
};
// Avoid re-creating cells while scrolling.
// This can lead to the same cell being created many times and can cause performance issues for "heavy" cells.
// If a scroll is in progress- cache and reuse cells.
// This cache will be thrown away once scrolling complets.
if (isScrolling) {
if (!(index in cellCache)) {
cellCache[index] = cellRenderer(cellRendererProps);
}
return cellCache[index];
} else {
return cellRenderer(cellRendererProps);
}
}).filter(function (renderedCell) {
return !!renderedCell;
});
}