diff --git a/src/components/structures/ScrollPanel.tsx b/src/components/structures/ScrollPanel.tsx index a246a1a9119..598f2488e8b 100644 --- a/src/components/structures/ScrollPanel.tsx +++ b/src/components/structures/ScrollPanel.tsx @@ -28,12 +28,11 @@ const DEBUG_SCROLL = false; // The amount of extra scroll distance to allow prior to unfilling. // See getExcessHeight. const UNPAGINATION_PADDING = 6000; -// The number of milliseconds to debounce calls to onUnfillRequest, to prevent -// many scroll events causing many unfilling requests. +// The number of milliseconds to debounce calls to onUnfillRequest, +// to prevent many scroll events causing many unfilling requests. const UNFILL_REQUEST_DEBOUNCE_MS = 200; -// _updateHeight makes the height a ceiled multiple of this so we -// don't have to update the height too often. It also allows the user -// to scroll past the pagination spinner a bit so they don't feel blocked so +// updateHeight makes the height a ceiled multiple of this so we don't have to update the height too often. +// It also allows the user to scroll past the pagination spinner a bit so they don't feel blocked so // much while the content loads. const PAGE_SIZE = 400; @@ -192,16 +191,14 @@ export default class ScrollPanel extends React.Component { private preventShrinkingState: IPreventShrinkingState; private unfillDebouncer: number; private bottomGrowth: number; - private pages: number; + private minListHeight: number; private heightUpdateInProgress: boolean; private divScroll: HTMLDivElement; constructor(props, context) { super(props, context); - if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); - } + this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize); this.resetScrollState(); } @@ -227,9 +224,7 @@ export default class ScrollPanel extends React.Component { // (We could use isMounted(), but facebook have deprecated that.) this.unmounted = true; - if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize); - } + this.props.resizeNotifier?.removeListener("middlePanelResizedNoisy", this.onResize); } private onScroll = ev => { @@ -541,7 +536,7 @@ export default class ScrollPanel extends React.Component { stuckAtBottom: this.props.startAtBottom, }; this.bottomGrowth = 0; - this.pages = 0; + this.minListHeight = 0; this.scrollTimeout = new Timer(100); this.heightUpdateInProgress = false; }; @@ -734,9 +729,13 @@ export default class ScrollPanel extends React.Component { const sn = this.getScrollNode(); const itemlist = this.itemlist.current; const contentHeight = this.getMessagesHeight(); - const minHeight = sn.clientHeight; - const height = Math.max(minHeight, contentHeight); - this.pages = Math.ceil(height / PAGE_SIZE); + // Only round to the nearest page when we're basing the height off the content, not off the scrollNode height + // otherwise it'll cause too much overscroll which makes it possible to entirely scroll content off-screen. + if (contentHeight < sn.clientHeight - PAGE_SIZE) { + this.minListHeight = sn.clientHeight; + } else { + this.minListHeight = Math.ceil(contentHeight / PAGE_SIZE) * PAGE_SIZE; + } this.bottomGrowth = 0; const newHeight = `${this.getListHeight()}px`; @@ -805,7 +804,7 @@ export default class ScrollPanel extends React.Component { } private getListHeight(): number { - return this.bottomGrowth + (this.pages * PAGE_SIZE); + return this.bottomGrowth + this.minListHeight; } private getMessagesHeight(): number {