Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions packages/component-base/src/virtualizer-iron-list-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,19 @@ export class IronListAdapter {
return element ? this.scrollTarget.getBoundingClientRect().top - element.getBoundingClientRect().top : undefined;
}

/**
* Adjusts the scroll position to compensate for any offset change of a given index.
* @param {number} index - The index whose scroll offset to restore
* @param {number|undefined} offsetBefore - The scroll offset of the index before the change
* @private
*/
__restoreScrollOffset(index, offsetBefore) {
const offsetAfter = this.__getIndexScrollOffset(index);
if (offsetBefore !== undefined && offsetAfter !== undefined) {
this._scrollTop += offsetBefore - offsetAfter;
}
}

get size() {
return this.__size;
}
Expand Down Expand Up @@ -382,10 +395,7 @@ export class IronListAdapter {
// and remove exceeding items when size is decreased.
this.scrollToIndex(fvi);

const fviOffsetAfter = this.__getIndexScrollOffset(fvi);
if (fviOffsetBefore !== undefined && fviOffsetAfter !== undefined) {
this._scrollTop += fviOffsetBefore - fviOffsetAfter;
}
this.__restoreScrollOffset(fvi, fviOffsetBefore);
}

this.__preventElementUpdates = false;
Expand Down Expand Up @@ -851,6 +861,9 @@ export class IronListAdapter {
const threshold = OFFSET_ADJUST_MIN_THRESHOLD;
const maxShift = 100;

const fvi = this.adjustedFirstVisibleIndex;
const fviOffsetBefore = this.__getIndexScrollOffset(fvi);

// Near start
if (this._scrollTop === 0) {
this._vidxOffset = 0;
Expand All @@ -872,6 +885,8 @@ export class IronListAdapter {
this._vidxOffset += Math.min(maxOffset - this._vidxOffset, maxShift);
super.scrollToIndex(this.firstVisibleIndex - (this._vidxOffset - oldOffset));
}

this.__restoreScrollOffset(fvi, fviOffsetBefore);
}
}
}
Expand Down
31 changes: 27 additions & 4 deletions packages/component-base/test/virtualizer-unlimited-size.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ describe('unlimited size', () => {
return [...elementsContainer.children].reduce((max, el) => Math.max(max, el.index), 0);
}

function getIndexScrollOffset(fvi) {
const element = elementsContainer.querySelector(`#item-${fvi}`);
return scrollTarget.getBoundingClientRect().top - element.getBoundingClientRect().top;
}

it('should scroll to a large index', () => {
const index = Math.floor(virtualizer.size / 2);
virtualizer.scrollToIndex(index);
Expand Down Expand Up @@ -159,10 +164,18 @@ describe('unlimited size', () => {
smallestIndex = Math.min(...Array.from(elementsContainer.children).map((el) => el.index));

scrollTarget.scrollTop -= (elementHeight * elementCount) / 2;

// Scroll offset of the index before the scroll event
const scrollOffsetBefore = getIndexScrollOffset(smallestIndex);
await oneEvent(scrollTarget, 'scroll');

const item = elementsContainer.querySelector(`#item-${smallestIndex}`);
expect(item).to.be.ok;

// Scroll offset of the same index after the scroll event
const scrollOffsetAfter = getIndexScrollOffset(smallestIndex);
// Expect the scroll offset to be approximately the same, meaning the scroll position is preserved relative to the item
expect(scrollOffsetAfter).to.be.closeTo(scrollOffsetBefore, 1);
}

const item = elementsContainer.querySelector('#item-0');
Expand Down Expand Up @@ -267,8 +280,7 @@ describe('unlimited size', () => {
expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1);
});

// FIXME: Fails due to a scroll offset reset caused by _adjustVirtualIndexOffset on scroll event.
it.skip('should preserve scroll position when size decrease does not affect any rendered indexes', async () => {
it('should preserve scroll position when size decrease does not affect any rendered indexes', async () => {
// Scroll to an index and add an additional scroll offset.
const index = virtualizer.size - 2000;
virtualizer.scrollToIndex(index);
Expand All @@ -282,8 +294,7 @@ describe('unlimited size', () => {
expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1);
});

// FIXME: Fails due to a scroll offset reset caused by _adjustVirtualIndexOffset on scroll event.
it.skip('should preserve scroll position on size increase', async () => {
it('should preserve scroll position on size increase', async () => {
const index = virtualizer.size - 2000;
virtualizer.scrollToIndex(index);
scrollTarget.scrollTop += 10;
Expand All @@ -294,4 +305,16 @@ describe('unlimited size', () => {
const item = elementsContainer.querySelector(`#item-${index}`);
expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1);
});

it('should preserve scroll position on large size decrease', async () => {
const index = 300000;
virtualizer.scrollToIndex(index);
scrollTarget.scrollTop += 10;

virtualizer.size = 500000;
await oneEvent(scrollTarget, 'scroll');

const item = elementsContainer.querySelector(`#item-${index}`);
expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1);
});
});
Loading