@ -8,6 +8,7 @@ import { throttle } from 'lodash';
import { List as ImmutableList } from 'immutable' ;
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' ;
import LoadingIndicator from './loading_indicator' ;
const MOUSE _IDLE _DELAY = 300 ;
const MOUSE _IDLE _DELAY = 300 ;
@ -25,6 +26,7 @@ export default class ScrollableList extends PureComponent {
trackScroll : PropTypes . bool ,
trackScroll : PropTypes . bool ,
shouldUpdateScroll : PropTypes . func ,
shouldUpdateScroll : PropTypes . func ,
isLoading : PropTypes . bool ,
isLoading : PropTypes . bool ,
showLoading : PropTypes . bool ,
hasMore : PropTypes . bool ,
hasMore : PropTypes . bool ,
prepend : PropTypes . node ,
prepend : PropTypes . node ,
alwaysPrepend : PropTypes . bool ,
alwaysPrepend : PropTypes . bool ,
@ -70,6 +72,7 @@ export default class ScrollableList extends PureComponent {
if ( this . mouseIdleTimer === null ) {
if ( this . mouseIdleTimer === null ) {
return ;
return ;
}
}
clearTimeout ( this . mouseIdleTimer ) ;
clearTimeout ( this . mouseIdleTimer ) ;
this . mouseIdleTimer = null ;
this . mouseIdleTimer = null ;
} ;
} ;
@ -77,13 +80,13 @@ export default class ScrollableList extends PureComponent {
handleMouseMove = throttle ( ( ) => {
handleMouseMove = throttle ( ( ) => {
// As long as the mouse keeps moving, clear and restart the idle timer.
// As long as the mouse keeps moving, clear and restart the idle timer.
this . clearMouseIdleTimer ( ) ;
this . clearMouseIdleTimer ( ) ;
this . mouseIdleTimer =
this . mouseIdleTimer = setTimeout ( this . handleMouseIdle , MOUSE _IDLE _DELAY ) ;
setTimeout ( this . handleMouseIdle , MOUSE _IDLE _DELAY ) ;
if ( ! this . mouseMovedRecently && this . node . scrollTop === 0 ) {
if ( ! this . mouseMovedRecently && this . node . scrollTop === 0 ) {
// Only set if we just started moving and are scrolled to the top.
// Only set if we just started moving and are scrolled to the top.
this . scrollToTopOnMouseIdle = true ;
this . scrollToTopOnMouseIdle = true ;
}
}
// Save setting this flag for last, so we can do the comparison above.
// Save setting this flag for last, so we can do the comparison above.
this . mouseMovedRecently = true ;
this . mouseMovedRecently = true ;
} , MOUSE _IDLE _DELAY / 2 ) ;
} , MOUSE _IDLE _DELAY / 2 ) ;
@ -98,6 +101,7 @@ export default class ScrollableList extends PureComponent {
if ( this . scrollToTopOnMouseIdle ) {
if ( this . scrollToTopOnMouseIdle ) {
this . node . scrollTop = 0 ;
this . node . scrollTop = 0 ;
}
}
this . mouseMovedRecently = false ;
this . mouseMovedRecently = false ;
this . scrollToTopOnMouseIdle = false ;
this . scrollToTopOnMouseIdle = false ;
}
}
@ -105,6 +109,7 @@ export default class ScrollableList extends PureComponent {
componentDidMount ( ) {
componentDidMount ( ) {
this . attachScrollListener ( ) ;
this . attachScrollListener ( ) ;
this . attachIntersectionObserver ( ) ;
this . attachIntersectionObserver ( ) ;
attachFullscreenListener ( this . onFullScreenChange ) ;
attachFullscreenListener ( this . onFullScreenChange ) ;
// Handle initial scroll posiiton
// Handle initial scroll posiiton
@ -115,6 +120,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 ) || this . mouseMovedRecently ) {
if ( ( someItemInserted && this . node . scrollTop > 0 ) || this . mouseMovedRecently ) {
return this . node . scrollHeight - this . node . scrollTop ;
return this . node . scrollHeight - this . node . scrollTop ;
} else {
} else {
@ -168,12 +174,14 @@ export default class ScrollableList extends PureComponent {
getFirstChildKey ( props ) {
getFirstChildKey ( props ) {
const { children } = props ;
const { children } = props ;
let firstChild = children ;
let firstChild = children ;
if ( children instanceof ImmutableList ) {
if ( children instanceof ImmutableList ) {
firstChild = children . get ( 0 ) ;
firstChild = children . get ( 0 ) ;
} else if ( Array . isArray ( children ) ) {
} else if ( Array . isArray ( children ) ) {
firstChild = children [ 0 ] ;
firstChild = children [ 0 ] ;
}
}
return firstChild && firstChild . key ;
return firstChild && firstChild . key ;
}
}
@ -181,20 +189,32 @@ export default class ScrollableList extends PureComponent {
this . node = c ;
this . node = c ;
}
}
handleLoadMore = ( e ) => {
handleLoadMore = e => {
e . preventDefault ( ) ;
e . preventDefault ( ) ;
this . props . onLoadMore ( ) ;
this . props . onLoadMore ( ) ;
}
}
render ( ) {
render ( ) {
const { children , scrollKey , trackScroll , shouldUpdateScroll , isLoading, hasMore , prepend , alwaysPrepend , alwaysShowScrollbar , emptyMessage , onLoadMore } = this . props ;
const { children , scrollKey , trackScroll , shouldUpdateScroll , showLoading, isLoading, hasMore , prepend , alwaysPrepend , alwaysShowScrollbar , emptyMessage , onLoadMore } = this . props ;
const { fullscreen } = this . state ;
const { fullscreen } = this . state ;
const childrenCount = React . Children . count ( children ) ;
const childrenCount = React . Children . count ( children ) ;
const loadMore = ( hasMore && childrenCount > 0 && onLoadMore ) ? < LoadMore visible = { ! isLoading } onClick = { this . handleLoadMore } / > : null ;
const loadMore = ( hasMore && childrenCount > 0 && onLoadMore ) ? < LoadMore visible = { ! isLoading } onClick = { this . handleLoadMore } / > : null ;
let scrollableArea = null ;
let scrollableArea = null ;
if ( isLoading || childrenCount > 0 || ! emptyMessage ) {
if ( showLoading ) {
scrollableArea = (
< div className = 'scrollable scrollable--flex' ref = { this . setRef } >
< div role = 'feed' className = 'item-list' >
{ prepend }
< / d i v >
< div className = 'scrollable__append' >
< LoadingIndicator / >
< / d i v >
< / d i v >
) ;
} else if ( isLoading || childrenCount > 0 || ! emptyMessage ) {
scrollableArea = (
scrollableArea = (
< div className = { classNames ( 'scrollable' , { fullscreen } ) } ref = { this . setRef } onMouseMove = { this . handleMouseMove } >
< div className = { classNames ( 'scrollable' , { fullscreen } ) } ref = { this . setRef } onMouseMove = { this . handleMouseMove } >
< div role = 'feed' className = 'item-list' >
< div role = 'feed' className = 'item-list' >