@@ -20,6 +20,7 @@ import {
2020 OnInit ,
2121 Optional ,
2222 Output ,
23+ Renderer2 ,
2324 ViewChild ,
2425 ViewEncapsulation ,
2526} from '@angular/core' ;
@@ -38,6 +39,7 @@ import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-s
3839import { ViewportRuler } from './viewport-ruler' ;
3940import { CdkVirtualScrollRepeater } from './virtual-scroll-repeater' ;
4041import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
42+ import { CdkVirtualScrollable , VIRTUAL_SCROLLABLE } from './virtual-scrollable' ;
4143
4244/** Checks if the given ranges are equal. */
4345function rangesEqual ( r1 : ListRange , r2 : ListRange ) : boolean {
@@ -67,11 +69,12 @@ const SCROLL_SCHEDULER =
6769 providers : [
6870 {
6971 provide : CdkScrollable ,
70- useExisting : CdkVirtualScrollViewport ,
72+ useFactory : ( scrollViewport : CdkVirtualScrollViewport ) => scrollViewport . scrollable ,
73+ deps : [ CdkVirtualScrollViewport ] ,
7174 } ,
7275 ] ,
7376} )
74- export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit , OnDestroy {
77+ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements OnInit , OnDestroy {
7578 /** Emits when the viewport is detached from a CdkVirtualForOf. */
7679 private readonly _detachedSubject = new Subject < void > ( ) ;
7780
@@ -179,6 +182,8 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O
179182 @Optional ( ) dir : Directionality ,
180183 scrollDispatcher : ScrollDispatcher ,
181184 viewportRuler : ViewportRuler ,
185+ renderer : Renderer2 ,
186+ @Optional ( ) @Inject ( VIRTUAL_SCROLLABLE ) public scrollable : CdkVirtualScrollable ,
182187 ) {
183188 super ( elementRef , scrollDispatcher , ngZone , dir ) ;
184189
@@ -189,11 +194,18 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O
189194 this . _viewportChanges = viewportRuler . change ( ) . subscribe ( ( ) => {
190195 this . checkViewportSize ( ) ;
191196 } ) ;
197+
198+ if ( ! this . scrollable ) {
199+ // No scrollable is provided, so the virtual-scroll-viewport needs to become a scrollable
200+ renderer . addClass ( this . elementRef . nativeElement , 'cdk-virtual-scrollable' ) ;
201+ this . scrollable = this ;
202+ }
192203 }
193204
194205 override ngOnInit ( ) {
195- super . ngOnInit ( ) ;
196-
206+ if ( this . scrollable === this ) {
207+ super . ngOnInit ( ) ;
208+ }
197209 // It's still too early to measure the viewport at this point. Deferring with a promise allows
198210 // the Viewport to be rendered with the correct size before we measure. We run this outside the
199211 // zone to avoid causing more change detection cycles. We handle the change detection loop
@@ -203,7 +215,8 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O
203215 this . _measureViewportSize ( ) ;
204216 this . _scrollStrategy . attach ( this ) ;
205217
206- this . elementScrolled ( )
218+ this . scrollable
219+ . elementScrolled ( )
207220 . pipe (
208221 // Start off with a fake scroll event so we properly detect our initial position.
209222 startWith ( null ) ,
@@ -359,7 +372,7 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O
359372 } else {
360373 options . top = offset ;
361374 }
362- this . scrollTo ( options ) ;
375+ this . scrollable . scrollTo ( options ) ;
363376 }
364377
365378 /**
@@ -372,16 +385,50 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O
372385 }
373386
374387 /**
375- * Gets the current scroll offset from the start of the viewport (in pixels).
388+ * Gets the current scroll offset from the start of the scrollable (in pixels).
376389 * @param from The edge to measure the offset from. Defaults to 'top' in vertical mode and 'start'
377390 * in horizontal mode.
378391 */
379392 override measureScrollOffset (
380393 from ?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end' ,
381394 ) : number {
382- return from
383- ? super . measureScrollOffset ( from )
384- : super . measureScrollOffset ( this . orientation === 'horizontal' ? 'start' : 'top' ) ;
395+ // This is to break the call cycle
396+ let measureScrollOffset : InstanceType < typeof CdkVirtualScrollable > [ 'measureScrollOffset' ] ;
397+ if ( this . scrollable == this ) {
398+ measureScrollOffset = ( _from : NonNullable < typeof from > ) => super . measureScrollOffset ( _from ) ;
399+ } else {
400+ measureScrollOffset = ( _from : NonNullable < typeof from > ) =>
401+ this . scrollable . measureScrollOffset ( _from ) ;
402+ }
403+
404+ return Math . max (
405+ 0 ,
406+ measureScrollOffset ( from ?? ( this . orientation === 'horizontal' ? 'start' : 'top' ) ) -
407+ this . measureViewportOffset ( ) ,
408+ ) ;
409+ }
410+
411+ measureViewportOffset ( from ?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end' ) {
412+ let fromRect : 'left' | 'top' | 'right' | 'bottom' ;
413+ const LEFT = 'left' ;
414+ const RIGHT = 'right' ;
415+ const isRtl = this . dir ?. value == 'rtl' ;
416+ if ( from == 'start' ) {
417+ fromRect = isRtl ? RIGHT : LEFT ;
418+ } else if ( from == 'end' ) {
419+ fromRect = isRtl ? LEFT : RIGHT ;
420+ } else if ( from ) {
421+ fromRect = from ;
422+ } else {
423+ fromRect = this . orientation === 'horizontal' ? 'left' : 'top' ;
424+ }
425+
426+ const scrollerClientRect = this . scrollable
427+ . getElementRef ( )
428+ . nativeElement . getBoundingClientRect ( ) [ fromRect ] ;
429+ const viewportClientRect = this . elementRef . nativeElement . getBoundingClientRect ( ) [ fromRect ] ;
430+
431+ return viewportClientRect - scrollerClientRect ;
385432 }
386433
387434 /** Measure the combined size of all of the rendered items. */
@@ -410,9 +457,7 @@ export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, O
410457
411458 /** Measure the viewport size. */
412459 private _measureViewportSize ( ) {
413- const viewportEl = this . elementRef . nativeElement ;
414- this . _viewportSize =
415- this . orientation === 'horizontal' ? viewportEl . clientWidth : viewportEl . clientHeight ;
460+ this . _viewportSize = this . scrollable . measureViewportSize ( this . orientation ) ;
416461 }
417462
418463 /** Queue up change detection to run. */
0 commit comments