diff --git a/packages/component-base/src/virtualizer-iron-list-adapter.js b/packages/component-base/src/virtualizer-iron-list-adapter.js index 2c2f177edb4..a9d02b1f310 100644 --- a/packages/component-base/src/virtualizer-iron-list-adapter.js +++ b/packages/component-base/src/virtualizer-iron-list-adapter.js @@ -52,6 +52,20 @@ export class IronListAdapter { this.__resizeObserver.observe(this.scrollTarget); this.scrollTarget.addEventListener('scroll', () => this._scrollHandler()); + const attachObserver = new ResizeObserver(([{ contentRect }]) => { + const isHidden = contentRect.width === 0 && contentRect.height === 0; + if (!isHidden && this.__scrollTargetHidden && this.scrollTarget.scrollTop !== this._scrollPosition) { + // When removing element from DOM, its scroll position is lost and + // virtualizer doesn't re-render when adding it to the DOM again. + // Restore scroll position when the scroll target becomes visible, + // which is the case e.g. when virtualizer is used inside a dialog. + this.scrollTarget.scrollTop = this._scrollPosition; + } + + this.__scrollTargetHidden = isHidden; + }); + attachObserver.observe(this.scrollTarget); + this._scrollLineHeight = this._getScrollLineHeight(); this.scrollTarget.addEventListener('wheel', (e) => this.__onWheel(e)); diff --git a/packages/component-base/test/virtualizer.test.js b/packages/component-base/test/virtualizer.test.js index 04ece5c58ad..1487234f8ba 100644 --- a/packages/component-base/test/virtualizer.test.js +++ b/packages/component-base/test/virtualizer.test.js @@ -1,5 +1,5 @@ import { expect } from '@vaadin/chai-plugins'; -import { aTimeout, fixtureSync, nextFrame, oneEvent } from '@vaadin/testing-helpers'; +import { aTimeout, fixtureSync, nextFrame, nextResize, oneEvent } from '@vaadin/testing-helpers'; import sinon from 'sinon'; import { Virtualizer } from '../src/virtualizer.js'; @@ -350,6 +350,22 @@ describe('virtualizer', () => { expect(initialCount).not.to.be.above(expectedCount); }); + it('should preserve scroll position when moving within DOM and changing visibility', async () => { + scrollTarget.scrollTop = 100; + await oneEvent(scrollTarget, 'scroll'); + + scrollTarget.hidden = true; + await nextResize(scrollTarget); + + const wrapper = fixtureSync('
'); + wrapper.appendChild(scrollTarget); + + scrollTarget.hidden = false; + await nextResize(scrollTarget); + + expect(scrollTarget.scrollTop).to.equal(100); + }); + describe('lazy rendering', () => { let render = false;