diff --git a/src/vaadin-grid-scroller.html b/src/vaadin-grid-scroller.html
index d07b413a3..1973672f1 100644
--- a/src/vaadin-grid-scroller.html
+++ b/src/vaadin-grid-scroller.html
@@ -280,6 +280,27 @@
_scrollHandler() {
const delta = this.$.table.scrollTop - this._scrollPosition;
this._accessIronListAPI(super._scrollHandler);
+
+ if (this._physicalCount !== 0) {
+ const isScrollingDown = delta >= 0;
+ const reusables = this._getReusables(!isScrollingDown);
+
+ if (reusables.indexes.length) {
+ // After running super._scrollHandler, fix internal properties to workaround an iron-list issue.
+ // See https://github.com/vaadin/web-components/issues/1691
+ this._physicalTop = reusables.physicalTop;
+
+ if (isScrollingDown) {
+ this._virtualStart -= reusables.indexes.length;
+ this._physicalStart -= reusables.indexes.length;
+ } else {
+ this._virtualStart += reusables.indexes.length;
+ this._physicalStart += reusables.indexes.length;
+ }
+ this._update(reusables.indexes, !isScrollingDown);
+ }
+ }
+
const oldOffset = this._vidxOffset;
if (this._accessIronListAPI(() => this._maxScrollTop) && this._virtualCount < this._effectiveSize) {
this._adjustVirtualIndexOffset(delta);
diff --git a/test/scrolling-mode.html b/test/scrolling-mode.html
index dfa41f14f..f1b3bbac2 100644
--- a/test/scrolling-mode.html
+++ b/test/scrolling-mode.html
@@ -188,6 +188,59 @@
grid.$.table.dispatchEvent(new CustomEvent('scroll'));
});
+ it('should have an item at the bottom of the viewport after scrolling up', async() => {
+ const scrollTarget = grid.$.table;
+ grid.size = 100000;
+ await nextFrame();
+
+ // Scroll to end
+ grid.scrollToIndex(100000);
+ await nextFrame();
+
+ // Scroll upwards in 1000px steps
+ for (let i = 0; i < 20; i++) {
+ await nextFrame();
+ scrollTarget.scrollTop -= 1000;
+
+ // Sanity check for iron-list internal properties
+ const firstItem = grid._physicalItems[grid._physicalStart];
+ expect(firstItem.index).to.equal(grid._virtualStart + grid._vidxOffset);
+ }
+
+ // There should be an item at the bottom of the viewport
+ await nextFrame();
+ const gridRect = scrollTarget.getBoundingClientRect();
+ const lastVisibleElement = grid._cellFromPoint(gridRect.left, gridRect.bottom - 30);
+ expect(lastVisibleElement.localName).to.equal('td');
+ });
+
+ it('should have an item at the top of the viewport after scrolling down', async() => {
+ const scrollTarget = grid.$.table;
+ grid.size = 100000;
+ await nextFrame();
+
+ // Scroll to start
+ grid.scrollToIndex(0);
+ await nextFrame();
+
+ // Scroll downwards in 1000px steps
+ for (let i = 0; i < 20; i++) {
+ await nextFrame();
+ scrollTarget.scrollTop += 1000;
+
+ // Sanity check for iron-list internal properties
+ const firstItem = grid._physicalItems[grid._physicalStart];
+ expect(firstItem.index).to.equal(grid._virtualStart + grid._vidxOffset);
+ }
+
+ // There should be an item at the top of the viewport
+ flushGrid(grid);
+ await nextFrame();
+ const gridRect = scrollTarget.getBoundingClientRect();
+ const firstVisibleElement = grid._cellFromPoint(gridRect.left, gridRect.top + 30);
+ expect(firstVisibleElement.localName).to.equal('td');
+ });
+
describe('overflow attribute', () => {
it('bottom right', () => {