-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cdk): added virtual scrolling and for-of
- Loading branch information
1 parent
20e3900
commit 66fd1f6
Showing
19 changed files
with
2,249 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
import { Directive, forwardRef, Input, OnChanges } from '@angular/core'; | ||
import { coerceNumberProperty } from '@ptsecurity/cdk/coercion'; | ||
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 Implemented as part of VirtualScrollStrategy. */ | ||
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 The size of the items in the virtually scrolling list. | ||
* @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more | ||
* @param maxBufferPx The amount of buffer (in pixels) to render when rendering more. | ||
*/ | ||
constructor(itemSize: number, minBufferPx: number, maxBufferPx: number) { | ||
this._itemSize = itemSize; | ||
this._minBufferPx = minBufferPx; | ||
this._maxBufferPx = maxBufferPx; | ||
} | ||
|
||
/** | ||
* Attaches this scroll strategy to a viewport. | ||
* @param viewport The viewport to attach this strategy to. | ||
*/ | ||
attach(viewport: CdkVirtualScrollViewport) { | ||
this._viewport = viewport; | ||
this._updateTotalContentSize(); | ||
this._updateRenderedRange(); | ||
} | ||
|
||
/** Detaches this scroll strategy from the currently attached viewport. */ | ||
detach() { | ||
this._scrolledIndexChange.complete(); | ||
this._viewport = null; | ||
} | ||
|
||
/** | ||
* Update the item size and buffer size. | ||
* @param itemSize The size of the items in the virtually scrolling list. | ||
* @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more | ||
* @param maxBufferPx The amount of buffer (in pixels) to render when rendering more. | ||
*/ | ||
updateItemAndBufferSize(itemSize: number, minBufferPx: number, maxBufferPx: number) { | ||
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 Implemented as part of VirtualScrollStrategy. */ | ||
onContentScrolled() { | ||
this._updateRenderedRange(); | ||
} | ||
|
||
/** @docs-private Implemented as part of VirtualScrollStrategy. */ | ||
onDataLengthChanged() { | ||
this._updateTotalContentSize(); | ||
this._updateRenderedRange(); | ||
} | ||
|
||
/** @docs-private Implemented as part of VirtualScrollStrategy. */ | ||
onContentRendered() { /* no-op */ | ||
} | ||
|
||
/** @docs-private Implemented as part of VirtualScrollStrategy. */ | ||
onRenderedOffsetChanged() { /* no-op */ | ||
} | ||
|
||
/** | ||
* Scroll to the offset for the given index. | ||
* @param index The index of the element to scroll to. | ||
* @param behavior The ScrollBehavior to use when scrolling. | ||
*/ | ||
scrollToIndex(index: number, behavior: ScrollBehavior): void { | ||
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)); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Provider factory for `FixedSizeVirtualScrollStrategy` that simply extracts the already created | ||
* `FixedSizeVirtualScrollStrategy` from the given directive. | ||
* @param fixedSizeDir The instance of `CdkFixedSizeVirtualScroll` to extract the | ||
* `FixedSizeVirtualScrollStrategy` from. | ||
*/ | ||
export function _fixedSizeVirtualScrollStrategyFactory(fixedSizeDir: CdkFixedSizeVirtualScroll) { | ||
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: number) { | ||
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: number) { | ||
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: number) { | ||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.