license import { coerceNumberProperty } from '@angular/cdk/coercion' ; import { Directive , forwardRef , Input , OnChanges } from '@angular/core' ; import { Observable , Subject } from 'rxjs' ; import { distinctUntilChanged } from 'rxjs/operators' ; import { VIRTUAL_SCROLL_STRATEGY , VirtualScrollStrategy } from './virtual-scroll-strategy' ; import { CdkVirtualScrollViewport } from './virtual-scroll-viewport' ; /** Virtual scrolling strategy for lists with items of known fixed size. */ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { private _scrolledIndexChange = new Subject < number > ( ) ; docs-private scrolledIndexChange : Observable < number > = this . _scrolledIndexChange . pipe ( distinctUntilChanged ( ) ) ; /** The attached viewport. */ private _viewport : CdkVirtualScrollViewport | null = null ; /** The size of the items in the virtually scrolling list. */ private _itemSize : number ; /** The minimum amount of buffer rendered beyond the viewport (in pixels). */ private _minBufferPx : number ; /** The number of buffer items to render beyond the edge of the viewport (in pixels). */ private _maxBufferPx : number ; param itemSize param minBufferPx param maxBufferPx constructor ( itemSize , minBufferPx , maxBufferPx ) { this . _itemSize = itemSize ; this . _minBufferPx = minBufferPx ; this . _maxBufferPx = maxBufferPx ; } param viewport attach ( viewport ) { this . _viewport = viewport ; this . _updateTotalContentSize ( ) ; this . _updateRenderedRange ( ) ; } /** Detaches this scroll strategy from the currently attached viewport. */ detach ( ) { this . _scrolledIndexChange . complete ( ) ; this . _viewport = null ; } param itemSize param minBufferPx param maxBufferPx updateItemAndBufferSize ( itemSize , minBufferPx , maxBufferPx ) { if ( maxBufferPx < minBufferPx ) { throw Error ( 'CDK virtual scroll: maxBufferPx must be greater than or equal to minBufferPx' ) ; } this . _itemSize = itemSize ; this . _minBufferPx = minBufferPx ; this . _maxBufferPx = maxBufferPx ; this . _updateTotalContentSize ( ) ; this . _updateRenderedRange ( ) ; } docs-private onContentScrolled ( ) { this . _updateRenderedRange ( ) ; } docs-private onDataLengthChanged ( ) { this . _updateTotalContentSize ( ) ; this . _updateRenderedRange ( ) ; } docs-private onContentRendered ( ) { } docs-private onRenderedOffsetChanged ( ) { } param index param behavior scrollToIndex ( index , behavior ) { if ( this . _viewport ) { this . _viewport . scrollToOffset ( index * this . _itemSize , behavior ) ; } } /** Update the viewport's total content size. */ private _updateTotalContentSize ( ) { if ( ! this . _viewport ) { return ; } this . _viewport . setTotalContentSize ( this . _viewport . getDataLength ( ) * this . _itemSize ) ; } /** Update the viewport's rendered range. */ private _updateRenderedRange ( ) { if ( ! this . _viewport ) { return ; } const scrollOffset = this . _viewport . measureScrollOffset ( ) ; const firstVisibleIndex = scrollOffset / this . _itemSize ; const renderedRange = this . _viewport . getRenderedRange ( ) ; const newRange = { start : renderedRange . start , end : renderedRange . end } ; const viewportSize = this . _viewport . getViewportSize ( ) ; const dataLength = this . _viewport . getDataLength ( ) ; const startBuffer = scrollOffset - newRange . start * this . _itemSize ; if ( startBuffer < this . _minBufferPx && newRange . start != 0 ) { const expandStart = Math . ceil ( ( this . _maxBufferPx - startBuffer ) / this . _itemSize ) ; newRange . start = Math . max ( 0 , newRange . start - expandStart ) ; newRange . end = Math . min ( dataLength , Math . ceil ( firstVisibleIndex + ( viewportSize + this . _minBufferPx ) / this . _itemSize ) ) ; } else { const endBuffer = newRange . end * this . _itemSize - ( scrollOffset + viewportSize ) ; if ( endBuffer < this . _minBufferPx && newRange . end != dataLength ) { const expandEnd = Math . ceil ( ( this . _maxBufferPx - endBuffer ) / this . _itemSize ) ; if ( expandEnd > 0 ) { newRange . end = Math . min ( dataLength , newRange . end + expandEnd ) ; newRange . start = Math . max ( 0 , Math . floor ( firstVisibleIndex - this . _minBufferPx / this . _itemSize ) ) ; } } } this . _viewport . setRenderedRange ( newRange ) ; this . _viewport . setRenderedContentOffset ( this . _itemSize * newRange . start ) ; this . _scrolledIndexChange . next ( Math . floor ( firstVisibleIndex ) ) ; } } param fixedSizeDir export function _fixedSizeVirtualScrollStrategyFactory ( fixedSizeDir ) { return fixedSizeDir . _scrollStrategy ; } /** A virtual scroll strategy that supports fixed-size items. */ @ Directive ( { selector : 'cdk-virtual-scroll-viewport[itemSize]' , providers : [ { provide : VIRTUAL_SCROLL_STRATEGY , useFactory : _fixedSizeVirtualScrollStrategyFactory , deps : [ forwardRef ( ( ) => CdkFixedSizeVirtualScroll ) ] , } ] , } ) export class CdkFixedSizeVirtualScroll implements OnChanges { /** The size of the items in the list (in pixels). */ @ Input ( ) get itemSize ( ) : number { return this . _itemSize ; } set itemSize ( value ) { this . _itemSize = coerceNumberProperty ( value ) ; } _itemSize = 20 ; /**
     * The minimum amount of buffer rendered beyond the viewport (in pixels).
     * If the amount of buffer dips below this number, more items will be rendered. Defaults to 100px.
     */ @ Input ( ) get minBufferPx ( ) : number { return this . _minBufferPx ; } set minBufferPx ( value ) { this . _minBufferPx = coerceNumberProperty ( value ) ; } _minBufferPx = 100 ; /**
     * The number of pixels worth of buffer to render for when rendering new items. Defaults to 200px.
     */ @ Input ( ) get maxBufferPx ( ) : number { return this . _maxBufferPx ; } set maxBufferPx ( value ) { this . _maxBufferPx = coerceNumberProperty ( value ) ; } _maxBufferPx = 200 ; /** The scroll strategy used by this directive. */ _scrollStrategy = new FixedSizeVirtualScrollStrategy ( this . itemSize , this . minBufferPx , this . maxBufferPx ) ; ngOnChanges ( ) { this . _scrollStrategy . updateItemAndBufferSize ( this . itemSize , this . minBufferPx , this . maxBufferPx ) ; } }