diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index e05365cce0..168c8bf73c 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -551,9 +551,19 @@ export class IronListAdapter { this._adjustVirtualIndexOffset(this._scrollTop - (this.__previousScrollTop || 0)); const delta = this.scrollTarget.scrollTop - this._scrollPosition; + const lastIndexVisible = this.adjustedLastVisibleIndex === this.size - 1; + const hasEmptySpace = lastIndexVisible && this._physicalBottom < this._scrollBottom; + super._scrollHandler(); - if (this._physicalCount !== 0) { + const needToCreateItemsAbove = lastIndexVisible && (delta < 0 || hasEmptySpace); + if (needToCreateItemsAbove) { + 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); + this._update(); + } else if (this._physicalCount !== 0) { const isScrollingDown = delta >= 0; const reusables = this._getReusables(!isScrollingDown); diff --git a/packages/component-base/test/virtualizer-scrolling.test.js b/packages/component-base/test/virtualizer-scrolling.test.js index 92624853c3..27f3beb7c0 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 7c2a196941..457942295e 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 = '';