From ce68314de96645a0e4b0c98a2579d33855bdf2be Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Thu, 9 Jul 2020 14:43:20 +0000
Subject: [PATCH 01/12] Revert "Merge pull request #4932 from
 matrix-org/travis/room-list/invisible-show-more"

This reverts commit f58a0a753897b00b7a8ae5647c6345bb88057aa4.
---
 res/css/views/rooms/_RoomSublist2.scss      | 20 +++++++++++++++-----
 src/components/views/rooms/RoomSublist2.tsx | 10 ++--------
 src/stores/room-list/ListLayout.ts          |  4 ++++
 3 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss
index 83e7e68563..1d13f25b8f 100644
--- a/res/css/views/rooms/_RoomSublist2.scss
+++ b/res/css/views/rooms/_RoomSublist2.scss
@@ -187,16 +187,16 @@ limitations under the License.
         flex-direction: column;
         overflow: hidden;
 
-        .mx_RoomSublist2_placeholder {
-            height: 44px; // Height of a room tile plus margins
-        }
-
         .mx_RoomSublist2_showNButton {
             cursor: pointer;
             font-size: $font-13px;
             line-height: $font-18px;
             color: $roomtile2-preview-color;
 
+            // This is the same color as the left panel background because it needs
+            // to occlude the lastmost tile in the list.
+            background-color: $roomlist2-bg-color;
+
             // Update the render() function for RoomSublist2 if these change
             // Update the ListLayout class for minVisibleTiles if these change.
             //
@@ -209,7 +209,7 @@ limitations under the License.
             // We force this to the bottom so it will overlap rooms as needed.
             // We account for the space it takes up (24px) in the code through padding.
             position: absolute;
-            bottom: 0;
+            bottom: 0; // the height of the resize handle
             left: 0;
             right: 0;
 
@@ -236,6 +236,16 @@ limitations under the License.
             .mx_RoomSublist2_showLessButtonChevron {
                 mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
             }
+
+            &.mx_RoomSublist2_isCutting::before {
+                content: '';
+                position: absolute;
+                top: 0;
+                left: 0;
+                right: 0;
+                height: 4px;
+                box-shadow: 0px -2px 3px rgba(46, 47, 50, 0.08);
+            }
         }
 
         // Class name comes from the ResizableBox component
diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index c3ac85e2de..73aa97b6e8 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -562,8 +562,10 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         if (visibleTiles.length > 0) {
             const layout = this.layout; // to shorten calls
 
+            const maxTilesFactored = layout.tilesWithResizerBoxFactor(this.numTiles);
             const showMoreBtnClasses = classNames({
                 'mx_RoomSublist2_showNButton': true,
+                'mx_RoomSublist2_isCutting': this.state.isResizing && layout.visibleTiles < maxTilesFactored,
             });
 
             // If we're hiding rooms, show a 'show more' button to the user. This button
@@ -642,14 +644,6 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             const tilesWithoutPadding = Math.min(relativeTiles, layout.visibleTiles);
             const tilesPx = layout.calculateTilesToPixelsMin(relativeTiles, tilesWithoutPadding, padding);
 
-            // Now that we know our padding constraints, let's find out if we need to chop off the
-            // last rendered visible tile so it doesn't collide with the 'show more' button
-            let visibleUnpaddedTiles = Math.round(layout.visibleTiles - layout.pixelsToTiles(padding));
-            if (visibleUnpaddedTiles === visibleTiles.length - 1) {
-                const placeholder = <div className="mx_RoomSublist2_placeholder" key='placeholder' />;
-                visibleTiles.splice(visibleUnpaddedTiles, 1, placeholder);
-            }
-
             const dimensions = {
                 height: tilesPx,
             };
diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts
index 5169c5e4e5..99674fe74f 100644
--- a/src/stores/room-list/ListLayout.ts
+++ b/src/stores/room-list/ListLayout.ts
@@ -109,6 +109,10 @@ export class ListLayout {
         return this.tilesToPixels(Math.min(maxTiles, n)) + padding;
     }
 
+    public tilesWithResizerBoxFactor(n: number): number {
+        return n + RESIZER_BOX_FACTOR;
+    }
+
     public tilesWithPadding(n: number, paddingPx: number): number {
         return this.pixelsToTiles(this.tilesToPixelsWithPadding(n, paddingPx));
     }

From 859f65659c03d2b930f62d142651cd5116c21dd0 Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Thu, 9 Jul 2020 13:07:13 -0600
Subject: [PATCH 02/12] Attempt to support a hard cutoff with the show more
 button

Known issues:
* Causes scroll jumps when the button gets added to DOM
* Resize handle is invisible when there's a show more button

TODO:
* Clean up comments
* Clean up useless code (all the padding stuff isn't needed)
---
 res/css/views/rooms/_RoomSublist2.scss      | 120 ++++++++++----------
 src/components/views/rooms/RoomSublist2.tsx |  41 ++++---
 src/stores/room-list/ListLayout.ts          |   3 +-
 3 files changed, 84 insertions(+), 80 deletions(-)

diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss
index 1d13f25b8f..6a77056917 100644
--- a/res/css/views/rooms/_RoomSublist2.scss
+++ b/res/css/views/rooms/_RoomSublist2.scss
@@ -179,7 +179,6 @@ limitations under the License.
     }
 
     .mx_RoomSublist2_resizeBox {
-        margin-bottom: 4px; // for the resize handle
         position: relative;
 
         // Create another flexbox column for the tiles
@@ -187,65 +186,12 @@ limitations under the License.
         flex-direction: column;
         overflow: hidden;
 
-        .mx_RoomSublist2_showNButton {
-            cursor: pointer;
-            font-size: $font-13px;
-            line-height: $font-18px;
-            color: $roomtile2-preview-color;
-
-            // This is the same color as the left panel background because it needs
-            // to occlude the lastmost tile in the list.
-            background-color: $roomlist2-bg-color;
-
-            // Update the render() function for RoomSublist2 if these change
-            // Update the ListLayout class for minVisibleTiles if these change.
-            //
-            // At 24px high, 8px padding on the top and 4px padding on the bottom  this equates to 0.73 of
-            // a tile due to how the padding calculations work.
-            height: 24px;
-            padding-top: 8px;
-            padding-bottom: 4px;
-
-            // We force this to the bottom so it will overlap rooms as needed.
-            // We account for the space it takes up (24px) in the code through padding.
+        .mx_RoomSublist2_resizerHandles_showNButton {
             position: absolute;
-            bottom: 0; // the height of the resize handle
+            bottom: -32px; // height of the button
             left: 0;
             right: 0;
-
-            // We create a flexbox to cheat at alignment
-            display: flex;
-            align-items: center;
-
-            .mx_RoomSublist2_showNButtonChevron {
-                position: relative;
-                width: 16px;
-                height: 16px;
-                margin-left: 12px;
-                margin-right: 18px;
-                mask-position: center;
-                mask-size: contain;
-                mask-repeat: no-repeat;
-                background: $roomtile2-preview-color;
-            }
-
-            .mx_RoomSublist2_showMoreButtonChevron {
-                mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
-            }
-
-            .mx_RoomSublist2_showLessButtonChevron {
-                mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
-            }
-
-            &.mx_RoomSublist2_isCutting::before {
-                content: '';
-                position: absolute;
-                top: 0;
-                left: 0;
-                right: 0;
-                height: 4px;
-                box-shadow: 0px -2px 3px rgba(46, 47, 50, 0.08);
-            }
+            height: 4px; // height of the handle
         }
 
         // Class name comes from the ResizableBox component
@@ -277,6 +223,56 @@ limitations under the License.
         }
     }
 
+    .mx_RoomSublist2_showNButton {
+        cursor: pointer;
+        font-size: $font-13px;
+        line-height: $font-18px;
+        color: $roomtile2-preview-color;
+
+        // Update the render() function for RoomSublist2 if these change
+        // Update the ListLayout class for minVisibleTiles if these change.
+        //
+        // At 24px high, 8px padding on the top and 4px padding on the bottom  this equates to 0.73 of
+        // a tile due to how the padding calculations work.
+        height: 24px;
+        padding-top: 8px;
+        padding-bottom: 4px;
+
+        // We create a flexbox to cheat at alignment
+        display: flex;
+        align-items: center;
+
+        .mx_RoomSublist2_showNButtonChevron {
+            position: relative;
+            width: 16px;
+            height: 16px;
+            margin-left: 12px;
+            margin-right: 18px;
+            mask-position: center;
+            mask-size: contain;
+            mask-repeat: no-repeat;
+            background: $roomtile2-preview-color;
+        }
+
+        .mx_RoomSublist2_showMoreButtonChevron {
+            mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+        }
+
+        .mx_RoomSublist2_showLessButtonChevron {
+            mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
+        }
+
+        &.mx_RoomSublist2_isCutting::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            height: 4px;
+            box-shadow: 0px -2px 3px rgba(46, 47, 50, 0.08);
+        }
+    }
+
     &.mx_RoomSublist2_hasMenuOpen,
     &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:focus-within,
     &:not(.mx_RoomSublist2_minimized) > .mx_RoomSublist2_headerContainer:hover {
@@ -322,13 +318,13 @@ limitations under the License.
 
         .mx_RoomSublist2_resizeBox {
             align-items: center;
+        }
 
-            .mx_RoomSublist2_showNButton {
-                flex-direction: column;
+        .mx_RoomSublist2_showNButton {
+            flex-direction: column;
 
-                .mx_RoomSublist2_showNButtonChevron {
-                    margin-right: 12px; // to center
-                }
+            .mx_RoomSublist2_showNButtonChevron {
+                margin-right: 12px; // to center
             }
         }
 
diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 73aa97b6e8..caa679f1d0 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -119,7 +119,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
     }
 
     private get numVisibleTiles(): number {
-        const nVisible = Math.floor(this.layout.visibleTiles);
+        const nVisible = Math.ceil(this.layout.visibleTiles);
         return Math.min(nVisible, this.numTiles);
     }
 
@@ -635,8 +635,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
 
             // The padding is variable though, so figure out what we need padding for.
             let padding = 0;
-            if (showNButton) padding += SHOW_N_BUTTON_HEIGHT;
-            padding += RESIZE_HANDLE_HEIGHT; // always append the handle height
+            //if (showNButton) padding += SHOW_N_BUTTON_HEIGHT;
+            //padding += RESIZE_HANDLE_HEIGHT; // always append the handle height
 
             const relativeTiles = layout.tilesWithPadding(this.numTiles, padding);
             const minTilesPx = layout.calculateTilesToPixelsMin(relativeTiles, layout.minVisibleTiles, padding);
@@ -644,25 +644,32 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             const tilesWithoutPadding = Math.min(relativeTiles, layout.visibleTiles);
             const tilesPx = layout.calculateTilesToPixelsMin(relativeTiles, tilesWithoutPadding, padding);
 
+            const handleWrapperClasses = classNames({
+                'mx_RoomSublist2_resizerHandles': true,
+                'mx_RoomSublist2_resizerHandles_showNButton': !!showNButton,
+            });
+
             const dimensions = {
                 height: tilesPx,
             };
             content = (
-                <Resizable
-                    size={dimensions as any}
-                    minHeight={minTilesPx}
-                    maxHeight={maxTilesPx}
-                    onResizeStart={this.onResizeStart}
-                    onResizeStop={this.onResizeStop}
-                    onResize={this.onResize}
-                    handleWrapperClass="mx_RoomSublist2_resizerHandles"
-                    handleClasses={{bottom: "mx_RoomSublist2_resizerHandle"}}
-                    className="mx_RoomSublist2_resizeBox"
-                    enable={handles}
-                >
-                    {visibleTiles}
+                <React.Fragment>
+                    <Resizable
+                        size={dimensions as any}
+                        minHeight={minTilesPx}
+                        maxHeight={maxTilesPx}
+                        onResizeStart={this.onResizeStart}
+                        onResizeStop={this.onResizeStop}
+                        onResize={this.onResize}
+                        handleWrapperClass={handleWrapperClasses}
+                        handleClasses={{bottom: "mx_RoomSublist2_resizerHandle"}}
+                        className="mx_RoomSublist2_resizeBox"
+                        enable={handles}
+                    >
+                        {visibleTiles}
+                    </Resizable>
                     {showNButton}
-                </Resizable>
+                </React.Fragment>
             );
         }
 
diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts
index 99674fe74f..f1900487bc 100644
--- a/src/stores/room-list/ListLayout.ts
+++ b/src/stores/room-list/ListLayout.ts
@@ -20,7 +20,8 @@ const TILE_HEIGHT_PX = 44;
 
 // this comes from the CSS where the show more button is
 // mathematically this percent of a tile when floating.
-const RESIZER_BOX_FACTOR = 0.78;
+//const RESIZER_BOX_FACTOR = 0.78;
+const RESIZER_BOX_FACTOR = 0;
 
 interface ISerializedListLayout {
     numTiles: number;

From d5a3071518a095f19b82d3fb784b6872372ae063 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:29:39 +0200
Subject: [PATCH 03/12] put show more button inside resizer

this way we have a flexbox layout in the resizer with:
 - the resize handle (fixed)
 - the show more/less button, if any (fixed)
 - the list of tiles (grabbing whatever is left)
---
 res/css/views/rooms/_RoomSublist2.scss      | 20 +++++++++++++++-----
 src/components/views/rooms/RoomSublist2.tsx |  6 ++++--
 2 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss
index 6a77056917..bd00a7fc72 100644
--- a/res/css/views/rooms/_RoomSublist2.scss
+++ b/res/css/views/rooms/_RoomSublist2.scss
@@ -186,12 +186,22 @@ limitations under the License.
         flex-direction: column;
         overflow: hidden;
 
+        .mx_RoomSublist2_tiles {
+            flex: 1 0 0;
+            overflow: hidden;
+            // need this to be flex otherwise the overflow hidden from above
+            // sometimes vertically centers the clipped list ... no idea why it would do this
+            // as the box model should be top aligned. Happens in both FF and Chromium
+            display: flex;
+            flex-direction: column;
+        }
+
         .mx_RoomSublist2_resizerHandles_showNButton {
-            position: absolute;
-            bottom: -32px; // height of the button
-            left: 0;
-            right: 0;
-            height: 4px; // height of the handle
+            flex: 0 0 32px;
+        }
+
+        .mx_RoomSublist2_resizerHandles {
+            flex: 0 0 4px;
         }
 
         // Class name comes from the ResizableBox component
diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index caa679f1d0..70d65f2437 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -666,9 +666,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
                         className="mx_RoomSublist2_resizeBox"
                         enable={handles}
                     >
-                        {visibleTiles}
+                        <div className="mx_RoomSublist2_tiles">
+                            {visibleTiles}
+                        </div>
+                        {showNButton}
                     </Resizable>
-                    {showNButton}
                 </React.Fragment>
             );
         }

From 725b7f895015c5af42b6d4535f46e6928f9baca1 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:30:52 +0200
Subject: [PATCH 04/12] make show more button a bit less tall

---
 res/css/views/rooms/_RoomSublist2.scss      | 4 ----
 src/components/views/rooms/RoomSublist2.tsx | 2 +-
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss
index bd00a7fc72..3744641390 100644
--- a/res/css/views/rooms/_RoomSublist2.scss
+++ b/res/css/views/rooms/_RoomSublist2.scss
@@ -241,11 +241,7 @@ limitations under the License.
 
         // Update the render() function for RoomSublist2 if these change
         // Update the ListLayout class for minVisibleTiles if these change.
-        //
-        // At 24px high, 8px padding on the top and 4px padding on the bottom  this equates to 0.73 of
-        // a tile due to how the padding calculations work.
         height: 24px;
-        padding-top: 8px;
         padding-bottom: 4px;
 
         // We create a flexbox to cheat at alignment
diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 70d65f2437..66b5a4aad1 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -59,7 +59,7 @@ import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
  * warning disappears.                                             *
  *******************************************************************/
 
-const SHOW_N_BUTTON_HEIGHT = 32; // As defined by CSS
+const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
 const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
 export const HEADER_HEIGHT = 32; // As defined by CSS
 

From 49f7170d9591068bff30203bae82e8e52237bded Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:31:53 +0200
Subject: [PATCH 05/12] extract type

---
 src/components/views/rooms/RoomSublist2.tsx | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 66b5a4aad1..c219b87c92 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -86,6 +86,12 @@ interface IProps {
     // TODO: Account for https://github.com/vector-im/riot-web/issues/14179
 }
 
+// TODO: Use re-resizer's NumberSize when it is exposed as the type
+interface ResizeDelta {
+    width: number,
+    height: number,
+}
+
 type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;
 
 interface IState {
@@ -161,7 +167,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         e: MouseEvent | TouchEvent,
         travelDirection: Direction,
         refToElement: HTMLDivElement,
-        delta: { width: number, height: number }, // TODO: Use re-resizer's NumberSize when it is exposed as the type
+        delta: ResizeDelta,
     ) => {
         // Do some sanity checks, but in reality we shouldn't need these.
         if (travelDirection !== "bottom") return;

From 652fb9e6130323553c5fa5aa83646ed7bea589e5 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:35:07 +0200
Subject: [PATCH 06/12] track height in pixels in component state

---
 src/components/views/rooms/RoomSublist2.tsx | 47 +++++++++++++--------
 1 file changed, 29 insertions(+), 18 deletions(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index c219b87c92..13690bd1ca 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -111,15 +111,36 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
 
         this.layout = RoomListLayoutStore.instance.getLayoutFor(this.props.tagId);
 
+        const height = this.calculateInitialHeight();
         this.state = {
             notificationState: RoomNotificationStateStore.instance.getListState(this.props.tagId),
             contextMenuPosition: null,
             isResizing: false,
+            height,
         };
         this.state.notificationState.setRooms(this.props.rooms);
         this.dispatcherRef = defaultDispatcher.register(this.onAction);
     }
 
+    private calculateInitialHeight() {
+        const requestedVisibleTiles = Math.max(Math.floor(this.layout.visibleTiles), this.layout.minVisibleTiles);
+        const tileCount = Math.min(this.numTiles, requestedVisibleTiles);
+        const height = this.layout.tilesToPixelsWithPadding(tileCount, this.padding);
+        return height;
+    }
+
+    private get padding() {
+        let padding = RESIZE_HANDLE_HEIGHT;
+        // this is used for calculating the max height of the whole container,
+        // and takes into account whether there should be room reserved for the show less button
+        // when fully expanded. Note that the show more button might still be shown when not fully expanded,
+        // but in this case it will take the space of a tile and we don't need to reserve space for it.
+        if (this.numTiles > this.layout.defaultVisibleTiles) {
+            padding += SHOW_N_BUTTON_HEIGHT;
+        }
+        return padding;
+    }
+
     private get numTiles(): number {
         return (this.props.rooms || []).length + (this.props.extraBadTilesThatShouldntExist || []).length;
     }
@@ -568,7 +589,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         if (visibleTiles.length > 0) {
             const layout = this.layout; // to shorten calls
 
-            const maxTilesFactored = layout.tilesWithResizerBoxFactor(this.numTiles);
+            const minTiles = Math.min(layout.minVisibleTiles, this.numTiles);
+            const showMoreAtMinHeight = minTiles < this.numTiles;
+            const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0);
+            const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding);
+            const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
             const showMoreBtnClasses = classNames({
                 'mx_RoomSublist2_showNButton': true,
                 'mx_RoomSublist2_isCutting': this.state.isResizing && layout.visibleTiles < maxTilesFactored,
@@ -578,9 +603,9 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             // floats above the resize handle, if we have one present. If the user has all
             // tiles visible, it becomes 'show less'.
             let showNButton = null;
-            if (this.numTiles > visibleTiles.length) {
                 // we have a cutoff condition - add the button to show all
                 const numMissing = this.numTiles - visibleTiles.length;
+            if (maxTilesPx > this.state.height) {
                 let showMoreText = (
                     <span className='mx_RoomSublist2_showNButtonText'>
                         {_t("Show %(count)s more", {count: numMissing})}
@@ -595,7 +620,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
                         {showMoreText}
                     </RovingAccessibleButton>
                 );
-            } else if (this.numTiles <= visibleTiles.length && this.numTiles > this.layout.defaultVisibleTiles) {
+            } else if (this.numTiles > this.layout.defaultVisibleTiles) {
                 // we have all tiles visible - add a button to show less
                 let showLessText = (
                     <span className='mx_RoomSublist2_showNButtonText'>
@@ -639,29 +664,15 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             // goes backwards and can become wildly incorrect (visibleTiles says 18 when there's
             // only mathematically 7 possible).
 
-            // The padding is variable though, so figure out what we need padding for.
-            let padding = 0;
-            //if (showNButton) padding += SHOW_N_BUTTON_HEIGHT;
-            //padding += RESIZE_HANDLE_HEIGHT; // always append the handle height
-
-            const relativeTiles = layout.tilesWithPadding(this.numTiles, padding);
-            const minTilesPx = layout.calculateTilesToPixelsMin(relativeTiles, layout.minVisibleTiles, padding);
-            const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, padding);
-            const tilesWithoutPadding = Math.min(relativeTiles, layout.visibleTiles);
-            const tilesPx = layout.calculateTilesToPixelsMin(relativeTiles, tilesWithoutPadding, padding);
-
             const handleWrapperClasses = classNames({
                 'mx_RoomSublist2_resizerHandles': true,
                 'mx_RoomSublist2_resizerHandles_showNButton': !!showNButton,
             });
 
-            const dimensions = {
-                height: tilesPx,
-            };
             content = (
                 <React.Fragment>
                     <Resizable
-                        size={dimensions as any}
+                        size={{height: this.state.height}}
                         minHeight={minTilesPx}
                         maxHeight={maxTilesPx}
                         onResizeStart={this.onResizeStart}

From 96f296885403f73a6db5360fd1ea105a430463ba Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:36:33 +0200
Subject: [PATCH 07/12] make all height changes update component state

also set visibleTiles as side-effect
---
 src/components/views/rooms/RoomSublist2.tsx | 46 ++++++++++-----------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 13690bd1ca..713b70ce94 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -110,7 +110,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         super(props);
 
         this.layout = RoomListLayoutStore.instance.getLayoutFor(this.props.tagId);
-
+        this.heightAtStart = 0;
         const height = this.calculateInitialHeight();
         this.state = {
             notificationState: RoomNotificationStateStore.instance.getListState(this.props.tagId),
@@ -184,47 +184,45 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         if (this.props.onAddRoom) this.props.onAddRoom();
     };
 
+    private applyHeightChange(newHeight: number) {
+        const heightInTiles = Math.ceil(this.layout.pixelsToTiles(newHeight - this.padding));
+        this.layout.visibleTiles = Math.min(this.numTiles, heightInTiles);
+    }
+
     private onResize = (
         e: MouseEvent | TouchEvent,
         travelDirection: Direction,
         refToElement: HTMLDivElement,
         delta: ResizeDelta,
     ) => {
-        // Do some sanity checks, but in reality we shouldn't need these.
-        if (travelDirection !== "bottom") return;
-        if (delta.height === 0) return; // something went wrong, so just ignore it.
-
-        // NOTE: the movement in the MouseEvent (not present on a TouchEvent) is inaccurate
-        // for our purposes. The delta provided by the library is also a change *from when
-        // resizing started*, meaning it is fairly useless for us. This is why we just use
-        // the client height and run with it.
-
-        const heightBefore = this.layout.visibleTiles;
-        const heightInTiles = this.layout.pixelsToTiles(refToElement.clientHeight);
-        this.layout.setVisibleTilesWithin(heightInTiles, this.numTiles);
-        if (heightBefore === this.layout.visibleTiles) return; // no-op
-        this.forceUpdate(); // because the layout doesn't trigger a re-render
+        const newHeight = this.heightAtStart + delta.height;
+        this.applyHeightChange(newHeight);
+        this.setState({height: newHeight});
     };
 
     private onResizeStart = () => {
+        this.heightAtStart = this.state.height;
         this.setState({isResizing: true});
     };
 
-    private onResizeStop = () => {
-        this.setState({isResizing: false});
+    private onResizeStop = (e, direction, ref, d) => {
+        const newHeight = this.heightAtStart + d.height;
+        this.applyHeightChange(newHeight);
+        this.setState({isResizing: false, height: newHeight});
     };
 
     private onShowAllClick = () => {
-        const numVisibleTiles = this.numVisibleTiles;
-        this.layout.visibleTiles = this.layout.tilesWithPadding(this.numTiles, MAX_PADDING_HEIGHT);
-        this.forceUpdate(); // because the layout doesn't trigger a re-render
-        setImmediate(this.focusRoomTile, numVisibleTiles); // focus the tile after the current bottom one
+        const newHeight = this.layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
+        this.applyHeightChange(newHeight);
+        this.setState({height: newHeight}, () => {
+            this.focusRoomTile(this.numTiles - 1);
+        });
     };
 
     private onShowLessClick = () => {
-        this.layout.visibleTiles = this.layout.defaultVisibleTiles;
-        this.forceUpdate(); // because the layout doesn't trigger a re-render
-        // focus will flow to the show more button here
+        const newHeight = this.layout.tilesToPixelsWithPadding(this.layout.defaultVisibleTiles, this.padding);
+        this.applyHeightChange(newHeight);
+        this.setState({height: newHeight});
     };
 
     private focusRoomTile = (index: number) => {

From 86817430c51cbad1234c200c61ff8de56f8ab415 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:37:58 +0200
Subject: [PATCH 08/12] update initially shown amount of tiles on component
 update

as rooms aren't all available at ctor time
---
 src/components/views/rooms/RoomSublist2.tsx | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 713b70ce94..6acb5f929c 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -142,7 +142,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
     }
 
     private get numTiles(): number {
-        return (this.props.rooms || []).length + (this.props.extraBadTilesThatShouldntExist || []).length;
+        return RoomSublist2.calcNumTiles(this.props);
+    }
+
+    private static calcNumTiles(props) {
+        return (props.rooms || []).length + (props.extraBadTilesThatShouldntExist || []).length;
     }
 
     private get numVisibleTiles(): number {
@@ -150,8 +154,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         return Math.min(nVisible, this.numTiles);
     }
 
-    public componentDidUpdate() {
+    public componentDidUpdate(prevProps) {
         this.state.notificationState.setRooms(this.props.rooms);
+        // as the rooms can come in one by one we need to reevaluate
+        // the amount of available rooms to cap the amount of requested visible rooms by the layout
+        if (RoomSublist2.calcNumTiles(prevProps) !== this.numTiles) {
+            this.setState({height: this.calculateInitialHeight()});
+        }
     }
 
     public componentWillUnmount() {

From e2aa6ecf6b6dd9a9f0290fef092d1de8cc2afee7 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:38:32 +0200
Subject: [PATCH 09/12] fix show X more counter

---
 src/components/views/rooms/RoomSublist2.tsx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 6acb5f929c..1a1a45b7cf 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -610,9 +610,11 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             // floats above the resize handle, if we have one present. If the user has all
             // tiles visible, it becomes 'show less'.
             let showNButton = null;
-                // we have a cutoff condition - add the button to show all
-                const numMissing = this.numTiles - visibleTiles.length;
+
             if (maxTilesPx > this.state.height) {
+                const nonPaddedHeight = this.state.height - RESIZE_HANDLE_HEIGHT - SHOW_N_BUTTON_HEIGHT;
+                const amountFullyShown = Math.floor(nonPaddedHeight / this.layout.tileHeight);
+                const numMissing = this.numTiles - amountFullyShown;
                 let showMoreText = (
                     <span className='mx_RoomSublist2_showNButtonText'>
                         {_t("Show %(count)s more", {count: numMissing})}

From 85ac256231e75dcef0d3f82d463768e90c39992a Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:38:53 +0200
Subject: [PATCH 10/12] cleanup

---
 res/css/views/rooms/_RoomSublist2.scss      | 10 ----------
 src/components/views/rooms/RoomSublist2.tsx |  2 --
 2 files changed, 12 deletions(-)

diff --git a/res/css/views/rooms/_RoomSublist2.scss b/res/css/views/rooms/_RoomSublist2.scss
index 3744641390..73dc7d58b8 100644
--- a/res/css/views/rooms/_RoomSublist2.scss
+++ b/res/css/views/rooms/_RoomSublist2.scss
@@ -267,16 +267,6 @@ limitations under the License.
         .mx_RoomSublist2_showLessButtonChevron {
             mask-image: url('$(res)/img/feather-customised/chevron-up.svg');
         }
-
-        &.mx_RoomSublist2_isCutting::before {
-            content: '';
-            position: absolute;
-            top: 0;
-            left: 0;
-            right: 0;
-            height: 4px;
-            box-shadow: 0px -2px 3px rgba(46, 47, 50, 0.08);
-        }
     }
 
     &.mx_RoomSublist2_hasMenuOpen,
diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 1a1a45b7cf..5a7bfa3990 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -585,7 +585,6 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         // TODO: Error boundary: https://github.com/vector-im/riot-web/issues/14185
 
         const visibleTiles = this.renderVisibleTiles();
-
         const classes = classNames({
             'mx_RoomSublist2': true,
             'mx_RoomSublist2_hasMenuOpen': !!this.state.contextMenuPosition,
@@ -603,7 +602,6 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
             const showMoreBtnClasses = classNames({
                 'mx_RoomSublist2_showNButton': true,
-                'mx_RoomSublist2_isCutting': this.state.isResizing && layout.visibleTiles < maxTilesFactored,
             });
 
             // If we're hiding rooms, show a 'show more' button to the user. This button

From 15ea3a528796d6e50271095d8754b7f6fcb25bc2 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:42:51 +0200
Subject: [PATCH 11/12] add types

---
 src/components/views/rooms/RoomSublist2.tsx | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index 5a7bfa3990..a059d146e8 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -214,8 +214,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
         this.setState({isResizing: true});
     };
 
-    private onResizeStop = (e, direction, ref, d) => {
-        const newHeight = this.heightAtStart + d.height;
+    private onResizeStop = (
+        e: MouseEvent | TouchEvent,
+        travelDirection: Direction,
+        refToElement: HTMLDivElement,
+        delta: ResizeDelta,
+    ) => {
+        const newHeight = this.heightAtStart + delta.height;
         this.applyHeightChange(newHeight);
         this.setState({isResizing: false, height: newHeight});
     };

From ae8d6f5523a3e65548175e6d10410ddce0b67006 Mon Sep 17 00:00:00 2001
From: Bruno Windels <brunow@matrix.org>
Date: Fri, 10 Jul 2020 18:48:54 +0200
Subject: [PATCH 12/12] make tsc happy

---
 src/components/views/rooms/RoomSublist2.tsx | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx
index a059d146e8..73d53ccae7 100644
--- a/src/components/views/rooms/RoomSublist2.tsx
+++ b/src/components/views/rooms/RoomSublist2.tsx
@@ -88,8 +88,8 @@ interface IProps {
 
 // TODO: Use re-resizer's NumberSize when it is exposed as the type
 interface ResizeDelta {
-    width: number,
-    height: number,
+    width: number;
+    height: number;
 }
 
 type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;
@@ -98,6 +98,7 @@ interface IState {
     notificationState: ListNotificationState;
     contextMenuPosition: PartialDOMRect;
     isResizing: boolean;
+    height: number;
 }
 
 export default class RoomSublist2 extends React.Component<IProps, IState> {
@@ -105,6 +106,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
     private sublistRef = createRef<HTMLDivElement>();
     private dispatcherRef: string;
     private layout: ListLayout;
+    private heightAtStart: number;
 
     constructor(props: IProps) {
         super(props);
@@ -684,7 +686,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
             content = (
                 <React.Fragment>
                     <Resizable
-                        size={{height: this.state.height}}
+                        size={{height: this.state.height} as any}
                         minHeight={minTilesPx}
                         maxHeight={maxTilesPx}
                         onResizeStart={this.onResizeStart}