forked from cybrespace/mastodon
- On recent mouse movement, hold timeline position so statuses remain in place for interactions in progress. - If the timeline had been scrolled to the top before mouse movement, restore scroll on mouse idle.
This commit is contained in:
parent
cd41c2c6ad
commit
6a1216d2cd
|
@ -9,6 +9,8 @@ import { List as ImmutableList } from 'immutable';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
||||||
|
|
||||||
|
const MOUSE_IDLE_DELAY = 300;
|
||||||
|
|
||||||
export default class ScrollableList extends PureComponent {
|
export default class ScrollableList extends PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -37,6 +39,8 @@ export default class ScrollableList extends PureComponent {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
fullscreen: null,
|
fullscreen: null,
|
||||||
|
mouseMovedRecently: false,
|
||||||
|
scrollToTopOnMouseIdle: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
intersectionObserverWrapper = new IntersectionObserverWrapper();
|
||||||
|
@ -60,6 +64,47 @@ export default class ScrollableList extends PureComponent {
|
||||||
trailing: true,
|
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 () {
|
componentDidMount () {
|
||||||
this.attachScrollListener();
|
this.attachScrollListener();
|
||||||
this.attachIntersectionObserver();
|
this.attachIntersectionObserver();
|
||||||
|
@ -73,7 +118,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
|
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
|
||||||
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
||||||
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
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;
|
return this.node.scrollHeight - this.node.scrollTop;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -93,6 +138,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
this.clearMouseIdleTimer();
|
||||||
this.detachScrollListener();
|
this.detachScrollListener();
|
||||||
this.detachIntersectionObserver();
|
this.detachIntersectionObserver();
|
||||||
detachFullscreenListener(this.onFullScreenChange);
|
detachFullscreenListener(this.onFullScreenChange);
|
||||||
|
@ -151,7 +197,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
|
|
||||||
if (isLoading || childrenCount > 0 || !emptyMessage) {
|
if (isLoading || childrenCount > 0 || !emptyMessage) {
|
||||||
scrollableArea = (
|
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'>
|
<div role='feed' className='item-list'>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue