diff options
author | pluralcafe-docker <git@plural.cafe> | 2019-01-12 04:24:14 +0000 |
---|---|---|
committer | pluralcafe-docker <git@plural.cafe> | 2019-01-12 04:24:14 +0000 |
commit | ae24ef877b82466cab026a76ea6ed8598ddaae18 (patch) | |
tree | 584c25ada377f2e6da49a9b45b3da947501b1831 /app/javascript/flavours/glitch/components/scrollable_list.js | |
parent | fa1ab7fbe4166f3d8b481891dcc443df5aa212bb (diff) | |
parent | 2cfa55185a5fc7d93a160a4e9a4730aae6725b0f (diff) |
Merge branch 'glitch'
Diffstat (limited to 'app/javascript/flavours/glitch/components/scrollable_list.js')
-rw-r--r-- | app/javascript/flavours/glitch/components/scrollable_list.js | 68 |
1 files changed, 63 insertions, 5 deletions
diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 21d717b81..7cd0774a9 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -10,6 +10,8 @@ import classNames from 'classnames'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen'; import LoadingIndicator from './loading_indicator'; +const MOUSE_IDLE_DELAY = 300; + export default class ScrollableList extends PureComponent { static contextTypes = { @@ -56,11 +58,66 @@ export default class ScrollableList extends PureComponent { } else if (this.props.onScroll) { this.props.onScroll(); } + + if (!this.lastScrollWasSynthetic) { + // If the last scroll wasn't caused by setScrollTop(), assume it was + // intentional and cancel any pending scroll reset on mouse idle + this.scrollToTopOnMouseIdle = false; + } + this.lastScrollWasSynthetic = false; } }, 150, { trailing: true, }); + mouseIdleTimer = null; + mouseMovedRecently = false; + lastScrollWasSynthetic = false; + scrollToTopOnMouseIdle = false; + + setScrollTop = newScrollTop => { + if (this.node.scrollTop !== newScrollTop) { + this.lastScrollWasSynthetic = true; + this.node.scrollTop = newScrollTop; + } + }; + + 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); + + if (!this.mouseMovedRecently && this.node.scrollTop === 0) { + // Only set if we just started moving and are scrolled to the top. + this.scrollToTopOnMouseIdle = true; + } + // Save setting this flag for last, so we can do the comparison above. + this.mouseMovedRecently = true; + }, MOUSE_IDLE_DELAY / 2); + + handleWheel = throttle(() => { + this.scrollToTopOnMouseIdle = false; + }, 150, { + trailing: true, + }); + + handleMouseIdle = () => { + if (this.scrollToTopOnMouseIdle) { + this.setScrollTop(0); + } + this.mouseMovedRecently = false; + this.scrollToTopOnMouseIdle = false; + } + componentDidMount () { this.attachScrollListener(); this.attachIntersectionObserver(); @@ -81,16 +138,14 @@ export default class ScrollableList extends PureComponent { updateScrollBottom = (snapshot) => { const newScrollTop = this.node.scrollHeight - snapshot; - if (this.node.scrollTop !== newScrollTop) { - this.node.scrollTop = newScrollTop; - } + this.setScrollTop(newScrollTop); } getSnapshotBeforeUpdate (prevProps, prevState) { 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.mouseMovedRecently)) { return this.node.scrollHeight - this.node.scrollTop; } else { return null; @@ -104,6 +159,7 @@ export default class ScrollableList extends PureComponent { } componentWillUnmount () { + this.clearMouseIdleTimer(); this.detachScrollListener(); this.detachIntersectionObserver(); detachFullscreenListener(this.onFullScreenChange); @@ -126,10 +182,12 @@ export default class ScrollableList extends PureComponent { attachScrollListener () { this.node.addEventListener('scroll', this.handleScroll); + this.node.addEventListener('wheel', this.handleWheel); } detachScrollListener () { this.node.removeEventListener('scroll', this.handleScroll); + this.node.removeEventListener('wheel', this.handleWheel); } getFirstChildKey (props) { @@ -181,7 +239,7 @@ export default class ScrollableList extends PureComponent { ); } else if (isLoading || childrenCount > 0 || hasMore || !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} |