react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
258 lines (211 loc) • 8.62 kB
JavaScript
import _extends from 'babel-runtime/helpers/extends';
import _Object$getPrototypeOf from 'babel-runtime/core-js/object/get-prototype-of';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _createClass from 'babel-runtime/helpers/createClass';
import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
import _inherits from 'babel-runtime/helpers/inherits';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { registerScrollListener, unregisterScrollListener } from './utils/onScroll';
import { getDimensions, getPositionOffset, getScrollOffset } from './utils/dimensions';
import createDetectElementResize from '../vendor/detectElementResize';
/**
* Specifies the number of miliseconds during which to disable pointer events while a scroll is in progress.
* This improves performance and makes scrolling smoother.
*/
export var IS_SCROLLING_TIMEOUT = 150;
var getWindow = function getWindow() {
return typeof window !== 'undefined' ? window : undefined;
};
var WindowScroller = function (_React$PureComponent) {
_inherits(WindowScroller, _React$PureComponent);
function WindowScroller() {
var _ref;
var _temp, _this, _ret;
_classCallCheck(this, WindowScroller);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = WindowScroller.__proto__ || _Object$getPrototypeOf(WindowScroller)).call.apply(_ref, [this].concat(args))), _this), _this._window = getWindow(), _this._isMounted = false, _this._positionFromTop = 0, _this._positionFromLeft = 0, _this.state = _extends({}, getDimensions(_this.props.scrollElement, _this.props), {
isScrolling: false,
scrollLeft: 0,
scrollTop: 0
}), _this._registerChild = function (element) {
if (element && !(element instanceof Element)) {
console.warn('WindowScroller registerChild expects to be passed Element or null');
}
_this._child = element;
_this.updatePosition();
}, _this._onChildScroll = function (_ref2) {
var scrollTop = _ref2.scrollTop;
if (_this.state.scrollTop === scrollTop) {
return;
}
var scrollElement = _this.props.scrollElement;
if (scrollElement) {
if (typeof scrollElement.scrollTo === 'function') {
scrollElement.scrollTo(0, scrollTop + _this._positionFromTop);
} else {
scrollElement.scrollTop = scrollTop + _this._positionFromTop;
}
}
}, _this._registerResizeListener = function (element) {
if (element === window) {
window.addEventListener('resize', _this._onResize, false);
} else {
_this._detectElementResize.addResizeListener(element, _this._onResize);
}
}, _this._unregisterResizeListener = function (element) {
if (element === window) {
window.removeEventListener('resize', _this._onResize, false);
} else if (element) {
_this._detectElementResize.removeResizeListener(element, _this._onResize);
}
}, _this._onResize = function () {
_this.updatePosition();
}, _this.__handleWindowScrollEvent = function () {
if (!_this._isMounted) {
return;
}
var onScroll = _this.props.onScroll;
var scrollElement = _this.props.scrollElement;
if (scrollElement) {
var scrollOffset = getScrollOffset(scrollElement);
var _scrollLeft = Math.max(0, scrollOffset.left - _this._positionFromLeft);
var _scrollTop = Math.max(0, scrollOffset.top - _this._positionFromTop);
_this.setState({
isScrolling: true,
scrollLeft: _scrollLeft,
scrollTop: _scrollTop
});
onScroll({
scrollLeft: _scrollLeft,
scrollTop: _scrollTop
});
}
}, _this.__resetIsScrolling = function () {
_this.setState({
isScrolling: false
});
}, _temp), _possibleConstructorReturn(_this, _ret);
}
_createClass(WindowScroller, [{
key: 'updatePosition',
value: function updatePosition() {
var scrollElement = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props.scrollElement;
var onResize = this.props.onResize;
var _state = this.state,
height = _state.height,
width = _state.width;
var thisNode = this._child || ReactDOM.findDOMNode(this);
if (thisNode instanceof Element && scrollElement) {
var offset = getPositionOffset(thisNode, scrollElement);
this._positionFromTop = offset.top;
this._positionFromLeft = offset.left;
}
var dimensions = getDimensions(scrollElement, this.props);
if (height !== dimensions.height || width !== dimensions.width) {
this.setState({
height: dimensions.height,
width: dimensions.width
});
onResize({
height: dimensions.height,
width: dimensions.width
});
}
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
var scrollElement = this.props.scrollElement;
this._detectElementResize = createDetectElementResize();
this.updatePosition(scrollElement);
if (scrollElement) {
registerScrollListener(this, scrollElement);
this._registerResizeListener(scrollElement);
}
this._isMounted = true;
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var scrollElement = this.props.scrollElement;
var prevScrollElement = prevProps.scrollElement;
if (prevScrollElement !== scrollElement && prevScrollElement != null && scrollElement != null) {
this.updatePosition(scrollElement);
unregisterScrollListener(this, prevScrollElement);
registerScrollListener(this, scrollElement);
this._unregisterResizeListener(prevScrollElement);
this._registerResizeListener(scrollElement);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
var scrollElement = this.props.scrollElement;
if (scrollElement) {
unregisterScrollListener(this, scrollElement);
this._unregisterResizeListener(scrollElement);
}
this._isMounted = false;
}
}, {
key: 'render',
value: function render() {
var children = this.props.children;
var _state2 = this.state,
isScrolling = _state2.isScrolling,
scrollTop = _state2.scrollTop,
scrollLeft = _state2.scrollLeft,
height = _state2.height,
width = _state2.width;
return children({
onChildScroll: this._onChildScroll,
registerChild: this._registerChild,
height: height,
isScrolling: isScrolling,
scrollLeft: scrollLeft,
scrollTop: scrollTop,
width: width
});
}
// Referenced by utils/onScroll
// Referenced by utils/onScroll
}]);
return WindowScroller;
}(React.PureComponent);
WindowScroller.defaultProps = {
onResize: function onResize() {},
onScroll: function onScroll() {},
scrollingResetTimeInterval: IS_SCROLLING_TIMEOUT,
scrollElement: getWindow(),
serverHeight: 0,
serverWidth: 0
};
WindowScroller.propTypes = process.env.NODE_ENV === 'production' ? null : {
/**
* Function responsible for rendering children.
* This function should implement the following signature:
* ({ height, isScrolling, scrollLeft, scrollTop, width }) => PropTypes.element
*/
children: PropTypes.func.isRequired,
/** Callback to be invoked on-resize: ({ height, width }) */
onResize: PropTypes.func.isRequired,
/** Callback to be invoked on-scroll: ({ scrollLeft, scrollTop }) */
onScroll: PropTypes.func.isRequired,
/** Element to attach scroll event listeners. Defaults to window. */
scrollElement: PropTypes.oneOfType([PropTypes.any, function () {
return (typeof Element === 'function' ? PropTypes.instanceOf(Element) : PropTypes.any).apply(this, arguments);
}]),
/**
* Wait this amount of time after the last scroll event before resetting child `pointer-events`.
*/
scrollingResetTimeInterval: PropTypes.number.isRequired,
/** Height used for server-side rendering */
serverHeight: PropTypes.number.isRequired,
/** Width used for server-side rendering */
serverWidth: PropTypes.number.isRequired
};
export default WindowScroller;
import PropTypes from 'prop-types';