Skip to content

Commit

Permalink
feat(cdk): added virtual scrolling and for-of
Browse files Browse the repository at this point in the history
  • Loading branch information
pimenovoleg committed Dec 29, 2018
1 parent 20e3900 commit 66fd1f6
Show file tree
Hide file tree
Showing 19 changed files with 2,249 additions and 46 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@
"server-dev:badge": "npm run server-dev -- --env.component badge",
"server-dev:button": "npm run server-dev -- --env.component button",
"server-dev:card": "npm run server-dev -- --env.component card",
"server-dev:cdk-vscroll-custom-strategy": "npm run server-dev -- --env.component cdk-virtual-scroll-custom-strategy",
"server-dev:cdk-vscroll-data-source": "npm run server-dev -- --env.component cdk-virtual-scroll-data-source",
"server-dev:checkbox": "npm run server-dev -- --env.component checkbox",
"server-dev:dropdown": "npm run server-dev -- --env.component dropdown",
"server-dev:icon": "npm run server-dev -- --env.component icon",
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/keycodes/keycodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Commonly used keycode constants.
#### Example
```ts
import {Directive} from '@angular/core';
import {UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
import {UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW} from '@ptsecurity/cdk/keycodes';

@Directive({
selector: '[count-arrows]'
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const cdkVersion = loadPackageVersionGracefully('@ptsecurity/cdk');

/**
* Schematic factory entry-point for the `ng-add` schematic. The ng-add schematic will be
* automatically executed if developers run `ng add @angular/cdk`.
* automatically executed if developers run `ng add @ptsecurity/cdk`.
*/
export default function(): Rule {
return (host: Tree) => {
Expand Down
219 changes: 219 additions & 0 deletions src/cdk/scrolling/fixed-size-virtual-scroll.ts
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);
}
}
3 changes: 3 additions & 0 deletions src/cdk/scrolling/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export * from './scroll-dispatcher';
export * from './scrollable';
export * from './scrolling-module';
export * from './viewport-ruler';
export * from './virtual-for-of';
export * from './virtual-scroll-strategy';
export * from './virtual-scroll-viewport';
16 changes: 8 additions & 8 deletions src/cdk/scrolling/scroll-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export class ScrollDispatcher implements OnDestroy {
* @param scrollable Scrollable instance to be registered.
*/
register(scrollable: CdkScrollable): void {
const scrollSubscription = scrollable.elementScrolled()
.subscribe(() => this._scrolled.next(scrollable));

this.scrollContainers.set(scrollable, scrollSubscription);
if (!this.scrollContainers.has(scrollable)) {
this.scrollContainers.set(scrollable, scrollable.elementScrolled()
.subscribe(() => this._scrolled.next(scrollable)));
}
}

/**
Expand Down Expand Up @@ -138,14 +138,14 @@ export class ScrollDispatcher implements OnDestroy {

/** Returns true if the element is contained within the provided Scrollable. */
private scrollableContainsElement(scrollable: CdkScrollable, elementRef: ElementRef): boolean {
let element = elementRef.nativeElement;
let scrollableElement = scrollable.getElementRef().nativeElement; //tslint:disable-line
let element: HTMLElement | null = elementRef.nativeElement;
let scrollableElement = scrollable.getElementRef().nativeElement;

// Traverse through the element parents until we reach null, checking if any of the elements
// are the scrollable's element.
do {
if (element === scrollableElement) { return true; }
} while (element = element.parentElement); // tslint:disable-line
if (element == scrollableElement) { return true; }
} while (element = element!.parentElement);

return false;
}
Expand Down
13 changes: 11 additions & 2 deletions src/cdk/scrolling/scrolling-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ import { NgModule } from '@angular/core';
import { BidiModule } from '@ptsecurity/cdk/bidi';
import { PlatformModule } from '@ptsecurity/cdk/platform';

import { CdkFixedSizeVirtualScroll } from './fixed-size-virtual-scroll';
import { CdkScrollable } from './scrollable';
import { CdkVirtualForOf } from './virtual-for-of';
import { CdkVirtualScrollViewport } from './virtual-scroll-viewport';


@NgModule({
imports: [BidiModule, PlatformModule],
exports: [
BidiModule,
CdkScrollable
CdkFixedSizeVirtualScroll,
CdkScrollable,
CdkVirtualForOf,
CdkVirtualScrollViewport
],
declarations: [
CdkScrollable
CdkFixedSizeVirtualScroll,
CdkScrollable,
CdkVirtualForOf,
CdkVirtualScrollViewport
]
})
export class ScrollingModule {}
Expand Down
Loading

0 comments on commit 66fd1f6

Please sign in to comment.