UNPKG

react-virtualized

Version:

React components for efficiently rendering large, scrollable lists and tabular data

307 lines (245 loc) 10.5 kB
'use strict'; 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; }); }