Skip to content

Commit 55f8e6d

Browse files
committed
fix(cdk/scrolling): scrollToIndex combined with cdkVirtualScrollingEement can result in wrong scroll offset
Fixes a bug when using cdkVirtualScrollingElement and there is some space between the scrolling element and the viewport the scroll to index function doesn't take into account the viewport offset from the scrolling container. Fixes #33063
1 parent 3c6f299 commit 55f8e6d

4 files changed

Lines changed: 44 additions & 10 deletions

File tree

goldens/cdk/scrolling/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
202202
scrollable: CdkVirtualScrollable;
203203
readonly scrolledIndexChange: Observable<number>;
204204
scrollToIndex(index: number, behavior?: ScrollBehavior): void;
205-
scrollToOffset(offset: number, behavior?: ScrollBehavior): void;
205+
scrollToOffset(offset: number, behavior?: ScrollBehavior, relativeTo?: 'viewport' | 'scrollingContainer'): void;
206206
setRenderedContentOffset(offset: number, to?: 'to-start' | 'to-end'): void;
207207
setRenderedRange(range: ListRange): void;
208208
setTotalContentSize(size: number): void;

src/cdk/scrolling/fixed-size-virtual-scroll.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy {
104104
*/
105105
scrollToIndex(index: number, behavior: ScrollBehavior): void {
106106
if (this._viewport) {
107-
this._viewport.scrollToOffset(index * this._itemSize, behavior);
107+
this._viewport.scrollToOffset(index * this._itemSize, behavior, 'viewport');
108108
}
109109
}
110110

src/cdk/scrolling/virtual-scroll-viewport.spec.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,16 +1076,27 @@ describe('CdkVirtualScrollViewport', () => {
10761076
.toBe(50);
10771077
});
10781078

1079-
it('should measure scroll offset with custom scrolling element', async () => {
1079+
it('should scroll to offset relative to scrolling container', async () => {
10801080
await finishInit(fixture);
1081-
await triggerScroll(viewport, 100);
1081+
await triggerScroll(viewport, 100, 'scrollingContainer');
10821082
fixture.detectChanges();
10831083
await fixture.whenStable();
10841084

10851085
expect(viewport.measureScrollOffset('top'))
1086-
.withContext('should be 50 (actual scroll offset - viewport offset)')
1086+
.withContext('should be 50 (scrolling container offset)')
10871087
.toBe(50);
10881088
});
1089+
1090+
it('should scroll to offset relative to viewport', async () => {
1091+
await finishInit(fixture);
1092+
await triggerScroll(viewport, 100, 'viewport');
1093+
fixture.detectChanges();
1094+
await fixture.whenStable();
1095+
1096+
expect(viewport.measureScrollOffset('top'))
1097+
.withContext('should be 100 (viewport offset)')
1098+
.toBe(100);
1099+
});
10891100
});
10901101

10911102
describe('with scrollable window', () => {
@@ -1138,9 +1149,13 @@ async function finishInit(fixture: ComponentFixture<any>) {
11381149
}
11391150

11401151
/** Trigger a scroll event on the viewport (optionally setting a new scroll offset). */
1141-
async function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) {
1152+
async function triggerScroll(
1153+
viewport: CdkVirtualScrollViewport,
1154+
offset?: number,
1155+
relativeTo?: 'viewport' | 'scrollingContainer',
1156+
) {
11421157
if (offset !== undefined) {
1143-
viewport.scrollToOffset(offset);
1158+
viewport.scrollToOffset(offset, 'auto', relativeTo);
11441159
}
11451160
dispatchFakeEvent(viewport.scrollable!.getElementRef().nativeElement, 'scroll');
11461161
await new Promise(resolve => setTimeout(resolve, 50));

src/cdk/scrolling/virtual-scroll-viewport.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,13 +411,28 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
411411
* direction, this would be the equivalent of setting a fictional `scrollRight` property.
412412
* @param offset The offset to scroll to.
413413
* @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
414+
* @param relativeTo The start point of the offset. Default is `scrollingContainer`.
414415
*/
415-
scrollToOffset(offset: number, behavior: ScrollBehavior = 'auto') {
416+
scrollToOffset(
417+
offset: number,
418+
behavior: ScrollBehavior = 'auto',
419+
relativeTo: 'viewport' | 'scrollingContainer' = 'scrollingContainer',
420+
) {
416421
const options: ExtendedScrollToOptions = {behavior};
417422
if (this.orientation === 'horizontal') {
418-
options.start = offset;
423+
if (relativeTo === 'scrollingContainer') {
424+
options.start = offset;
425+
} else {
426+
const viewportOffset = this.measureViewportOffset('start');
427+
options.start = viewportOffset + offset;
428+
}
419429
} else {
420-
options.top = offset;
430+
if (relativeTo === 'scrollingContainer') {
431+
options.top = offset;
432+
} else {
433+
const viewportOffset = this.measureViewportOffset('top');
434+
options.top = viewportOffset + offset;
435+
}
421436
}
422437
this.scrollable.scrollTo(options);
423438
}
@@ -460,6 +475,10 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
460475
* @param from The edge to measure from.
461476
*/
462477
measureViewportOffset(from?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end') {
478+
if (this.scrollable === this) {
479+
return 0;
480+
}
481+
463482
let fromRect: 'left' | 'top' | 'right' | 'bottom';
464483
const LEFT = 'left';
465484
const RIGHT = 'right';

0 commit comments

Comments
 (0)