import classNames from 'classnames';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
+const MOUSE_IDLE_DELAY = 300;
+
export default class ScrollableList extends PureComponent {
static contextTypes = {
state = {
fullscreen: null,
+ mouseMovedRecently: false,
+ scrollToTopOnMouseIdle: false,
};
intersectionObserverWrapper = new IntersectionObserverWrapper();
trailing: true,
});
+ mouseIdleTimer = null;
+
+ clearMouseIdleTimer = () => {
+ if (this.mouseIdleTimer === null) {
+ return;
+ }
+ clearTimeout(this.mouseIdleTimer);
+ this.mouseIdleTimer = null;
+ };
+
+ handleMouseMove = throttle(() => {
+ // As long as the mouse keeps moving, clear and restart the idle timer.
+ this.clearMouseIdleTimer();
+ this.mouseIdleTimer =
+ setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
+
+ this.setState(({
+ mouseMovedRecently,
+ scrollToTopOnMouseIdle,
+ }) => ({
+ mouseMovedRecently: true,
+ // Only set scrollToTopOnMouseIdle if we just started moving and were
+ // scrolled to the top. Otherwise, just retain the previous state.
+ scrollToTopOnMouseIdle:
+ mouseMovedRecently
+ ? scrollToTopOnMouseIdle
+ : (this.node.scrollTop === 0),
+ }));
+ }, MOUSE_IDLE_DELAY / 2);
+
+ handleMouseIdle = () => {
+ if (this.state.scrollToTopOnMouseIdle) {
+ this.node.scrollTop = 0;
+ this.props.onScrollToTop();
+ }
+ this.setState({
+ mouseMovedRecently: false,
+ scrollToTopOnMouseIdle: false,
+ });
+ }
+
componentDidMount () {
this.attachScrollListener();
this.attachIntersectionObserver();
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
- if (someItemInserted && this.node.scrollTop > 0) {
+ if ((someItemInserted && this.node.scrollTop > 0) || this.state.mouseMovedRecently) {
return this.node.scrollHeight - this.node.scrollTop;
} else {
return null;
}
componentWillUnmount () {
+ this.clearMouseIdleTimer();
this.detachScrollListener();
this.detachIntersectionObserver();
detachFullscreenListener(this.onFullScreenChange);
if (isLoading || childrenCount > 0 || !emptyMessage) {
scrollableArea = (
- <div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
+ <div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
<div role='feed' className='item-list'>
{prepend}