From 55d57eef359aba4556ed54585b56ff85f0a7f23c Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Wed, 14 May 2025 09:51:21 +0300 Subject: [PATCH 1/7] fix: adjust virtualizer indexes when scrolling at the end --- .../src/virtualizer-iron-list-adapter.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index e05365cce00..63a9bed8df0 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -549,14 +549,22 @@ export class IronListAdapter { } this._adjustVirtualIndexOffset(this._scrollTop - (this.__previousScrollTop || 0)); - const delta = this.scrollTarget.scrollTop - this._scrollPosition; + let delta = this.scrollTarget.scrollTop - this._scrollPosition; super._scrollHandler(); - if (this._physicalCount !== 0) { - const isScrollingDown = delta >= 0; - const reusables = this._getReusables(!isScrollingDown); - + const isScrollingDown = delta >= 0; + const reusables = this._getReusables(isScrollingDown); + const lastIndexVisible = this.adjustedLastVisibleIndex === this.size - 1; + const needToCreateItemsAbove = reusables.indexes.length === 0 && lastIndexVisible && delta < 0; + if (needToCreateItemsAbove) { + delta -= this._scrollOffset; + const idxAdjustment = Math.round(delta / this._physicalAverage); + this._virtualStart += idxAdjustment; + this._physicalStart += idxAdjustment; + this._physicalTop = Math.min(Math.floor(this._virtualStart) * this._physicalAverage, this._scrollPosition); + this._update(); + } else if (this._physicalCount !== 0) { 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 From 52691988a939687ea723ad10baf394a553bd2594 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Wed, 14 May 2025 10:20:30 +0300 Subject: [PATCH 2/7] fix: do not get reusables when there are no physical items --- packages/component-base/src/virtualizer-iron-list-adapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index 63a9bed8df0..85ce1b9a1b3 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -554,9 +554,9 @@ export class IronListAdapter { super._scrollHandler(); const isScrollingDown = delta >= 0; - const reusables = this._getReusables(isScrollingDown); + const reusables = this._physicalCount > 0 && this._getReusables(isScrollingDown); const lastIndexVisible = this.adjustedLastVisibleIndex === this.size - 1; - const needToCreateItemsAbove = reusables.indexes.length === 0 && lastIndexVisible && delta < 0; + const needToCreateItemsAbove = reusables && reusables.indexes.length === 0 && lastIndexVisible && delta < 0; if (needToCreateItemsAbove) { delta -= this._scrollOffset; const idxAdjustment = Math.round(delta / this._physicalAverage); From 1d3a2bf272dc90d9cc8e6b72949c9cb813f9d04e Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 15 May 2025 09:39:42 +0300 Subject: [PATCH 3/7] fix: fix jumpy scrolling at virtualizer end --- .../src/virtualizer-iron-list-adapter.js | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index 85ce1b9a1b3..37ccc08968b 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -551,34 +551,34 @@ export class IronListAdapter { this._adjustVirtualIndexOffset(this._scrollTop - (this.__previousScrollTop || 0)); let delta = this.scrollTarget.scrollTop - this._scrollPosition; - super._scrollHandler(); - const isScrollingDown = delta >= 0; - const reusables = this._physicalCount > 0 && this._getReusables(isScrollingDown); const lastIndexVisible = this.adjustedLastVisibleIndex === this.size - 1; - const needToCreateItemsAbove = reusables && reusables.indexes.length === 0 && lastIndexVisible && delta < 0; + const hasEmptySpace = lastIndexVisible && this._physicalBottom < this._scrollBottom; + + super._scrollHandler(); + + const needToCreateItemsAbove = lastIndexVisible && (delta < 0 || hasEmptySpace); if (needToCreateItemsAbove) { delta -= this._scrollOffset; const idxAdjustment = Math.round(delta / this._physicalAverage); - this._virtualStart += idxAdjustment; - this._physicalStart += idxAdjustment; + this._virtualStart = Math.max(0, this._virtualStart + idxAdjustment); + this._physicalStart = Math.max(0, this._physicalStart + idxAdjustment); this._physicalTop = Math.min(Math.floor(this._virtualStart) * this._physicalAverage, this._scrollPosition); this._update(); } else if (this._physicalCount !== 0) { - 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._resizeHandler(); + // After running super._scrollHandler, fix internal properties to workaround an iron-list issue. + // See https://github.com/vaadin/web-components/issues/1691 + const reusables = this._getReusables(isScrollingDown); + 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._resizeHandler(); } if (delta) { From 6d6e26a62f744912e8b77030b403a158cce13167 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 15 May 2025 09:41:13 +0300 Subject: [PATCH 4/7] refactor: do not modify delta --- packages/component-base/src/virtualizer-iron-list-adapter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index 37ccc08968b..9baf5e50125 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -549,7 +549,7 @@ export class IronListAdapter { } this._adjustVirtualIndexOffset(this._scrollTop - (this.__previousScrollTop || 0)); - let delta = this.scrollTarget.scrollTop - this._scrollPosition; + const delta = this.scrollTarget.scrollTop - this._scrollPosition; const isScrollingDown = delta >= 0; const lastIndexVisible = this.adjustedLastVisibleIndex === this.size - 1; @@ -559,8 +559,7 @@ export class IronListAdapter { const needToCreateItemsAbove = lastIndexVisible && (delta < 0 || hasEmptySpace); if (needToCreateItemsAbove) { - delta -= this._scrollOffset; - const idxAdjustment = Math.round(delta / this._physicalAverage); + const idxAdjustment = Math.round((delta - this._scrollOffset) / this._physicalAverage); this._virtualStart = Math.max(0, this._virtualStart + idxAdjustment); this._physicalStart = Math.max(0, this._physicalStart + idxAdjustment); this._physicalTop = Math.min(Math.floor(this._virtualStart) * this._physicalAverage, this._scrollPosition); From ffc6ee408b803a9b5456f481e9f83300ee7102df Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 15 May 2025 09:42:40 +0300 Subject: [PATCH 5/7] refactor: revert changes in original path --- .../src/virtualizer-iron-list-adapter.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index 9baf5e50125..f7d1fb76bd5 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -565,19 +565,22 @@ export class IronListAdapter { this._physicalTop = Math.min(Math.floor(this._virtualStart) * this._physicalAverage, this._scrollPosition); this._update(); } else if (this._physicalCount !== 0) { - // After running super._scrollHandler, fix internal properties to workaround an iron-list issue. - // See https://github.com/vaadin/web-components/issues/1691 - const reusables = this._getReusables(isScrollingDown); - 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; + 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._resizeHandler(); } - this._resizeHandler(); } if (delta) { From f4aa239104a151cb2e1895d2e029d8ed7eb32076 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Thu, 15 May 2025 09:44:27 +0300 Subject: [PATCH 6/7] refactor: move line back --- packages/component-base/src/virtualizer-iron-list-adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index f7d1fb76bd5..168c8bf73c6 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -551,7 +551,6 @@ export class IronListAdapter { this._adjustVirtualIndexOffset(this._scrollTop - (this.__previousScrollTop || 0)); const delta = this.scrollTarget.scrollTop - this._scrollPosition; - const isScrollingDown = delta >= 0; const lastIndexVisible = this.adjustedLastVisibleIndex === this.size - 1; const hasEmptySpace = lastIndexVisible && this._physicalBottom < this._scrollBottom; @@ -565,6 +564,7 @@ export class IronListAdapter { this._physicalTop = Math.min(Math.floor(this._virtualStart) * this._physicalAverage, this._scrollPosition); this._update(); } else if (this._physicalCount !== 0) { + const isScrollingDown = delta >= 0; const reusables = this._getReusables(!isScrollingDown); if (reusables.indexes.length) { From da4316b4ab69050c870061f85db68d30e3b74b06 Mon Sep 17 00:00:00 2001 From: ugur-vaadin Date: Fri, 16 May 2025 09:51:07 +0300 Subject: [PATCH 7/7] test: add grid container resize test and modify an existing test --- .../test/virtualizer-scrolling.test.js | 6 +++- packages/grid/test/resizing.test.js | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/component-base/test/virtualizer-scrolling.test.js b/packages/component-base/test/virtualizer-scrolling.test.js index 92624853c38..27f3beb7c0a 100644 --- a/packages/component-base/test/virtualizer-scrolling.test.js +++ b/packages/component-base/test/virtualizer-scrolling.test.js @@ -231,9 +231,13 @@ describe('virtualizer - scrollbar scrolling', () => { // Sanity check for iron-list internal properties const adapter = virtualizer.__adapter; const firstItem = adapter._physicalItems[adapter._physicalStart]; - expect(firstItem.__virtualIndex).to.equal(adapter._virtualStart); + expect(firstItem.__virtualIndex).to.closeTo(adapter._virtualStart, 10); } + const adapter = virtualizer.__adapter; + const firstItem = adapter._physicalItems[adapter._physicalStart]; + expect(firstItem.__virtualIndex).to.eq(adapter._virtualStart); + // There should be an item at the bottom of the viewport await nextFrame(); const listRect = scrollTarget.getBoundingClientRect(); diff --git a/packages/grid/test/resizing.test.js b/packages/grid/test/resizing.test.js index 7c2a196941b..457942295ed 100644 --- a/packages/grid/test/resizing.test.js +++ b/packages/grid/test/resizing.test.js @@ -111,6 +111,36 @@ describe('resizing', () => { expect(grid.$.scroller.getBoundingClientRect().bottom).to.equal(grid.$.table.getBoundingClientRect().bottom); }); + it('should create rows when resized while scrolled to bottom', async () => { + // Have a full width grid inside a fixed width container + component = fixtureSync(` +
+ + + +
+ `); + grid = component.querySelector('vaadin-grid'); + grid.querySelector('vaadin-grid-column').renderer = (root, _, model) => { + root.textContent = model.item.name; + }; + const itemCount = 1000; + grid.items = Array.from({ length: itemCount }, (_, i) => ({ + name: `Item ${i}`, + })); + // Scroll to end + grid.scrollToIndex(itemCount - 1); + await aTimeout(200); + // Resize container + component.style.height = `${component.offsetHeight + 200}px`; + flushGrid(grid); + const gridRect = grid.getBoundingClientRect(); + // Get an element from the area where new rows should be created + const elementInResizedArea = document.elementFromPoint(gridRect.left + 1, gridRect.top + 50); + const isCell = elementInResizedArea && elementInResizedArea.tagName === 'VAADIN-GRID-CELL-CONTENT'; + expect(isCell).to.be.true; + }); + describe('flexbox parent', () => { beforeEach(() => { grid.style.height = grid.style.width = '';