From 868bbf9025b6f8597e686c368da8858347d0418a Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 24 May 2018 13:49:29 -0500 Subject: [PATCH 01/11] virtual scroll: implement scrollTo functions --- .../scrolling/auto-size-virtual-scroll.ts | 5 +++ .../scrolling/fixed-size-virtual-scroll.ts | 5 +++ .../scrolling/virtual-scroll-strategy.ts | 3 ++ .../scrolling/virtual-scroll-viewport.spec.ts | 9 ++++ .../scrolling/virtual-scroll-viewport.ts | 41 +++++++++++++++---- src/cdk/platform/features.ts | 19 +++++++++ 6 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index 027e703cf01c..253128417a3d 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -151,6 +151,11 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { } } + /** Get the offset for the given index. */ + getOffsetForIndex(index: number) { + return index * this._averager.getAverageItemSize(); + } + /** * Update the buffer parameters. * @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels). diff --git a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts index 2e0537b246eb..958dc978dd73 100644 --- a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts @@ -77,6 +77,11 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { /** @docs-private Implemented as part of VirtualScrollStrategy. */ onRenderedOffsetChanged() { /* no-op */ } + /** Get the offset for the given index. */ + getOffsetForIndex(index: number) { + return index * this._itemSize; + } + /** Update the viewport's total content size. */ private _updateTotalContentSize() { if (!this._viewport) { diff --git a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts index cbb667201709..4b59f18885b4 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts @@ -37,4 +37,7 @@ export interface VirtualScrollStrategy { /** Called when the offset of the rendered items changed. */ onRenderedOffsetChanged(); + + /** Get the offset for the given index. */ + getOffsetForIndex(index: number); } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index 01af96e49834..665584457f23 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -145,6 +145,15 @@ describe('CdkVirtualScrollViewport', () => { expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); })); + it('should scroll to offset', fakeAsync(() => { + finishInit(fixture); + viewport.scrollToOffset(testComponent.itemSize * 2); + fixture.detectChanges(); + + expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + it('should update viewport as user scrolls down', fakeAsync(() => { finishInit(fixture); diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index c409f8c137ae..1fe080f73fd8 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -26,6 +26,7 @@ import {animationFrameScheduler, fromEvent, Observable, Subject} from 'rxjs'; import {sampleTime, take, takeUntil} from 'rxjs/operators'; import {CdkVirtualForOf} from './virtual-for-of'; import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy'; +import { supportsSmoothScroll } from '@angular/cdk/platform'; /** Checks if the given ranges are equal. */ @@ -128,12 +129,8 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy { ngDoCheck() { // In order to batch setting the scroll offset together with other DOM writes, we wait until a // change detection cycle to actually apply it. - if (this._pendingScrollOffset != null) { - if (this.orientation === 'horizontal') { - this.elementRef.nativeElement.scrollLeft = this._pendingScrollOffset; - } else { - this.elementRef.nativeElement.scrollTop = this._pendingScrollOffset; - } + if (this._pendingScrollOffset !== null) { + this.scrollToOffset(this._pendingScrollOffset); } } @@ -275,7 +272,37 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy { } } - /** Sets the scroll offset on the viewport. */ + /** Scrolls to the offset on the viewport. */ + scrollToOffset(offset: number, options = { smooth: false, lazy: false }) { + const viewportElement = this.elementRef.nativeElement; + const top = this.orientation === 'vertical' ? offset : 0; + const left = this.orientation === 'horizontal' ? offset : 0; + + let shouldScroll = true; + if (options.lazy) { + const currentOffset = this.measureScrollOffset(); + const currentOffsetEnd = currentOffset + this.measureRenderedContentSize(); + shouldScroll = offset < currentOffset || offset > currentOffsetEnd; + } + + if (shouldScroll) { + const smooth = options.smooth && supportsSmoothScroll(); + if (smooth) { + viewportElement.scrollTo({ left, top, behavior: 'smooth' }); + } else { + viewportElement.scrollTo(left, top); + } + } + } + + /** Scroll the viewport to the specified index. */ + scrollToIndex(index: number, options = { smooth: false, lazy: false }) { + const contentSize = this.measureRenderedContentSize(); + const offset = this._scrollStrategy.getOffsetForIndex(index); + this.scrollToOffset(offset, options); + } + + /** Internal method to set the scroll offset on the viewport. */ setScrollOffset(offset: number) { // Rather than setting the offset immediately, we batch it up to be applied along with other DOM // writes during the next change detection cycle. diff --git a/src/cdk/platform/features.ts b/src/cdk/platform/features.ts index 10cd148f40f6..f72bc4929dec 100644 --- a/src/cdk/platform/features.ts +++ b/src/cdk/platform/features.ts @@ -27,6 +27,25 @@ export function supportsPassiveEventListeners(): boolean { return supportsPassiveEvents; } +/** Cached result of whether the user's browser supports scroll behaviors. */ +let scrollSupport = true; + +/** + * Check whether the browser supports scroll behaviors. + * https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo + */ +export function supportsSmoothScroll(): boolean { + if (scrollSupport == null && typeof window !== 'undefined') { + try { + window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); + } catch { + scrollSupport = false; + } + } + + return scrollSupport; +} + /** Cached result Set of input types support by the current browser. */ let supportedInputTypes: Set; From a31a4c81b8175ba585d8fafbd24aeffd06b37408 Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 31 May 2018 14:23:02 -0500 Subject: [PATCH 02/11] fix(scroll): fix scrolling not working all the time --- src/cdk-experimental/scrolling/virtual-scroll-viewport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index 1fe080f73fd8..fba1fd1619ff 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -281,7 +281,7 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy { let shouldScroll = true; if (options.lazy) { const currentOffset = this.measureScrollOffset(); - const currentOffsetEnd = currentOffset + this.measureRenderedContentSize(); + const currentOffsetEnd = currentOffset + this.getViewportSize(); shouldScroll = offset < currentOffset || offset > currentOffsetEnd; } From 017a1310bf13493b9b1f972474af0a8206a1a05e Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 31 May 2018 14:35:51 -0500 Subject: [PATCH 03/11] chore: pr nit --- src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts | 2 +- src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts | 2 +- src/cdk-experimental/scrolling/virtual-scroll-strategy.ts | 2 +- src/cdk-experimental/scrolling/virtual-scroll-viewport.ts | 2 +- src/cdk/platform/features.ts | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index 253128417a3d..4a7d512de751 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -152,7 +152,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { } /** Get the offset for the given index. */ - getOffsetForIndex(index: number) { + getScrollOffsetForIndex(index: number) { return index * this._averager.getAverageItemSize(); } diff --git a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts index 958dc978dd73..56233b002d7e 100644 --- a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts @@ -78,7 +78,7 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { onRenderedOffsetChanged() { /* no-op */ } /** Get the offset for the given index. */ - getOffsetForIndex(index: number) { + getScrollOffsetForIndex(index: number) { return index * this._itemSize; } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts index 4b59f18885b4..8867e261f7e8 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts @@ -39,5 +39,5 @@ export interface VirtualScrollStrategy { onRenderedOffsetChanged(); /** Get the offset for the given index. */ - getOffsetForIndex(index: number); + getScrollOffsetForIndex(index: number); } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index fba1fd1619ff..3c760ebe70a8 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -298,7 +298,7 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy { /** Scroll the viewport to the specified index. */ scrollToIndex(index: number, options = { smooth: false, lazy: false }) { const contentSize = this.measureRenderedContentSize(); - const offset = this._scrollStrategy.getOffsetForIndex(index); + const offset = this._scrollStrategy.getScrollOffsetForIndex(index); this.scrollToOffset(offset, options); } diff --git a/src/cdk/platform/features.ts b/src/cdk/platform/features.ts index f72bc4929dec..068377594407 100644 --- a/src/cdk/platform/features.ts +++ b/src/cdk/platform/features.ts @@ -28,7 +28,7 @@ export function supportsPassiveEventListeners(): boolean { } /** Cached result of whether the user's browser supports scroll behaviors. */ -let scrollSupport = true; +let scrollSupport: boolean; /** * Check whether the browser supports scroll behaviors. @@ -38,6 +38,7 @@ export function supportsSmoothScroll(): boolean { if (scrollSupport == null && typeof window !== 'undefined') { try { window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); + scrollSupport = true; } catch { scrollSupport = false; } From 2527306339cee7dd6e5d054fb7234614b4d381a7 Mon Sep 17 00:00:00 2001 From: Austin Date: Sun, 3 Jun 2018 11:21:10 -0500 Subject: [PATCH 04/11] chore: throw exception for offset getter --- src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index 4a7d512de751..61caed42c2ab 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -152,8 +152,8 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { } /** Get the offset for the given index. */ - getScrollOffsetForIndex(index: number) { - return index * this._averager.getAverageItemSize(); + getScrollOffsetForIndex() { + throw new Error('Offset location is not supported with autosize.'); } /** From 05c29b96f03f51c57ec0cf863fcf0df9558cb26d Mon Sep 17 00:00:00 2001 From: Austin Date: Sun, 10 Jun 2018 09:59:38 -0500 Subject: [PATCH 05/11] chore: better checks for scroll smooth --- .../scrolling/virtual-scroll-viewport.ts | 9 ++++++--- src/cdk/platform/features.ts | 15 +-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index 3c760ebe70a8..808a5109aad8 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -286,11 +286,14 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy { } if (shouldScroll) { - const smooth = options.smooth && supportsSmoothScroll(); - if (smooth) { + if (options.smooth && supportsSmoothScroll()) { viewportElement.scrollTo({ left, top, behavior: 'smooth' }); } else { - viewportElement.scrollTo(left, top); + if (this.orientation === 'vertical') { + viewportElement.scrollTop = top; + } else { + viewportElement.scrollLeft = left; + } } } } diff --git a/src/cdk/platform/features.ts b/src/cdk/platform/features.ts index 068377594407..0a59e392d0e1 100644 --- a/src/cdk/platform/features.ts +++ b/src/cdk/platform/features.ts @@ -27,24 +27,11 @@ export function supportsPassiveEventListeners(): boolean { return supportsPassiveEvents; } -/** Cached result of whether the user's browser supports scroll behaviors. */ -let scrollSupport: boolean; - /** * Check whether the browser supports scroll behaviors. - * https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo */ export function supportsSmoothScroll(): boolean { - if (scrollSupport == null && typeof window !== 'undefined') { - try { - window.scrollTo({ left: 0, top: 0, behavior: 'smooth' }); - scrollSupport = true; - } catch { - scrollSupport = false; - } - } - - return scrollSupport; + return 'scrollBehavior' in document.documentElement.style; } /** Cached result Set of input types support by the current browser. */ From 38487f7fbe3dcfe4d3391de72301802986cddb7c Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 18 Jul 2018 14:12:02 -0700 Subject: [PATCH 06/11] tweak API slightly and add demo --- .../scrolling/auto-size-virtual-scroll.ts | 8 +++-- .../scrolling/fixed-size-virtual-scroll.ts | 8 +++-- .../scrolling/virtual-scroll-strategy.ts | 4 +-- .../scrolling/virtual-scroll-viewport.spec.ts | 15 ++++++++ .../scrolling/virtual-scroll-viewport.ts | 34 ++++++------------- src/cdk/platform/features.ts | 6 ++-- .../virtual-scroll/virtual-scroll-demo.html | 25 +++++++++++++- .../virtual-scroll/virtual-scroll-demo.ts | 3 ++ 8 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index d6820f9dc6f7..6093e189fb60 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -151,9 +151,11 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { } } - /** Get the offset for the given index. */ - getScrollOffsetForIndex() { - throw new Error('Offset location is not supported with autosize.'); + /** Scroll to the offset for the given index. */ + scrollToIndex() { + // TODO(mmalerba): Implement. + throw new Error('cdk-virtual-scroll: scrollToIndex is currently not supported for the autosize' + + ' scroll strategy'); } /** diff --git a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts index 91b22c374cfc..e93b629e31c2 100644 --- a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts @@ -77,9 +77,11 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { /** @docs-private Implemented as part of VirtualScrollStrategy. */ onRenderedOffsetChanged() { /* no-op */ } - /** Get the offset for the given index. */ - getScrollOffsetForIndex(index: number) { - return index * this._itemSize; + /** Scroll to the offset for the given index. */ + scrollToIndex(index: number, behavior: ScrollBehavior) { + if (this._viewport) { + return this._viewport.scrollToOffset(index * this._itemSize, behavior); + } } /** Update the viewport's total content size. */ diff --git a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts index 8867e261f7e8..9e31a1d1391f 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts @@ -38,6 +38,6 @@ export interface VirtualScrollStrategy { /** Called when the offset of the rendered items changed. */ onRenderedOffsetChanged(); - /** Get the offset for the given index. */ - getScrollOffsetForIndex(index: number); + /** Scroll to the offset for the given index. */ + scrollToIndex(index: number, behavior: ScrollBehavior); } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index 7e0240981a64..c499b05c47c2 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -170,7 +170,22 @@ describe('CdkVirtualScrollViewport', () => { it('should scroll to offset', fakeAsync(() => { finishInit(fixture); viewport.scrollToOffset(testComponent.itemSize * 2); + + triggerScroll(viewport); fixture.detectChanges(); + flush(); + + expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); + expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); + })); + + it('should scroll to index', fakeAsync(() => { + finishInit(fixture); + viewport.scrollToIndex(2); + + triggerScroll(viewport); + fixture.detectChanges(); + flush(); expect(viewport.elementRef.nativeElement.scrollTop).toBe(testComponent.itemSize * 2); expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index cad9ee53cd62..77bbbde7c5aa 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -7,6 +7,7 @@ */ import {ListRange} from '@angular/cdk/collections'; +import {supportsScrollBehavior} from '@angular/cdk/platform'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -25,7 +26,6 @@ import {animationFrameScheduler, fromEvent, Observable, Subject} from 'rxjs'; import {sampleTime, take, takeUntil} from 'rxjs/operators'; import {CdkVirtualForOf} from './virtual-for-of'; import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy'; -import { supportsSmoothScroll } from '@angular/cdk/platform'; /** Checks if the given ranges are equal. */ @@ -248,36 +248,24 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { } /** Scrolls to the offset on the viewport. */ - scrollToOffset(offset: number, options = { smooth: false, lazy: false }) { + scrollToOffset(offset: number, behavior: ScrollBehavior = 'auto') { const viewportElement = this.elementRef.nativeElement; - const top = this.orientation === 'vertical' ? offset : 0; - const left = this.orientation === 'horizontal' ? offset : 0; - - let shouldScroll = true; - if (options.lazy) { - const currentOffset = this.measureScrollOffset(); - const currentOffsetEnd = currentOffset + this.getViewportSize(); - shouldScroll = offset < currentOffset || offset > currentOffsetEnd; - } + const offsetDirection = this.orientation === 'horizontal' ? 'left' : 'top'; - if (shouldScroll) { - if (options.smooth && supportsSmoothScroll()) { - viewportElement.scrollTo({ left, top, behavior: 'smooth' }); + if (supportsScrollBehavior()) { + viewportElement.scrollTo({[offsetDirection]: offset, behavior}); + } else { + if (this.orientation === 'horizontal') { + viewportElement.scrollLeft = offset; } else { - if (this.orientation === 'vertical') { - viewportElement.scrollTop = top; - } else { - viewportElement.scrollLeft = left; - } + viewportElement.scrollTop = offset; } } } /** Scroll the viewport to the specified index. */ - scrollToIndex(index: number, options = { smooth: false, lazy: false }) { - const contentSize = this.measureRenderedContentSize(); - const offset = this._scrollStrategy.getScrollOffsetForIndex(index); - this.scrollToOffset(offset, options); + scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') { + this._scrollStrategy.scrollToIndex(index, behavior); } /** Internal method to set the scroll offset on the viewport. */ diff --git a/src/cdk/platform/features.ts b/src/cdk/platform/features.ts index 0a59e392d0e1..d8a0104ec79b 100644 --- a/src/cdk/platform/features.ts +++ b/src/cdk/platform/features.ts @@ -27,10 +27,8 @@ export function supportsPassiveEventListeners(): boolean { return supportsPassiveEvents; } -/** - * Check whether the browser supports scroll behaviors. - */ -export function supportsSmoothScroll(): boolean { +/** Check whether the browser supports scroll behaviors. */ +export function supportsScrollBehavior(): boolean { return 'scrollBehavior' in document.documentElement.style; } diff --git a/src/demo-app/virtual-scroll/virtual-scroll-demo.html b/src/demo-app/virtual-scroll/virtual-scroll-demo.html index 7c8b754eee04..a8139dc85670 100644 --- a/src/demo-app/virtual-scroll/virtual-scroll-demo.html +++ b/src/demo-app/virtual-scroll/virtual-scroll-demo.html @@ -34,7 +34,30 @@

Random size

Fixed size

- + + Behavior + + Auto + Instant + Smooth + + + + Offset + + + + + Index + + + + +
Item #{{i}} - ({{size}}px) diff --git a/src/demo-app/virtual-scroll/virtual-scroll-demo.ts b/src/demo-app/virtual-scroll/virtual-scroll-demo.ts index 8aff47670d97..54af3dcfe1d0 100644 --- a/src/demo-app/virtual-scroll/virtual-scroll-demo.ts +++ b/src/demo-app/virtual-scroll/virtual-scroll-demo.ts @@ -24,6 +24,9 @@ type State = { encapsulation: ViewEncapsulation.None, }) export class VirtualScrollDemo { + scrollToOffset = 0; + scrollToIndex = 0; + scrollToBehavior = 'auto'; fixedSizeData = Array(10000).fill(50); increasingSizeData = Array(10000).fill(0).map((_, i) => (1 + Math.floor(i / 1000)) * 20); decreasingSizeData = Array(10000).fill(0) From 91bcd5845785dcc1275a14d2ade3454bfe29f5c5 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 18 Jul 2018 14:25:55 -0700 Subject: [PATCH 07/11] update BUILD file --- src/cdk-experimental/scrolling/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cdk-experimental/scrolling/BUILD.bazel b/src/cdk-experimental/scrolling/BUILD.bazel index 9fdbbb7b5752..054f9f5aae51 100644 --- a/src/cdk-experimental/scrolling/BUILD.bazel +++ b/src/cdk-experimental/scrolling/BUILD.bazel @@ -12,6 +12,7 @@ ng_module( deps = [ "//src/cdk/coercion", "//src/cdk/collections", + "//src/cdk/platform", "@rxjs", ], tsconfig = "//src/cdk-experimental:tsconfig-build.json", From 09473814d5dfbe9baa96a195c8296ed14cc53f07 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 18 Jul 2018 15:49:49 -0700 Subject: [PATCH 08/11] fix duplicate template ref name --- src/demo-app/virtual-scroll/virtual-scroll-demo.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/demo-app/virtual-scroll/virtual-scroll-demo.html b/src/demo-app/virtual-scroll/virtual-scroll-demo.html index 7d200b2b4231..30b12deb7f8a 100644 --- a/src/demo-app/virtual-scroll/virtual-scroll-demo.html +++ b/src/demo-app/virtual-scroll/virtual-scroll-demo.html @@ -46,18 +46,18 @@

Fixed size

Offset - Index - - +
Item #{{i}} - ({{size}}px) @@ -120,8 +120,8 @@

trackBy state name

Use with <ol>

- -
    + +
    1. {{state.name}} - {{state.capital}}
    2. From 2f47b3d225d7483896f6498ec7cdbac5dde884c1 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 18 Jul 2018 17:21:00 -0700 Subject: [PATCH 09/11] add output for scrolledIndexChange --- .../scrolling/auto-size-virtual-scroll.ts | 8 +++++++ .../scrolling/fixed-size-virtual-scroll.ts | 10 +++++++++ .../scrolling/virtual-scroll-strategy.ts | 6 +++++- .../scrolling/virtual-scroll-viewport.ts | 21 +++++++++++++++++-- .../virtual-scroll/virtual-scroll-demo.html | 6 +++++- .../virtual-scroll/virtual-scroll-demo.ts | 6 ++++++ 6 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index 6093e189fb60..b92588ac0701 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -9,6 +9,7 @@ import {coerceNumberProperty} from '@angular/cdk/coercion'; import {ListRange} from '@angular/cdk/collections'; import {Directive, forwardRef, Input, OnChanges} from '@angular/core'; +import {Observable} from 'rxjs'; import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy'; import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; @@ -65,6 +66,13 @@ export class ItemSizeAverager { /** Virtual scrolling strategy for lists with items of unknown or dynamic size. */ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { + /** @docs-private Implemented as part of VirtualScrollStrategy. */ + scrolledIndexChange = Observable.create(() => { + // TODO(mmalerba): Implement. + throw new Error('cdk-virtual-scroll: scrolledIndexChange is currently not supported for the' + + ' autosize scroll strategy'); + }); + /** The attached viewport. */ private _viewport: CdkVirtualScrollViewport | null = null; diff --git a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts index e93b629e31c2..201645e71042 100644 --- a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts @@ -9,12 +9,19 @@ import {coerceNumberProperty} from '@angular/cdk/coercion'; import {ListRange} from '@angular/cdk/collections'; 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(); + + /** @docs-private Implemented as part of VirtualScrollStrategy. */ + scrolledIndexChange: Observable = this._scrolledIndexChange.pipe(distinctUntilChanged()); + /** The attached viewport. */ private _viewport: CdkVirtualScrollViewport | null = null; @@ -45,6 +52,7 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { /** Detaches this scroll strategy from the currently attached viewport. */ detach() { + this._scrolledIndexChange.complete(); this._viewport = null; } @@ -109,6 +117,8 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { this._bufferSize); this._viewport.setRenderedRange(range); this._viewport.setRenderedContentOffset(this._itemSize * range.start); + + this._scrolledIndexChange.next(firstVisibleIndex); } /** diff --git a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts index 9e31a1d1391f..49440fe50e6f 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts @@ -6,8 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; import {InjectionToken} from '@angular/core'; +import {Observable} from 'rxjs'; +import {CdkVirtualScrollViewport} from './virtual-scroll-viewport'; /** The injection token used to specify the virtual scrolling strategy. */ @@ -17,6 +18,9 @@ export const VIRTUAL_SCROLL_STRATEGY = /** A strategy that dictates which items should be rendered in the viewport. */ export interface VirtualScrollStrategy { + /** Emits when the index of the first element visible in the viewport changes. */ + scrolledIndexChange: Observable; + /** * Attaches this scroll strategy to a viewport. * @param viewport The viewport to attach this strategy to. diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index 989b026513f8..11c765a12c3a 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -18,11 +18,12 @@ import { NgZone, OnDestroy, OnInit, + Output, ViewChild, ViewEncapsulation, } from '@angular/core'; import {animationFrameScheduler, fromEvent, Observable, Subject} from 'rxjs'; -import {sampleTime, take, takeUntil} from 'rxjs/operators'; +import {sample, sampleTime, take, takeUntil} from 'rxjs/operators'; import {CdkVirtualForOf} from './virtual-for-of'; import {VIRTUAL_SCROLL_STRATEGY, VirtualScrollStrategy} from './virtual-scroll-strategy'; @@ -54,9 +55,22 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { /** Emits when the rendered range changes. */ private _renderedRangeSubject = new Subject(); + /** Emits when a change detection cycle completes. */ + private _changeDetectionComplete = new Subject(); + /** The direction the viewport scrolls. */ @Input() orientation: 'horizontal' | 'vertical' = 'vertical'; + // Note: we don't use the typical EventEmitter here because we need to subscribe to the scroll + // strategy lazily (i.e. only if the user is actually listening to the events). We do this because + // depending on how the strategy calculates the scrolled index, it may come at a cost to + // performance. + /** Emits when the index of the first element visible in the viewport changes. */ + @Output() scrolledIndexChange = + Observable.create(observer => this._scrollStrategy.scrolledIndexChange + .pipe(sample(this._changeDetectionComplete)) + .subscribe(observer)); + /** The element that wraps the rendered content. */ @ViewChild('contentWrapper') _contentWrapper: ElementRef; @@ -109,7 +123,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { constructor(public elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, - private _ngZone: NgZone, + public _ngZone: NgZone, @Inject(VIRTUAL_SCROLL_STRATEGY) private _scrollStrategy: VirtualScrollStrategy) {} ngOnInit() { @@ -139,6 +153,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { // Complete all subjects this._renderedRangeSubject.complete(); this._detachedSubject.complete(); + this._changeDetectionComplete.complete(); this._destroyed.complete(); } @@ -356,5 +371,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { fn(); } this._runAfterChangeDetection = []; + + this._ngZone.run(() => this._changeDetectionComplete.next()); } } diff --git a/src/demo-app/virtual-scroll/virtual-scroll-demo.html b/src/demo-app/virtual-scroll/virtual-scroll-demo.html index 30b12deb7f8a..0ce50761f34e 100644 --- a/src/demo-app/virtual-scroll/virtual-scroll-demo.html +++ b/src/demo-app/virtual-scroll/virtual-scroll-demo.html @@ -56,8 +56,12 @@

      Fixed size

      +

      + Currently scrolled to item: {{scrolledIndex}} +

      - +
      Item #{{i}} - ({{size}}px) diff --git a/src/demo-app/virtual-scroll/virtual-scroll-demo.ts b/src/demo-app/virtual-scroll/virtual-scroll-demo.ts index 54af3dcfe1d0..7cdc5d02b4c9 100644 --- a/src/demo-app/virtual-scroll/virtual-scroll-demo.ts +++ b/src/demo-app/virtual-scroll/virtual-scroll-demo.ts @@ -27,6 +27,7 @@ export class VirtualScrollDemo { scrollToOffset = 0; scrollToIndex = 0; scrollToBehavior = 'auto'; + scrolledIndex = 0; fixedSizeData = Array(10000).fill(50); increasingSizeData = Array(10000).fill(0).map((_, i) => (1 + Math.floor(i / 1000)) * 20); decreasingSizeData = Array(10000).fill(0) @@ -109,4 +110,9 @@ export class VirtualScrollDemo { return 0; })); } + + scrolled(index: number) { + console.log(index); + this.scrolledIndex = index; + } } From b6dd29dad02e898bca7e9b6beeba56190bb9b9ab Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Wed, 25 Jul 2018 15:39:51 -0700 Subject: [PATCH 10/11] add test --- .../scrolling/virtual-scroll-viewport.spec.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts index 5614b8a30a97..eb0454df5cd3 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.spec.ts @@ -206,6 +206,21 @@ describe('CdkVirtualScrollViewport', () => { expect(viewport.getRenderedRange()).toEqual({start: 2, end: 6}); })); + it('should output scrolled index', fakeAsync(() => { + finishInit(fixture); + triggerScroll(viewport, testComponent.itemSize * 2 - 1); + fixture.detectChanges(); + flush(); + + expect(testComponent.scrolledToIndex).toBe(1); + + triggerScroll(viewport, testComponent.itemSize * 2); + fixture.detectChanges(); + flush(); + + expect(testComponent.scrolledToIndex).toBe(2); + })); + it('should update viewport as user scrolls down', fakeAsync(() => { finishInit(fixture); @@ -608,7 +623,8 @@ function triggerScroll(viewport: CdkVirtualScrollViewport, offset?: number) { template: ` + [style.height.px]="viewportHeight" [style.width.px]="viewportWidth" + (scrolledIndexChange)="scrolledToIndex = $event">
      Date: Thu, 26 Jul 2018 09:49:34 -0700 Subject: [PATCH 11/11] address comments --- src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts | 4 ++-- src/cdk-experimental/scrolling/virtual-scroll-viewport.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index 35fdb56eb22b..b42c8062dae5 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -69,7 +69,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { /** @docs-private Implemented as part of VirtualScrollStrategy. */ scrolledIndexChange = Observable.create(() => { // TODO(mmalerba): Implement. - throw new Error('cdk-virtual-scroll: scrolledIndexChange is currently not supported for the' + + throw Error('cdk-virtual-scroll: scrolledIndexChange is currently not supported for the' + ' autosize scroll strategy'); }); @@ -162,7 +162,7 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { /** Scroll to the offset for the given index. */ scrollToIndex(): void { // TODO(mmalerba): Implement. - throw new Error('cdk-virtual-scroll: scrollToIndex is currently not supported for the autosize' + throw Error('cdk-virtual-scroll: scrollToIndex is currently not supported for the autosize' + ' scroll strategy'); } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index 3ae96e252e64..2b81861a95a4 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -66,7 +66,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { // depending on how the strategy calculates the scrolled index, it may come at a cost to // performance. /** Emits when the index of the first element visible in the viewport changes. */ - @Output() scrolledIndexChange = + @Output() scrolledIndexChange: Observable = Observable.create(observer => this._scrollStrategy.scrolledIndexChange .pipe(sample(this._changeDetectionComplete)) .subscribe(observer));