From 4adad91518a200e1c84efceeedd6b84be59476c9 Mon Sep 17 00:00:00 2001 From: Vaadin Bot Date: Tue, 11 Feb 2025 12:38:09 +0100 Subject: [PATCH] fix: restore scroll position when virtualizer used in dialog (#8642) (#8671) Co-authored-by: Serhii Kulykov --- .../src/virtualizer-iron-list-adapter.js | 14 ++++++++++++++ .../component-base/test/virtualizer.test.js | 18 +++++++++++++++++- 2 files changed, 31 insertions(+), 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 d2838238162..2abcf325c16 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;