| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850 |
- /**
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
- import { InjectionToken, Directive, forwardRef, Input, Injectable, NgZone, Optional, SkipSelf, ElementRef, NgModule, IterableDiffers, TemplateRef, ViewContainerRef, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Output, ViewChild, ViewEncapsulation, ɵɵdefineInjectable, ɵɵinject } from '@angular/core';
- import { coerceNumberProperty } from '@angular/cdk/coercion';
- import { Subject, fromEvent, of, Observable, animationFrameScheduler, asapScheduler, merge } from 'rxjs';
- import { distinctUntilChanged, auditTime, filter, takeUntil, startWith, pairwise, shareReplay, switchMap } from 'rxjs/operators';
- import { Platform, getRtlScrollAxisType, RtlScrollAxisType, supportsScrollBehavior, PlatformModule } from '@angular/cdk/platform';
- import { Directionality, BidiModule } from '@angular/cdk/bidi';
- import { ArrayDataSource, isDataSource } from '@angular/cdk/collections';
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * The injection token used to specify the virtual scrolling strategy.
- * @type {?}
- */
- const VIRTUAL_SCROLL_STRATEGY = new InjectionToken('VIRTUAL_SCROLL_STRATEGY');
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * Virtual scrolling strategy for lists with items of known fixed size.
- */
- class FixedSizeVirtualScrollStrategy {
- /**
- * @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, minBufferPx, maxBufferPx) {
- this._scrolledIndexChange = new Subject();
- /**
- * \@docs-private Implemented as part of VirtualScrollStrategy.
- */
- this.scrolledIndexChange = this._scrolledIndexChange.pipe(distinctUntilChanged());
- /**
- * The attached viewport.
- */
- this._viewport = null;
- 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.
- * @return {?}
- */
- attach(viewport) {
- this._viewport = viewport;
- this._updateTotalContentSize();
- this._updateRenderedRange();
- }
- /**
- * Detaches this scroll strategy from the currently attached viewport.
- * @return {?}
- */
- 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.
- * @return {?}
- */
- 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 Implemented as part of VirtualScrollStrategy.
- * @return {?}
- */
- onContentScrolled() {
- this._updateRenderedRange();
- }
- /**
- * \@docs-private Implemented as part of VirtualScrollStrategy.
- * @return {?}
- */
- onDataLengthChanged() {
- this._updateTotalContentSize();
- this._updateRenderedRange();
- }
- /**
- * \@docs-private Implemented as part of VirtualScrollStrategy.
- * @return {?}
- */
- onContentRendered() { }
- /**
- * \@docs-private Implemented as part of VirtualScrollStrategy.
- * @return {?}
- */
- onRenderedOffsetChanged() { }
- /**
- * 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.
- * @return {?}
- */
- scrollToIndex(index, behavior) {
- if (this._viewport) {
- this._viewport.scrollToOffset(index * this._itemSize, behavior);
- }
- }
- /**
- * Update the viewport's total content size.
- * @private
- * @return {?}
- */
- _updateTotalContentSize() {
- if (!this._viewport) {
- return;
- }
- this._viewport.setTotalContentSize(this._viewport.getDataLength() * this._itemSize);
- }
- /**
- * Update the viewport's rendered range.
- * @private
- * @return {?}
- */
- _updateRenderedRange() {
- if (!this._viewport) {
- return;
- }
- /** @type {?} */
- const scrollOffset = this._viewport.measureScrollOffset();
- /** @type {?} */
- const firstVisibleIndex = scrollOffset / this._itemSize;
- /** @type {?} */
- const renderedRange = this._viewport.getRenderedRange();
- /** @type {?} */
- const newRange = { start: renderedRange.start, end: renderedRange.end };
- /** @type {?} */
- const viewportSize = this._viewport.getViewportSize();
- /** @type {?} */
- const dataLength = this._viewport.getDataLength();
- /** @type {?} */
- const startBuffer = scrollOffset - newRange.start * this._itemSize;
- if (startBuffer < this._minBufferPx && newRange.start != 0) {
- /** @type {?} */
- 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 {
- /** @type {?} */
- const endBuffer = newRange.end * this._itemSize - (scrollOffset + viewportSize);
- if (endBuffer < this._minBufferPx && newRange.end != dataLength) {
- /** @type {?} */
- 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.
- * @return {?}
- */
- function _fixedSizeVirtualScrollStrategyFactory(fixedSizeDir) {
- return fixedSizeDir._scrollStrategy;
- }
- /**
- * A virtual scroll strategy that supports fixed-size items.
- */
- class CdkFixedSizeVirtualScroll {
- constructor() {
- this._itemSize = 20;
- this._minBufferPx = 100;
- this._maxBufferPx = 200;
- /**
- * The scroll strategy used by this directive.
- */
- this._scrollStrategy = new FixedSizeVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx);
- }
- /**
- * The size of the items in the list (in pixels).
- * @return {?}
- */
- get itemSize() { return this._itemSize; }
- /**
- * @param {?} value
- * @return {?}
- */
- set itemSize(value) { this._itemSize = coerceNumberProperty(value); }
- /**
- * 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.
- * @return {?}
- */
- get minBufferPx() { return this._minBufferPx; }
- /**
- * @param {?} value
- * @return {?}
- */
- set minBufferPx(value) { this._minBufferPx = coerceNumberProperty(value); }
- /**
- * The number of pixels worth of buffer to render for when rendering new items. Defaults to 200px.
- * @return {?}
- */
- get maxBufferPx() { return this._maxBufferPx; }
- /**
- * @param {?} value
- * @return {?}
- */
- set maxBufferPx(value) { this._maxBufferPx = coerceNumberProperty(value); }
- /**
- * @return {?}
- */
- ngOnChanges() {
- this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
- }
- }
- CdkFixedSizeVirtualScroll.decorators = [
- { type: Directive, args: [{
- selector: 'cdk-virtual-scroll-viewport[itemSize]',
- providers: [{
- provide: VIRTUAL_SCROLL_STRATEGY,
- useFactory: _fixedSizeVirtualScrollStrategyFactory,
- deps: [forwardRef((/**
- * @return {?}
- */
- () => CdkFixedSizeVirtualScroll))],
- }],
- },] },
- ];
- CdkFixedSizeVirtualScroll.propDecorators = {
- itemSize: [{ type: Input }],
- minBufferPx: [{ type: Input }],
- maxBufferPx: [{ type: Input }]
- };
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * Time in ms to throttle the scrolling events by default.
- * @type {?}
- */
- const DEFAULT_SCROLL_TIME = 20;
- /**
- * Service contained all registered Scrollable references and emits an event when any one of the
- * Scrollable references emit a scrolled event.
- */
- class ScrollDispatcher {
- /**
- * @param {?} _ngZone
- * @param {?} _platform
- */
- constructor(_ngZone, _platform) {
- this._ngZone = _ngZone;
- this._platform = _platform;
- /**
- * Subject for notifying that a registered scrollable reference element has been scrolled.
- */
- this._scrolled = new Subject();
- /**
- * Keeps track of the global `scroll` and `resize` subscriptions.
- */
- this._globalSubscription = null;
- /**
- * Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards.
- */
- this._scrolledCount = 0;
- /**
- * Map of all the scrollable references that are registered with the service and their
- * scroll event subscriptions.
- */
- this.scrollContainers = new Map();
- }
- /**
- * Registers a scrollable instance with the service and listens for its scrolled events. When the
- * scrollable is scrolled, the service emits the event to its scrolled observable.
- * @param {?} scrollable Scrollable instance to be registered.
- * @return {?}
- */
- register(scrollable) {
- if (!this.scrollContainers.has(scrollable)) {
- this.scrollContainers.set(scrollable, scrollable.elementScrolled()
- .subscribe((/**
- * @return {?}
- */
- () => this._scrolled.next(scrollable))));
- }
- }
- /**
- * Deregisters a Scrollable reference and unsubscribes from its scroll event observable.
- * @param {?} scrollable Scrollable instance to be deregistered.
- * @return {?}
- */
- deregister(scrollable) {
- /** @type {?} */
- const scrollableReference = this.scrollContainers.get(scrollable);
- if (scrollableReference) {
- scrollableReference.unsubscribe();
- this.scrollContainers.delete(scrollable);
- }
- }
- /**
- * Returns an observable that emits an event whenever any of the registered Scrollable
- * references (or window, document, or body) fire a scrolled event. Can provide a time in ms
- * to override the default "throttle" time.
- *
- * **Note:** in order to avoid hitting change detection for every scroll event,
- * all of the events emitted from this stream will be run outside the Angular zone.
- * If you need to update any data bindings as a result of a scroll event, you have
- * to run the callback using `NgZone.run`.
- * @param {?=} auditTimeInMs
- * @return {?}
- */
- scrolled(auditTimeInMs = DEFAULT_SCROLL_TIME) {
- if (!this._platform.isBrowser) {
- return of();
- }
- return new Observable((/**
- * @param {?} observer
- * @return {?}
- */
- (observer) => {
- if (!this._globalSubscription) {
- this._addGlobalListener();
- }
- // In the case of a 0ms delay, use an observable without auditTime
- // since it does add a perceptible delay in processing overhead.
- /** @type {?} */
- const subscription = auditTimeInMs > 0 ?
- this._scrolled.pipe(auditTime(auditTimeInMs)).subscribe(observer) :
- this._scrolled.subscribe(observer);
- this._scrolledCount++;
- return (/**
- * @return {?}
- */
- () => {
- subscription.unsubscribe();
- this._scrolledCount--;
- if (!this._scrolledCount) {
- this._removeGlobalListener();
- }
- });
- }));
- }
- /**
- * @return {?}
- */
- ngOnDestroy() {
- this._removeGlobalListener();
- this.scrollContainers.forEach((/**
- * @param {?} _
- * @param {?} container
- * @return {?}
- */
- (_, container) => this.deregister(container)));
- this._scrolled.complete();
- }
- /**
- * Returns an observable that emits whenever any of the
- * scrollable ancestors of an element are scrolled.
- * @param {?} elementRef Element whose ancestors to listen for.
- * @param {?=} auditTimeInMs Time to throttle the scroll events.
- * @return {?}
- */
- ancestorScrolled(elementRef, auditTimeInMs) {
- /** @type {?} */
- const ancestors = this.getAncestorScrollContainers(elementRef);
- return this.scrolled(auditTimeInMs).pipe(filter((/**
- * @param {?} target
- * @return {?}
- */
- target => {
- return !target || ancestors.indexOf(target) > -1;
- })));
- }
- /**
- * Returns all registered Scrollables that contain the provided element.
- * @param {?} elementRef
- * @return {?}
- */
- getAncestorScrollContainers(elementRef) {
- /** @type {?} */
- const scrollingContainers = [];
- this.scrollContainers.forEach((/**
- * @param {?} _subscription
- * @param {?} scrollable
- * @return {?}
- */
- (_subscription, scrollable) => {
- if (this._scrollableContainsElement(scrollable, elementRef)) {
- scrollingContainers.push(scrollable);
- }
- }));
- return scrollingContainers;
- }
- /**
- * Returns true if the element is contained within the provided Scrollable.
- * @private
- * @param {?} scrollable
- * @param {?} elementRef
- * @return {?}
- */
- _scrollableContainsElement(scrollable, elementRef) {
- /** @type {?} */
- let element = elementRef.nativeElement;
- /** @type {?} */
- 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 = (/** @type {?} */ (element)).parentElement);
- return false;
- }
- /**
- * Sets up the global scroll listeners.
- * @private
- * @return {?}
- */
- _addGlobalListener() {
- this._globalSubscription = this._ngZone.runOutsideAngular((/**
- * @return {?}
- */
- () => {
- return fromEvent(window.document, 'scroll').subscribe((/**
- * @return {?}
- */
- () => this._scrolled.next()));
- }));
- }
- /**
- * Cleans up the global scroll listener.
- * @private
- * @return {?}
- */
- _removeGlobalListener() {
- if (this._globalSubscription) {
- this._globalSubscription.unsubscribe();
- this._globalSubscription = null;
- }
- }
- }
- ScrollDispatcher.decorators = [
- { type: Injectable, args: [{ providedIn: 'root' },] },
- ];
- /** @nocollapse */
- ScrollDispatcher.ctorParameters = () => [
- { type: NgZone },
- { type: Platform }
- ];
- /** @nocollapse */ ScrollDispatcher.ngInjectableDef = ɵɵdefineInjectable({ factory: function ScrollDispatcher_Factory() { return new ScrollDispatcher(ɵɵinject(NgZone), ɵɵinject(Platform)); }, token: ScrollDispatcher, providedIn: "root" });
- /**
- * \@docs-private \@deprecated \@breaking-change 8.0.0
- * @param {?} parentDispatcher
- * @param {?} ngZone
- * @param {?} platform
- * @return {?}
- */
- function SCROLL_DISPATCHER_PROVIDER_FACTORY(parentDispatcher, ngZone, platform) {
- return parentDispatcher || new ScrollDispatcher(ngZone, platform);
- }
- /**
- * \@docs-private \@deprecated \@breaking-change 8.0.0
- * @type {?}
- */
- const SCROLL_DISPATCHER_PROVIDER = {
- // If there is already a ScrollDispatcher available, use that. Otherwise, provide a new one.
- provide: ScrollDispatcher,
- deps: [[new Optional(), new SkipSelf(), ScrollDispatcher], NgZone, Platform],
- useFactory: SCROLL_DISPATCHER_PROVIDER_FACTORY
- };
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * Sends an event when the directive's element is scrolled. Registers itself with the
- * ScrollDispatcher service to include itself as part of its collection of scrolling events that it
- * can be listened to through the service.
- */
- class CdkScrollable {
- /**
- * @param {?} elementRef
- * @param {?} scrollDispatcher
- * @param {?} ngZone
- * @param {?=} dir
- */
- constructor(elementRef, scrollDispatcher, ngZone, dir) {
- this.elementRef = elementRef;
- this.scrollDispatcher = scrollDispatcher;
- this.ngZone = ngZone;
- this.dir = dir;
- this._destroyed = new Subject();
- this._elementScrolled = new Observable((/**
- * @param {?} observer
- * @return {?}
- */
- (observer) => this.ngZone.runOutsideAngular((/**
- * @return {?}
- */
- () => fromEvent(this.elementRef.nativeElement, 'scroll').pipe(takeUntil(this._destroyed))
- .subscribe(observer)))));
- }
- /**
- * @return {?}
- */
- ngOnInit() {
- this.scrollDispatcher.register(this);
- }
- /**
- * @return {?}
- */
- ngOnDestroy() {
- this.scrollDispatcher.deregister(this);
- this._destroyed.next();
- this._destroyed.complete();
- }
- /**
- * Returns observable that emits when a scroll event is fired on the host element.
- * @return {?}
- */
- elementScrolled() {
- return this._elementScrolled;
- }
- /**
- * Gets the ElementRef for the viewport.
- * @return {?}
- */
- getElementRef() {
- return this.elementRef;
- }
- /**
- * Scrolls to the specified offsets. This is a normalized version of the browser's native scrollTo
- * method, since browsers are not consistent about what scrollLeft means in RTL. For this method
- * left and right always refer to the left and right side of the scrolling container irrespective
- * of the layout direction. start and end refer to left and right in an LTR context and vice-versa
- * in an RTL context.
- * @param {?} options specified the offsets to scroll to.
- * @return {?}
- */
- scrollTo(options) {
- /** @type {?} */
- const el = this.elementRef.nativeElement;
- /** @type {?} */
- const isRtl = this.dir && this.dir.value == 'rtl';
- // Rewrite start & end offsets as right or left offsets.
- options.left = options.left == null ? (isRtl ? options.end : options.start) : options.left;
- options.right = options.right == null ? (isRtl ? options.start : options.end) : options.right;
- // Rewrite the bottom offset as a top offset.
- if (options.bottom != null) {
- ((/** @type {?} */ (options))).top =
- el.scrollHeight - el.clientHeight - options.bottom;
- }
- // Rewrite the right offset as a left offset.
- if (isRtl && getRtlScrollAxisType() != RtlScrollAxisType.NORMAL) {
- if (options.left != null) {
- ((/** @type {?} */ (options))).right =
- el.scrollWidth - el.clientWidth - options.left;
- }
- if (getRtlScrollAxisType() == RtlScrollAxisType.INVERTED) {
- options.left = options.right;
- }
- else if (getRtlScrollAxisType() == RtlScrollAxisType.NEGATED) {
- options.left = options.right ? -options.right : options.right;
- }
- }
- else {
- if (options.right != null) {
- ((/** @type {?} */ (options))).left =
- el.scrollWidth - el.clientWidth - options.right;
- }
- }
- this._applyScrollToOptions(options);
- }
- /**
- * @private
- * @param {?} options
- * @return {?}
- */
- _applyScrollToOptions(options) {
- /** @type {?} */
- const el = this.elementRef.nativeElement;
- if (supportsScrollBehavior()) {
- el.scrollTo(options);
- }
- else {
- if (options.top != null) {
- el.scrollTop = options.top;
- }
- if (options.left != null) {
- el.scrollLeft = options.left;
- }
- }
- }
- /**
- * Measures the scroll offset relative to the specified edge of the viewport. This method can be
- * used instead of directly checking scrollLeft or scrollTop, since browsers are not consistent
- * about what scrollLeft means in RTL. The values returned by this method are normalized such that
- * left and right always refer to the left and right side of the scrolling container irrespective
- * of the layout direction. start and end refer to left and right in an LTR context and vice-versa
- * in an RTL context.
- * @param {?} from The edge to measure from.
- * @return {?}
- */
- measureScrollOffset(from) {
- /** @type {?} */
- const LEFT = 'left';
- /** @type {?} */
- const RIGHT = 'right';
- /** @type {?} */
- const el = this.elementRef.nativeElement;
- if (from == 'top') {
- return el.scrollTop;
- }
- if (from == 'bottom') {
- return el.scrollHeight - el.clientHeight - el.scrollTop;
- }
- // Rewrite start & end as left or right offsets.
- /** @type {?} */
- const isRtl = this.dir && this.dir.value == 'rtl';
- if (from == 'start') {
- from = isRtl ? RIGHT : LEFT;
- }
- else if (from == 'end') {
- from = isRtl ? LEFT : RIGHT;
- }
- if (isRtl && getRtlScrollAxisType() == RtlScrollAxisType.INVERTED) {
- // For INVERTED, scrollLeft is (scrollWidth - clientWidth) when scrolled all the way left and
- // 0 when scrolled all the way right.
- if (from == LEFT) {
- return el.scrollWidth - el.clientWidth - el.scrollLeft;
- }
- else {
- return el.scrollLeft;
- }
- }
- else if (isRtl && getRtlScrollAxisType() == RtlScrollAxisType.NEGATED) {
- // For NEGATED, scrollLeft is -(scrollWidth - clientWidth) when scrolled all the way left and
- // 0 when scrolled all the way right.
- if (from == LEFT) {
- return el.scrollLeft + el.scrollWidth - el.clientWidth;
- }
- else {
- return -el.scrollLeft;
- }
- }
- else {
- // For NORMAL, as well as non-RTL contexts, scrollLeft is 0 when scrolled all the way left and
- // (scrollWidth - clientWidth) when scrolled all the way right.
- if (from == LEFT) {
- return el.scrollLeft;
- }
- else {
- return el.scrollWidth - el.clientWidth - el.scrollLeft;
- }
- }
- }
- }
- CdkScrollable.decorators = [
- { type: Directive, args: [{
- selector: '[cdk-scrollable], [cdkScrollable]'
- },] },
- ];
- /** @nocollapse */
- CdkScrollable.ctorParameters = () => [
- { type: ElementRef },
- { type: ScrollDispatcher },
- { type: NgZone },
- { type: Directionality, decorators: [{ type: Optional }] }
- ];
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * Checks if the given ranges are equal.
- * @param {?} r1
- * @param {?} r2
- * @return {?}
- */
- function rangesEqual(r1, r2) {
- return r1.start == r2.start && r1.end == r2.end;
- }
- /**
- * Scheduler to be used for scroll events. Needs to fall back to
- * something that doesn't rely on requestAnimationFrame on environments
- * that don't support it (e.g. server-side rendering).
- * @type {?}
- */
- const SCROLL_SCHEDULER = typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
- /**
- * A viewport that virtualizes its scrolling with the help of `CdkVirtualForOf`.
- */
- class CdkVirtualScrollViewport extends CdkScrollable {
- /**
- * @param {?} elementRef
- * @param {?} _changeDetectorRef
- * @param {?} ngZone
- * @param {?} _scrollStrategy
- * @param {?} dir
- * @param {?} scrollDispatcher
- */
- constructor(elementRef, _changeDetectorRef, ngZone, _scrollStrategy, dir, scrollDispatcher) {
- super(elementRef, scrollDispatcher, ngZone, dir);
- this.elementRef = elementRef;
- this._changeDetectorRef = _changeDetectorRef;
- this._scrollStrategy = _scrollStrategy;
- /**
- * Emits when the viewport is detached from a CdkVirtualForOf.
- */
- this._detachedSubject = new Subject();
- /**
- * Emits when the rendered range changes.
- */
- this._renderedRangeSubject = new Subject();
- this._orientation = '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.
- */
- this.scrolledIndexChange = new Observable((/**
- * @param {?} observer
- * @return {?}
- */
- (observer) => this._scrollStrategy.scrolledIndexChange.subscribe((/**
- * @param {?} index
- * @return {?}
- */
- index => Promise.resolve().then((/**
- * @return {?}
- */
- () => this.ngZone.run((/**
- * @return {?}
- */
- () => observer.next(index)))))))));
- /**
- * A stream that emits whenever the rendered range changes.
- */
- this.renderedRangeStream = this._renderedRangeSubject.asObservable();
- /**
- * The total size of all content (in pixels), including content that is not currently rendered.
- */
- this._totalContentSize = 0;
- /**
- * A string representing the `style.width` property value to be used for the spacer element.
- */
- this._totalContentWidth = '';
- /**
- * A string representing the `style.height` property value to be used for the spacer element.
- */
- this._totalContentHeight = '';
- /**
- * The currently rendered range of indices.
- */
- this._renderedRange = { start: 0, end: 0 };
- /**
- * The length of the data bound to this viewport (in number of items).
- */
- this._dataLength = 0;
- /**
- * The size of the viewport (in pixels).
- */
- this._viewportSize = 0;
- /**
- * The last rendered content offset that was set.
- */
- this._renderedContentOffset = 0;
- /**
- * Whether the last rendered content offset was to the end of the content (and therefore needs to
- * be rewritten as an offset to the start of the content).
- */
- this._renderedContentOffsetNeedsRewrite = false;
- /**
- * Whether there is a pending change detection cycle.
- */
- this._isChangeDetectionPending = false;
- /**
- * A list of functions to run after the next change detection cycle.
- */
- this._runAfterChangeDetection = [];
- if (!_scrollStrategy) {
- throw Error('Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.');
- }
- }
- /**
- * The direction the viewport scrolls.
- * @return {?}
- */
- get orientation() {
- return this._orientation;
- }
- /**
- * @param {?} orientation
- * @return {?}
- */
- set orientation(orientation) {
- if (this._orientation !== orientation) {
- this._orientation = orientation;
- this._calculateSpacerSize();
- }
- }
- /**
- * @return {?}
- */
- ngOnInit() {
- super.ngOnInit();
- // It's still too early to measure the viewport at this point. Deferring with a promise allows
- // the Viewport to be rendered with the correct size before we measure. We run this outside the
- // zone to avoid causing more change detection cycles. We handle the change detection loop
- // ourselves instead.
- this.ngZone.runOutsideAngular((/**
- * @return {?}
- */
- () => Promise.resolve().then((/**
- * @return {?}
- */
- () => {
- this._measureViewportSize();
- this._scrollStrategy.attach(this);
- this.elementScrolled()
- .pipe(
- // Start off with a fake scroll event so we properly detect our initial position.
- startWith((/** @type {?} */ (null))),
- // Collect multiple events into one until the next animation frame. This way if
- // there are multiple scroll events in the same frame we only need to recheck
- // our layout once.
- auditTime(0, SCROLL_SCHEDULER))
- .subscribe((/**
- * @return {?}
- */
- () => this._scrollStrategy.onContentScrolled()));
- this._markChangeDetectionNeeded();
- }))));
- }
- /**
- * @return {?}
- */
- ngOnDestroy() {
- this.detach();
- this._scrollStrategy.detach();
- // Complete all subjects
- this._renderedRangeSubject.complete();
- this._detachedSubject.complete();
- super.ngOnDestroy();
- }
- /**
- * Attaches a `CdkVirtualForOf` to this viewport.
- * @param {?} forOf
- * @return {?}
- */
- attach(forOf) {
- if (this._forOf) {
- throw Error('CdkVirtualScrollViewport is already attached.');
- }
- // Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length
- // changes. Run outside the zone to avoid triggering change detection, since we're managing the
- // change detection loop ourselves.
- this.ngZone.runOutsideAngular((/**
- * @return {?}
- */
- () => {
- this._forOf = forOf;
- this._forOf.dataStream.pipe(takeUntil(this._detachedSubject)).subscribe((/**
- * @param {?} data
- * @return {?}
- */
- data => {
- /** @type {?} */
- const newLength = data.length;
- if (newLength !== this._dataLength) {
- this._dataLength = newLength;
- this._scrollStrategy.onDataLengthChanged();
- }
- this._doChangeDetection();
- }));
- }));
- }
- /**
- * Detaches the current `CdkVirtualForOf`.
- * @return {?}
- */
- detach() {
- this._forOf = null;
- this._detachedSubject.next();
- }
- /**
- * Gets the length of the data bound to this viewport (in number of items).
- * @return {?}
- */
- getDataLength() {
- return this._dataLength;
- }
- /**
- * Gets the size of the viewport (in pixels).
- * @return {?}
- */
- getViewportSize() {
- return this._viewportSize;
- }
- // TODO(mmalerba): This is technically out of sync with what's really rendered until a render
- // cycle happens. I'm being careful to only call it after the render cycle is complete and before
- // setting it to something else, but its error prone and should probably be split into
- // `pendingRange` and `renderedRange`, the latter reflecting whats actually in the DOM.
- /**
- * Get the current rendered range of items.
- * @return {?}
- */
- getRenderedRange() {
- return this._renderedRange;
- }
- /**
- * Sets the total size of all content (in pixels), including content that is not currently
- * rendered.
- * @param {?} size
- * @return {?}
- */
- setTotalContentSize(size) {
- if (this._totalContentSize !== size) {
- this._totalContentSize = size;
- this._calculateSpacerSize();
- this._markChangeDetectionNeeded();
- }
- }
- /**
- * Sets the currently rendered range of indices.
- * @param {?} range
- * @return {?}
- */
- setRenderedRange(range) {
- if (!rangesEqual(this._renderedRange, range)) {
- this._renderedRangeSubject.next(this._renderedRange = range);
- this._markChangeDetectionNeeded((/**
- * @return {?}
- */
- () => this._scrollStrategy.onContentRendered()));
- }
- }
- /**
- * Gets the offset from the start of the viewport to the start of the rendered data (in pixels).
- * @return {?}
- */
- getOffsetToRenderedContentStart() {
- return this._renderedContentOffsetNeedsRewrite ? null : this._renderedContentOffset;
- }
- /**
- * Sets the offset from the start of the viewport to either the start or end of the rendered data
- * (in pixels).
- * @param {?} offset
- * @param {?=} to
- * @return {?}
- */
- setRenderedContentOffset(offset, to = 'to-start') {
- // For a horizontal viewport in a right-to-left language we need to translate along the x-axis
- // in the negative direction.
- /** @type {?} */
- const isRtl = this.dir && this.dir.value == 'rtl';
- /** @type {?} */
- const isHorizontal = this.orientation == 'horizontal';
- /** @type {?} */
- const axis = isHorizontal ? 'X' : 'Y';
- /** @type {?} */
- const axisDirection = isHorizontal && isRtl ? -1 : 1;
- /** @type {?} */
- let transform = `translate${axis}(${Number(axisDirection * offset)}px)`;
- this._renderedContentOffset = offset;
- if (to === 'to-end') {
- transform += ` translate${axis}(-100%)`;
- // The viewport should rewrite this as a `to-start` offset on the next render cycle. Otherwise
- // elements will appear to expand in the wrong direction (e.g. `mat-expansion-panel` would
- // expand upward).
- this._renderedContentOffsetNeedsRewrite = true;
- }
- if (this._renderedContentTransform != transform) {
- // We know this value is safe because we parse `offset` with `Number()` before passing it
- // into the string.
- this._renderedContentTransform = transform;
- this._markChangeDetectionNeeded((/**
- * @return {?}
- */
- () => {
- if (this._renderedContentOffsetNeedsRewrite) {
- this._renderedContentOffset -= this.measureRenderedContentSize();
- this._renderedContentOffsetNeedsRewrite = false;
- this.setRenderedContentOffset(this._renderedContentOffset);
- }
- else {
- this._scrollStrategy.onRenderedOffsetChanged();
- }
- }));
- }
- }
- /**
- * Scrolls to the given offset from the start of the viewport. Please note that this is not always
- * the same as setting `scrollTop` or `scrollLeft`. In a horizontal viewport with right-to-left
- * direction, this would be the equivalent of setting a fictional `scrollRight` property.
- * @param {?} offset The offset to scroll to.
- * @param {?=} behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
- * @return {?}
- */
- scrollToOffset(offset, behavior = 'auto') {
- /** @type {?} */
- const options = { behavior };
- if (this.orientation === 'horizontal') {
- options.start = offset;
- }
- else {
- options.top = offset;
- }
- this.scrollTo(options);
- }
- /**
- * Scrolls 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. Default is behavior is `auto`.
- * @return {?}
- */
- scrollToIndex(index, behavior = 'auto') {
- this._scrollStrategy.scrollToIndex(index, behavior);
- }
- /**
- * Gets the current scroll offset from the start of the viewport (in pixels).
- * @param {?=} from The edge to measure the offset from. Defaults to 'top' in vertical mode and 'start'
- * in horizontal mode.
- * @return {?}
- */
- measureScrollOffset(from) {
- return super.measureScrollOffset(from ? from : this.orientation === 'horizontal' ? 'start' : 'top');
- }
- /**
- * Measure the combined size of all of the rendered items.
- * @return {?}
- */
- measureRenderedContentSize() {
- /** @type {?} */
- const contentEl = this._contentWrapper.nativeElement;
- return this.orientation === 'horizontal' ? contentEl.offsetWidth : contentEl.offsetHeight;
- }
- /**
- * Measure the total combined size of the given range. Throws if the range includes items that are
- * not rendered.
- * @param {?} range
- * @return {?}
- */
- measureRangeSize(range) {
- if (!this._forOf) {
- return 0;
- }
- return this._forOf.measureRangeSize(range, this.orientation);
- }
- /**
- * Update the viewport dimensions and re-render.
- * @return {?}
- */
- checkViewportSize() {
- // TODO: Cleanup later when add logic for handling content resize
- this._measureViewportSize();
- this._scrollStrategy.onDataLengthChanged();
- }
- /**
- * Measure the viewport size.
- * @private
- * @return {?}
- */
- _measureViewportSize() {
- /** @type {?} */
- const viewportEl = this.elementRef.nativeElement;
- this._viewportSize = this.orientation === 'horizontal' ?
- viewportEl.clientWidth : viewportEl.clientHeight;
- }
- /**
- * Queue up change detection to run.
- * @private
- * @param {?=} runAfter
- * @return {?}
- */
- _markChangeDetectionNeeded(runAfter) {
- if (runAfter) {
- this._runAfterChangeDetection.push(runAfter);
- }
- // Use a Promise to batch together calls to `_doChangeDetection`. This way if we set a bunch of
- // properties sequentially we only have to run `_doChangeDetection` once at the end.
- if (!this._isChangeDetectionPending) {
- this._isChangeDetectionPending = true;
- this.ngZone.runOutsideAngular((/**
- * @return {?}
- */
- () => Promise.resolve().then((/**
- * @return {?}
- */
- () => {
- this._doChangeDetection();
- }))));
- }
- }
- /**
- * Run change detection.
- * @private
- * @return {?}
- */
- _doChangeDetection() {
- this._isChangeDetectionPending = false;
- // Apply changes to Angular bindings. Note: We must call `markForCheck` to run change detection
- // from the root, since the repeated items are content projected in. Calling `detectChanges`
- // instead does not properly check the projected content.
- this.ngZone.run((/**
- * @return {?}
- */
- () => this._changeDetectorRef.markForCheck()));
- // Apply the content transform. The transform can't be set via an Angular binding because
- // bypassSecurityTrustStyle is banned in Google. However the value is safe, it's composed of
- // string literals, a variable that can only be 'X' or 'Y', and user input that is run through
- // the `Number` function first to coerce it to a numeric value.
- this._contentWrapper.nativeElement.style.transform = this._renderedContentTransform;
- /** @type {?} */
- const runAfterChangeDetection = this._runAfterChangeDetection;
- this._runAfterChangeDetection = [];
- for (const fn of runAfterChangeDetection) {
- fn();
- }
- }
- /**
- * Calculates the `style.width` and `style.height` for the spacer element.
- * @private
- * @return {?}
- */
- _calculateSpacerSize() {
- this._totalContentHeight =
- this.orientation === 'horizontal' ? '' : `${this._totalContentSize}px`;
- this._totalContentWidth =
- this.orientation === 'horizontal' ? `${this._totalContentSize}px` : '';
- }
- }
- CdkVirtualScrollViewport.decorators = [
- { type: Component, args: [{selector: 'cdk-virtual-scroll-viewport',
- template: "<div #contentWrapper class=\"cdk-virtual-scroll-content-wrapper\"><ng-content></ng-content></div><div class=\"cdk-virtual-scroll-spacer\" [style.width]=\"_totalContentWidth\" [style.height]=\"_totalContentHeight\"></div>",
- styles: ["cdk-virtual-scroll-viewport{display:block;position:relative;overflow:auto;contain:strict;transform:translateZ(0);will-change:scroll-position;-webkit-overflow-scrolling:touch}.cdk-virtual-scroll-content-wrapper{position:absolute;top:0;left:0;contain:content}[dir=rtl] .cdk-virtual-scroll-content-wrapper{right:0;left:auto}.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper{min-height:100%}.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>dl:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>ol:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>table:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper>ul:not([cdkVirtualFor]){padding-left:0;padding-right:0;margin-left:0;margin-right:0;border-left-width:0;border-right-width:0;outline:0}.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper{min-width:100%}.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>dl:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>ol:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>table:not([cdkVirtualFor]),.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper>ul:not([cdkVirtualFor]){padding-top:0;padding-bottom:0;margin-top:0;margin-bottom:0;border-top-width:0;border-bottom-width:0;outline:0}.cdk-virtual-scroll-spacer{position:absolute;top:0;left:0;height:1px;width:1px;transform-origin:0 0}[dir=rtl] .cdk-virtual-scroll-spacer{right:0;left:auto;transform-origin:100% 0}"],
- host: {
- 'class': 'cdk-virtual-scroll-viewport',
- '[class.cdk-virtual-scroll-orientation-horizontal]': 'orientation === "horizontal"',
- '[class.cdk-virtual-scroll-orientation-vertical]': 'orientation !== "horizontal"',
- },
- encapsulation: ViewEncapsulation.None,
- changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [{
- provide: CdkScrollable,
- useExisting: CdkVirtualScrollViewport,
- }]
- },] },
- ];
- /** @nocollapse */
- CdkVirtualScrollViewport.ctorParameters = () => [
- { type: ElementRef },
- { type: ChangeDetectorRef },
- { type: NgZone },
- { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [VIRTUAL_SCROLL_STRATEGY,] }] },
- { type: Directionality, decorators: [{ type: Optional }] },
- { type: ScrollDispatcher }
- ];
- CdkVirtualScrollViewport.propDecorators = {
- orientation: [{ type: Input }],
- scrolledIndexChange: [{ type: Output }],
- _contentWrapper: [{ type: ViewChild, args: ['contentWrapper', { static: true },] }]
- };
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * Helper to extract size from a DOM Node.
- * @param {?} orientation
- * @param {?} node
- * @return {?}
- */
- function getSize(orientation, node) {
- /** @type {?} */
- const el = (/** @type {?} */ (node));
- if (!el.getBoundingClientRect) {
- return 0;
- }
- /** @type {?} */
- const rect = el.getBoundingClientRect();
- return orientation == 'horizontal' ? rect.width : rect.height;
- }
- /**
- * A directive similar to `ngForOf` to be used for rendering data inside a virtual scrolling
- * container.
- * @template T
- */
- class CdkVirtualForOf {
- /**
- * @param {?} _viewContainerRef
- * @param {?} _template
- * @param {?} _differs
- * @param {?} _viewport
- * @param {?} ngZone
- */
- constructor(_viewContainerRef, _template, _differs, _viewport, ngZone) {
- this._viewContainerRef = _viewContainerRef;
- this._template = _template;
- this._differs = _differs;
- this._viewport = _viewport;
- /**
- * Emits when the rendered view of the data changes.
- */
- this.viewChange = new Subject();
- /**
- * Subject that emits when a new DataSource instance is given.
- */
- this._dataSourceChanges = new Subject();
- /**
- * The size of the cache used to store templates that are not being used for re-use later.
- * Setting the cache size to `0` will disable caching. Defaults to 20 templates.
- */
- this.cdkVirtualForTemplateCacheSize = 20;
- /**
- * Emits whenever the data in the current DataSource changes.
- */
- this.dataStream = this._dataSourceChanges
- .pipe(
- // Start off with null `DataSource`.
- startWith((/** @type {?} */ (null))),
- // Bundle up the previous and current data sources so we can work with both.
- pairwise(),
- // Use `_changeDataSource` to disconnect from the previous data source and connect to the
- // new one, passing back a stream of data changes which we run through `switchMap` to give
- // us a data stream that emits the latest data from whatever the current `DataSource` is.
- switchMap((/**
- * @param {?} __0
- * @return {?}
- */
- ([prev, cur]) => this._changeDataSource(prev, cur))),
- // Replay the last emitted data when someone subscribes.
- shareReplay(1));
- /**
- * The differ used to calculate changes to the data.
- */
- this._differ = null;
- /**
- * The template cache used to hold on ot template instancess that have been stamped out, but don't
- * currently need to be rendered. These instances will be reused in the future rather than
- * stamping out brand new ones.
- */
- this._templateCache = [];
- /**
- * Whether the rendered data should be updated during the next ngDoCheck cycle.
- */
- this._needsUpdate = false;
- this._destroyed = new Subject();
- this.dataStream.subscribe((/**
- * @param {?} data
- * @return {?}
- */
- data => {
- this._data = data;
- this._onRenderedDataChange();
- }));
- this._viewport.renderedRangeStream.pipe(takeUntil(this._destroyed)).subscribe((/**
- * @param {?} range
- * @return {?}
- */
- range => {
- this._renderedRange = range;
- ngZone.run((/**
- * @return {?}
- */
- () => this.viewChange.next(this._renderedRange)));
- this._onRenderedDataChange();
- }));
- this._viewport.attach(this);
- }
- /**
- * The DataSource to display.
- * @return {?}
- */
- get cdkVirtualForOf() {
- return this._cdkVirtualForOf;
- }
- /**
- * @param {?} value
- * @return {?}
- */
- set cdkVirtualForOf(value) {
- this._cdkVirtualForOf = value;
- /** @type {?} */
- const ds = isDataSource(value) ? value :
- // Slice the value if its an NgIterable to ensure we're working with an array.
- new ArrayDataSource(value instanceof Observable ? value : Array.prototype.slice.call(value || []));
- this._dataSourceChanges.next(ds);
- }
- /**
- * The `TrackByFunction` to use for tracking changes. The `TrackByFunction` takes the index and
- * the item and produces a value to be used as the item's identity when tracking changes.
- * @return {?}
- */
- get cdkVirtualForTrackBy() {
- return this._cdkVirtualForTrackBy;
- }
- /**
- * @param {?} fn
- * @return {?}
- */
- set cdkVirtualForTrackBy(fn) {
- this._needsUpdate = true;
- this._cdkVirtualForTrackBy = fn ?
- (/**
- * @param {?} index
- * @param {?} item
- * @return {?}
- */
- (index, item) => fn(index + (this._renderedRange ? this._renderedRange.start : 0), item)) :
- undefined;
- }
- /**
- * The template used to stamp out new elements.
- * @param {?} value
- * @return {?}
- */
- set cdkVirtualForTemplate(value) {
- if (value) {
- this._needsUpdate = true;
- this._template = value;
- }
- }
- /**
- * Measures the combined size (width for horizontal orientation, height for vertical) of all items
- * in the specified range. Throws an error if the range includes items that are not currently
- * rendered.
- * @param {?} range
- * @param {?} orientation
- * @return {?}
- */
- measureRangeSize(range, orientation) {
- if (range.start >= range.end) {
- return 0;
- }
- if (range.start < this._renderedRange.start || range.end > this._renderedRange.end) {
- throw Error(`Error: attempted to measure an item that isn't rendered.`);
- }
- // The index into the list of rendered views for the first item in the range.
- /** @type {?} */
- const renderedStartIndex = range.start - this._renderedRange.start;
- // The length of the range we're measuring.
- /** @type {?} */
- const rangeLen = range.end - range.start;
- // Loop over all root nodes for all items in the range and sum up their size.
- /** @type {?} */
- let totalSize = 0;
- /** @type {?} */
- let i = rangeLen;
- while (i--) {
- /** @type {?} */
- const view = (/** @type {?} */ (this._viewContainerRef.get(i + renderedStartIndex)));
- /** @type {?} */
- let j = view ? view.rootNodes.length : 0;
- while (j--) {
- totalSize += getSize(orientation, (/** @type {?} */ (view)).rootNodes[j]);
- }
- }
- return totalSize;
- }
- /**
- * @return {?}
- */
- ngDoCheck() {
- if (this._differ && this._needsUpdate) {
- // TODO(mmalerba): We should differentiate needs update due to scrolling and a new portion of
- // this list being rendered (can use simpler algorithm) vs needs update due to data actually
- // changing (need to do this diff).
- /** @type {?} */
- const changes = this._differ.diff(this._renderedItems);
- if (!changes) {
- this._updateContext();
- }
- else {
- this._applyChanges(changes);
- }
- this._needsUpdate = false;
- }
- }
- /**
- * @return {?}
- */
- ngOnDestroy() {
- this._viewport.detach();
- this._dataSourceChanges.next();
- this._dataSourceChanges.complete();
- this.viewChange.complete();
- this._destroyed.next();
- this._destroyed.complete();
- for (let view of this._templateCache) {
- view.destroy();
- }
- }
- /**
- * React to scroll state changes in the viewport.
- * @private
- * @return {?}
- */
- _onRenderedDataChange() {
- if (!this._renderedRange) {
- return;
- }
- this._renderedItems = this._data.slice(this._renderedRange.start, this._renderedRange.end);
- if (!this._differ) {
- this._differ = this._differs.find(this._renderedItems).create(this.cdkVirtualForTrackBy);
- }
- this._needsUpdate = true;
- }
- /**
- * Swap out one `DataSource` for another.
- * @private
- * @param {?} oldDs
- * @param {?} newDs
- * @return {?}
- */
- _changeDataSource(oldDs, newDs) {
- if (oldDs) {
- oldDs.disconnect(this);
- }
- this._needsUpdate = true;
- return newDs ? newDs.connect(this) : of();
- }
- /**
- * Update the `CdkVirtualForOfContext` for all views.
- * @private
- * @return {?}
- */
- _updateContext() {
- /** @type {?} */
- const count = this._data.length;
- /** @type {?} */
- let i = this._viewContainerRef.length;
- while (i--) {
- /** @type {?} */
- let view = (/** @type {?} */ (this._viewContainerRef.get(i)));
- view.context.index = this._renderedRange.start + i;
- view.context.count = count;
- this._updateComputedContextProperties(view.context);
- view.detectChanges();
- }
- }
- /**
- * Apply changes to the DOM.
- * @private
- * @param {?} changes
- * @return {?}
- */
- _applyChanges(changes) {
- // Rearrange the views to put them in the right location.
- changes.forEachOperation((/**
- * @param {?} record
- * @param {?} adjustedPreviousIndex
- * @param {?} currentIndex
- * @return {?}
- */
- (record, adjustedPreviousIndex, currentIndex) => {
- if (record.previousIndex == null) { // Item added.
- // Item added.
- /** @type {?} */
- const view = this._insertViewForNewItem((/** @type {?} */ (currentIndex)));
- view.context.$implicit = record.item;
- }
- else if (currentIndex == null) { // Item removed.
- this._cacheView(this._detachView((/** @type {?} */ (adjustedPreviousIndex))));
- }
- else { // Item moved.
- // Item moved.
- /** @type {?} */
- const view = (/** @type {?} */ (this._viewContainerRef.get((/** @type {?} */ (adjustedPreviousIndex)))));
- this._viewContainerRef.move(view, currentIndex);
- view.context.$implicit = record.item;
- }
- }));
- // Update $implicit for any items that had an identity change.
- changes.forEachIdentityChange((/**
- * @param {?} record
- * @return {?}
- */
- (record) => {
- /** @type {?} */
- const view = (/** @type {?} */ (this._viewContainerRef.get((/** @type {?} */ (record.currentIndex)))));
- view.context.$implicit = record.item;
- }));
- // Update the context variables on all items.
- /** @type {?} */
- const count = this._data.length;
- /** @type {?} */
- let i = this._viewContainerRef.length;
- while (i--) {
- /** @type {?} */
- const view = (/** @type {?} */ (this._viewContainerRef.get(i)));
- view.context.index = this._renderedRange.start + i;
- view.context.count = count;
- this._updateComputedContextProperties(view.context);
- }
- }
- /**
- * Cache the given detached view.
- * @private
- * @param {?} view
- * @return {?}
- */
- _cacheView(view) {
- if (this._templateCache.length < this.cdkVirtualForTemplateCacheSize) {
- this._templateCache.push(view);
- }
- else {
- /** @type {?} */
- const index = this._viewContainerRef.indexOf(view);
- // It's very unlikely that the index will ever be -1, but just in case,
- // destroy the view on its own, otherwise destroy it through the
- // container to ensure that all the references are removed.
- if (index === -1) {
- view.destroy();
- }
- else {
- this._viewContainerRef.remove(index);
- }
- }
- }
- /**
- * Inserts a view for a new item, either from the cache or by creating a new one.
- * @private
- * @param {?} index
- * @return {?}
- */
- _insertViewForNewItem(index) {
- return this._insertViewFromCache(index) || this._createEmbeddedViewAt(index);
- }
- /**
- * Update the computed properties on the `CdkVirtualForOfContext`.
- * @private
- * @param {?} context
- * @return {?}
- */
- _updateComputedContextProperties(context) {
- context.first = context.index === 0;
- context.last = context.index === context.count - 1;
- context.even = context.index % 2 === 0;
- context.odd = !context.even;
- }
- /**
- * Creates a new embedded view and moves it to the given index
- * @private
- * @param {?} index
- * @return {?}
- */
- _createEmbeddedViewAt(index) {
- // Note that it's important that we insert the item directly at the proper index,
- // rather than inserting it and the moving it in place, because if there's a directive
- // on the same node that injects the `ViewContainerRef`, Angular will insert another
- // comment node which can throw off the move when it's being repeated for all items.
- return this._viewContainerRef.createEmbeddedView(this._template, {
- $implicit: (/** @type {?} */ (null)),
- cdkVirtualForOf: this._cdkVirtualForOf,
- index: -1,
- count: -1,
- first: false,
- last: false,
- odd: false,
- even: false
- }, index);
- }
- /**
- * Inserts a recycled view from the cache at the given index.
- * @private
- * @param {?} index
- * @return {?}
- */
- _insertViewFromCache(index) {
- /** @type {?} */
- const cachedView = this._templateCache.pop();
- if (cachedView) {
- this._viewContainerRef.insert(cachedView, index);
- }
- return cachedView || null;
- }
- /**
- * Detaches the embedded view at the given index.
- * @private
- * @param {?} index
- * @return {?}
- */
- _detachView(index) {
- return (/** @type {?} */ (this._viewContainerRef.detach(index)));
- }
- }
- CdkVirtualForOf.decorators = [
- { type: Directive, args: [{
- selector: '[cdkVirtualFor][cdkVirtualForOf]',
- },] },
- ];
- /** @nocollapse */
- CdkVirtualForOf.ctorParameters = () => [
- { type: ViewContainerRef },
- { type: TemplateRef },
- { type: IterableDiffers },
- { type: CdkVirtualScrollViewport, decorators: [{ type: SkipSelf }] },
- { type: NgZone }
- ];
- CdkVirtualForOf.propDecorators = {
- cdkVirtualForOf: [{ type: Input }],
- cdkVirtualForTrackBy: [{ type: Input }],
- cdkVirtualForTemplate: [{ type: Input }],
- cdkVirtualForTemplateCacheSize: [{ type: Input }]
- };
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- class ScrollingModule {
- }
- ScrollingModule.decorators = [
- { type: NgModule, args: [{
- imports: [BidiModule, PlatformModule],
- exports: [
- BidiModule,
- CdkFixedSizeVirtualScroll,
- CdkScrollable,
- CdkVirtualForOf,
- CdkVirtualScrollViewport,
- ],
- declarations: [
- CdkFixedSizeVirtualScroll,
- CdkScrollable,
- CdkVirtualForOf,
- CdkVirtualScrollViewport,
- ],
- },] },
- ];
- /**
- * @deprecated ScrollDispatchModule has been renamed to ScrollingModule.
- * \@breaking-change 8.0.0 delete this alias
- */
- class ScrollDispatchModule {
- }
- ScrollDispatchModule.decorators = [
- { type: NgModule, args: [{
- imports: [ScrollingModule],
- exports: [ScrollingModule],
- },] },
- ];
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * Time in ms to throttle the resize events by default.
- * @type {?}
- */
- const DEFAULT_RESIZE_TIME = 20;
- /**
- * Simple utility for getting the bounds of the browser viewport.
- * \@docs-private
- */
- class ViewportRuler {
- /**
- * @param {?} _platform
- * @param {?} ngZone
- */
- constructor(_platform, ngZone) {
- this._platform = _platform;
- ngZone.runOutsideAngular((/**
- * @return {?}
- */
- () => {
- this._change = _platform.isBrowser ?
- merge(fromEvent(window, 'resize'), fromEvent(window, 'orientationchange')) :
- of();
- // Note that we need to do the subscription inside `runOutsideAngular`
- // since subscribing is what causes the event listener to be added.
- this._invalidateCache = this.change().subscribe((/**
- * @return {?}
- */
- () => this._updateViewportSize()));
- }));
- }
- /**
- * @return {?}
- */
- ngOnDestroy() {
- this._invalidateCache.unsubscribe();
- }
- /**
- * Returns the viewport's width and height.
- * @return {?}
- */
- getViewportSize() {
- if (!this._viewportSize) {
- this._updateViewportSize();
- }
- /** @type {?} */
- const output = { width: this._viewportSize.width, height: this._viewportSize.height };
- // If we're not on a browser, don't cache the size since it'll be mocked out anyway.
- if (!this._platform.isBrowser) {
- this._viewportSize = (/** @type {?} */ (null));
- }
- return output;
- }
- /**
- * Gets a ClientRect for the viewport's bounds.
- * @return {?}
- */
- getViewportRect() {
- // Use the document element's bounding rect rather than the window scroll properties
- // (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll
- // properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different
- // conceptual viewports. Under most circumstances these viewports are equivalent, but they
- // can disagree when the page is pinch-zoomed (on devices that support touch).
- // See https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4
- // We use the documentElement instead of the body because, by default (without a css reset)
- // browsers typically give the document body an 8px margin, which is not included in
- // getBoundingClientRect().
- /** @type {?} */
- const scrollPosition = this.getViewportScrollPosition();
- const { width, height } = this.getViewportSize();
- return {
- top: scrollPosition.top,
- left: scrollPosition.left,
- bottom: scrollPosition.top + height,
- right: scrollPosition.left + width,
- height,
- width,
- };
- }
- /**
- * Gets the (top, left) scroll position of the viewport.
- * @return {?}
- */
- getViewportScrollPosition() {
- // While we can get a reference to the fake document
- // during SSR, it doesn't have getBoundingClientRect.
- if (!this._platform.isBrowser) {
- return { top: 0, left: 0 };
- }
- // The top-left-corner of the viewport is determined by the scroll position of the document
- // body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about
- // whether `document.body` or `document.documentElement` is the scrolled element, so reading
- // `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding rect of
- // `document.documentElement` works consistently, where the `top` and `left` values will
- // equal negative the scroll position.
- /** @type {?} */
- const documentElement = (/** @type {?} */ (document.documentElement));
- /** @type {?} */
- const documentRect = documentElement.getBoundingClientRect();
- /** @type {?} */
- const top = -documentRect.top || document.body.scrollTop || window.scrollY ||
- documentElement.scrollTop || 0;
- /** @type {?} */
- const left = -documentRect.left || document.body.scrollLeft || window.scrollX ||
- documentElement.scrollLeft || 0;
- return { top, left };
- }
- /**
- * Returns a stream that emits whenever the size of the viewport changes.
- * @param {?=} throttleTime Time in milliseconds to throttle the stream.
- * @return {?}
- */
- change(throttleTime = DEFAULT_RESIZE_TIME) {
- return throttleTime > 0 ? this._change.pipe(auditTime(throttleTime)) : this._change;
- }
- /**
- * Updates the cached viewport size.
- * @private
- * @return {?}
- */
- _updateViewportSize() {
- this._viewportSize = this._platform.isBrowser ?
- { width: window.innerWidth, height: window.innerHeight } :
- { width: 0, height: 0 };
- }
- }
- ViewportRuler.decorators = [
- { type: Injectable, args: [{ providedIn: 'root' },] },
- ];
- /** @nocollapse */
- ViewportRuler.ctorParameters = () => [
- { type: Platform },
- { type: NgZone }
- ];
- /** @nocollapse */ ViewportRuler.ngInjectableDef = ɵɵdefineInjectable({ factory: function ViewportRuler_Factory() { return new ViewportRuler(ɵɵinject(Platform), ɵɵinject(NgZone)); }, token: ViewportRuler, providedIn: "root" });
- /**
- * \@docs-private \@deprecated \@breaking-change 8.0.0
- * @param {?} parentRuler
- * @param {?} platform
- * @param {?} ngZone
- * @return {?}
- */
- function VIEWPORT_RULER_PROVIDER_FACTORY(parentRuler, platform, ngZone) {
- return parentRuler || new ViewportRuler(platform, ngZone);
- }
- /**
- * \@docs-private \@deprecated \@breaking-change 8.0.0
- * @type {?}
- */
- const VIEWPORT_RULER_PROVIDER = {
- // If there is already a ViewportRuler available, use that. Otherwise, provide a new one.
- provide: ViewportRuler,
- deps: [[new Optional(), new SkipSelf(), ViewportRuler], Platform, NgZone],
- useFactory: VIEWPORT_RULER_PROVIDER_FACTORY
- };
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- /**
- * @fileoverview added by tsickle
- * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
- */
- export { _fixedSizeVirtualScrollStrategyFactory, FixedSizeVirtualScrollStrategy, CdkFixedSizeVirtualScroll, SCROLL_DISPATCHER_PROVIDER_FACTORY, DEFAULT_SCROLL_TIME, ScrollDispatcher, SCROLL_DISPATCHER_PROVIDER, CdkScrollable, ScrollingModule, ScrollDispatchModule, VIEWPORT_RULER_PROVIDER_FACTORY, DEFAULT_RESIZE_TIME, ViewportRuler, VIEWPORT_RULER_PROVIDER, CdkVirtualForOf, VIRTUAL_SCROLL_STRATEGY, CdkVirtualScrollViewport };
- //# sourceMappingURL=scrolling.js.map
|