source: trip-planner-front/node_modules/@angular/cdk/fesm2015/scrolling.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 61.3 KB
Line 
1import { coerceNumberProperty, coerceElement, coerceBooleanProperty } from '@angular/cdk/coercion';
2import * as i0 from '@angular/core';
3import { InjectionToken, Directive, forwardRef, Input, Injectable, NgZone, Optional, Inject, ElementRef, Component, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, Output, ViewChild, ViewContainerRef, TemplateRef, IterableDiffers, SkipSelf, NgModule } from '@angular/core';
4import { Subject, of, Observable, fromEvent, animationFrameScheduler, asapScheduler, Subscription, isObservable } from 'rxjs';
5import { distinctUntilChanged, auditTime, filter, takeUntil, startWith, pairwise, switchMap, shareReplay } from 'rxjs/operators';
6import * as i1 from '@angular/cdk/platform';
7import { Platform, getRtlScrollAxisType, supportsScrollBehavior, PlatformModule } from '@angular/cdk/platform';
8import * as i2 from '@angular/common';
9import { DOCUMENT } from '@angular/common';
10import { Directionality, BidiModule } from '@angular/cdk/bidi';
11import { isDataSource, ArrayDataSource, _VIEW_REPEATER_STRATEGY, _RecycleViewRepeaterStrategy } from '@angular/cdk/collections';
12
13/**
14 * @license
15 * Copyright Google LLC All Rights Reserved.
16 *
17 * Use of this source code is governed by an MIT-style license that can be
18 * found in the LICENSE file at https://angular.io/license
19 */
20/** The injection token used to specify the virtual scrolling strategy. */
21const VIRTUAL_SCROLL_STRATEGY = new InjectionToken('VIRTUAL_SCROLL_STRATEGY');
22
23/**
24 * @license
25 * Copyright Google LLC All Rights Reserved.
26 *
27 * Use of this source code is governed by an MIT-style license that can be
28 * found in the LICENSE file at https://angular.io/license
29 */
30/** Virtual scrolling strategy for lists with items of known fixed size. */
31class FixedSizeVirtualScrollStrategy {
32 /**
33 * @param itemSize The size of the items in the virtually scrolling list.
34 * @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more
35 * @param maxBufferPx The amount of buffer (in pixels) to render when rendering more.
36 */
37 constructor(itemSize, minBufferPx, maxBufferPx) {
38 this._scrolledIndexChange = new Subject();
39 /** @docs-private Implemented as part of VirtualScrollStrategy. */
40 this.scrolledIndexChange = this._scrolledIndexChange.pipe(distinctUntilChanged());
41 /** The attached viewport. */
42 this._viewport = null;
43 this._itemSize = itemSize;
44 this._minBufferPx = minBufferPx;
45 this._maxBufferPx = maxBufferPx;
46 }
47 /**
48 * Attaches this scroll strategy to a viewport.
49 * @param viewport The viewport to attach this strategy to.
50 */
51 attach(viewport) {
52 this._viewport = viewport;
53 this._updateTotalContentSize();
54 this._updateRenderedRange();
55 }
56 /** Detaches this scroll strategy from the currently attached viewport. */
57 detach() {
58 this._scrolledIndexChange.complete();
59 this._viewport = null;
60 }
61 /**
62 * Update the item size and buffer size.
63 * @param itemSize The size of the items in the virtually scrolling list.
64 * @param minBufferPx The minimum amount of buffer (in pixels) before needing to render more
65 * @param maxBufferPx The amount of buffer (in pixels) to render when rendering more.
66 */
67 updateItemAndBufferSize(itemSize, minBufferPx, maxBufferPx) {
68 if (maxBufferPx < minBufferPx && (typeof ngDevMode === 'undefined' || ngDevMode)) {
69 throw Error('CDK virtual scroll: maxBufferPx must be greater than or equal to minBufferPx');
70 }
71 this._itemSize = itemSize;
72 this._minBufferPx = minBufferPx;
73 this._maxBufferPx = maxBufferPx;
74 this._updateTotalContentSize();
75 this._updateRenderedRange();
76 }
77 /** @docs-private Implemented as part of VirtualScrollStrategy. */
78 onContentScrolled() {
79 this._updateRenderedRange();
80 }
81 /** @docs-private Implemented as part of VirtualScrollStrategy. */
82 onDataLengthChanged() {
83 this._updateTotalContentSize();
84 this._updateRenderedRange();
85 }
86 /** @docs-private Implemented as part of VirtualScrollStrategy. */
87 onContentRendered() { }
88 /** @docs-private Implemented as part of VirtualScrollStrategy. */
89 onRenderedOffsetChanged() { }
90 /**
91 * Scroll to the offset for the given index.
92 * @param index The index of the element to scroll to.
93 * @param behavior The ScrollBehavior to use when scrolling.
94 */
95 scrollToIndex(index, behavior) {
96 if (this._viewport) {
97 this._viewport.scrollToOffset(index * this._itemSize, behavior);
98 }
99 }
100 /** Update the viewport's total content size. */
101 _updateTotalContentSize() {
102 if (!this._viewport) {
103 return;
104 }
105 this._viewport.setTotalContentSize(this._viewport.getDataLength() * this._itemSize);
106 }
107 /** Update the viewport's rendered range. */
108 _updateRenderedRange() {
109 if (!this._viewport) {
110 return;
111 }
112 const renderedRange = this._viewport.getRenderedRange();
113 const newRange = { start: renderedRange.start, end: renderedRange.end };
114 const viewportSize = this._viewport.getViewportSize();
115 const dataLength = this._viewport.getDataLength();
116 let scrollOffset = this._viewport.measureScrollOffset();
117 // Prevent NaN as result when dividing by zero.
118 let firstVisibleIndex = (this._itemSize > 0) ? scrollOffset / this._itemSize : 0;
119 // If user scrolls to the bottom of the list and data changes to a smaller list
120 if (newRange.end > dataLength) {
121 // We have to recalculate the first visible index based on new data length and viewport size.
122 const maxVisibleItems = Math.ceil(viewportSize / this._itemSize);
123 const newVisibleIndex = Math.max(0, Math.min(firstVisibleIndex, dataLength - maxVisibleItems));
124 // If first visible index changed we must update scroll offset to handle start/end buffers
125 // Current range must also be adjusted to cover the new position (bottom of new list).
126 if (firstVisibleIndex != newVisibleIndex) {
127 firstVisibleIndex = newVisibleIndex;
128 scrollOffset = newVisibleIndex * this._itemSize;
129 newRange.start = Math.floor(firstVisibleIndex);
130 }
131 newRange.end = Math.max(0, Math.min(dataLength, newRange.start + maxVisibleItems));
132 }
133 const startBuffer = scrollOffset - newRange.start * this._itemSize;
134 if (startBuffer < this._minBufferPx && newRange.start != 0) {
135 const expandStart = Math.ceil((this._maxBufferPx - startBuffer) / this._itemSize);
136 newRange.start = Math.max(0, newRange.start - expandStart);
137 newRange.end = Math.min(dataLength, Math.ceil(firstVisibleIndex + (viewportSize + this._minBufferPx) / this._itemSize));
138 }
139 else {
140 const endBuffer = newRange.end * this._itemSize - (scrollOffset + viewportSize);
141 if (endBuffer < this._minBufferPx && newRange.end != dataLength) {
142 const expandEnd = Math.ceil((this._maxBufferPx - endBuffer) / this._itemSize);
143 if (expandEnd > 0) {
144 newRange.end = Math.min(dataLength, newRange.end + expandEnd);
145 newRange.start = Math.max(0, Math.floor(firstVisibleIndex - this._minBufferPx / this._itemSize));
146 }
147 }
148 }
149 this._viewport.setRenderedRange(newRange);
150 this._viewport.setRenderedContentOffset(this._itemSize * newRange.start);
151 this._scrolledIndexChange.next(Math.floor(firstVisibleIndex));
152 }
153}
154/**
155 * Provider factory for `FixedSizeVirtualScrollStrategy` that simply extracts the already created
156 * `FixedSizeVirtualScrollStrategy` from the given directive.
157 * @param fixedSizeDir The instance of `CdkFixedSizeVirtualScroll` to extract the
158 * `FixedSizeVirtualScrollStrategy` from.
159 */
160function _fixedSizeVirtualScrollStrategyFactory(fixedSizeDir) {
161 return fixedSizeDir._scrollStrategy;
162}
163/** A virtual scroll strategy that supports fixed-size items. */
164class CdkFixedSizeVirtualScroll {
165 constructor() {
166 this._itemSize = 20;
167 this._minBufferPx = 100;
168 this._maxBufferPx = 200;
169 /** The scroll strategy used by this directive. */
170 this._scrollStrategy = new FixedSizeVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx);
171 }
172 /** The size of the items in the list (in pixels). */
173 get itemSize() { return this._itemSize; }
174 set itemSize(value) { this._itemSize = coerceNumberProperty(value); }
175 /**
176 * The minimum amount of buffer rendered beyond the viewport (in pixels).
177 * If the amount of buffer dips below this number, more items will be rendered. Defaults to 100px.
178 */
179 get minBufferPx() { return this._minBufferPx; }
180 set minBufferPx(value) { this._minBufferPx = coerceNumberProperty(value); }
181 /**
182 * The number of pixels worth of buffer to render for when rendering new items. Defaults to 200px.
183 */
184 get maxBufferPx() { return this._maxBufferPx; }
185 set maxBufferPx(value) { this._maxBufferPx = coerceNumberProperty(value); }
186 ngOnChanges() {
187 this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx);
188 }
189}
190CdkFixedSizeVirtualScroll.decorators = [
191 { type: Directive, args: [{
192 selector: 'cdk-virtual-scroll-viewport[itemSize]',
193 providers: [{
194 provide: VIRTUAL_SCROLL_STRATEGY,
195 useFactory: _fixedSizeVirtualScrollStrategyFactory,
196 deps: [forwardRef(() => CdkFixedSizeVirtualScroll)],
197 }],
198 },] }
199];
200CdkFixedSizeVirtualScroll.propDecorators = {
201 itemSize: [{ type: Input }],
202 minBufferPx: [{ type: Input }],
203 maxBufferPx: [{ type: Input }]
204};
205
206/**
207 * @license
208 * Copyright Google LLC All Rights Reserved.
209 *
210 * Use of this source code is governed by an MIT-style license that can be
211 * found in the LICENSE file at https://angular.io/license
212 */
213/** Time in ms to throttle the scrolling events by default. */
214const DEFAULT_SCROLL_TIME = 20;
215/**
216 * Service contained all registered Scrollable references and emits an event when any one of the
217 * Scrollable references emit a scrolled event.
218 */
219class ScrollDispatcher {
220 constructor(_ngZone, _platform, document) {
221 this._ngZone = _ngZone;
222 this._platform = _platform;
223 /** Subject for notifying that a registered scrollable reference element has been scrolled. */
224 this._scrolled = new Subject();
225 /** Keeps track of the global `scroll` and `resize` subscriptions. */
226 this._globalSubscription = null;
227 /** Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards. */
228 this._scrolledCount = 0;
229 /**
230 * Map of all the scrollable references that are registered with the service and their
231 * scroll event subscriptions.
232 */
233 this.scrollContainers = new Map();
234 this._document = document;
235 }
236 /**
237 * Registers a scrollable instance with the service and listens for its scrolled events. When the
238 * scrollable is scrolled, the service emits the event to its scrolled observable.
239 * @param scrollable Scrollable instance to be registered.
240 */
241 register(scrollable) {
242 if (!this.scrollContainers.has(scrollable)) {
243 this.scrollContainers.set(scrollable, scrollable.elementScrolled()
244 .subscribe(() => this._scrolled.next(scrollable)));
245 }
246 }
247 /**
248 * Deregisters a Scrollable reference and unsubscribes from its scroll event observable.
249 * @param scrollable Scrollable instance to be deregistered.
250 */
251 deregister(scrollable) {
252 const scrollableReference = this.scrollContainers.get(scrollable);
253 if (scrollableReference) {
254 scrollableReference.unsubscribe();
255 this.scrollContainers.delete(scrollable);
256 }
257 }
258 /**
259 * Returns an observable that emits an event whenever any of the registered Scrollable
260 * references (or window, document, or body) fire a scrolled event. Can provide a time in ms
261 * to override the default "throttle" time.
262 *
263 * **Note:** in order to avoid hitting change detection for every scroll event,
264 * all of the events emitted from this stream will be run outside the Angular zone.
265 * If you need to update any data bindings as a result of a scroll event, you have
266 * to run the callback using `NgZone.run`.
267 */
268 scrolled(auditTimeInMs = DEFAULT_SCROLL_TIME) {
269 if (!this._platform.isBrowser) {
270 return of();
271 }
272 return new Observable((observer) => {
273 if (!this._globalSubscription) {
274 this._addGlobalListener();
275 }
276 // In the case of a 0ms delay, use an observable without auditTime
277 // since it does add a perceptible delay in processing overhead.
278 const subscription = auditTimeInMs > 0 ?
279 this._scrolled.pipe(auditTime(auditTimeInMs)).subscribe(observer) :
280 this._scrolled.subscribe(observer);
281 this._scrolledCount++;
282 return () => {
283 subscription.unsubscribe();
284 this._scrolledCount--;
285 if (!this._scrolledCount) {
286 this._removeGlobalListener();
287 }
288 };
289 });
290 }
291 ngOnDestroy() {
292 this._removeGlobalListener();
293 this.scrollContainers.forEach((_, container) => this.deregister(container));
294 this._scrolled.complete();
295 }
296 /**
297 * Returns an observable that emits whenever any of the
298 * scrollable ancestors of an element are scrolled.
299 * @param elementOrElementRef Element whose ancestors to listen for.
300 * @param auditTimeInMs Time to throttle the scroll events.
301 */
302 ancestorScrolled(elementOrElementRef, auditTimeInMs) {
303 const ancestors = this.getAncestorScrollContainers(elementOrElementRef);
304 return this.scrolled(auditTimeInMs).pipe(filter(target => {
305 return !target || ancestors.indexOf(target) > -1;
306 }));
307 }
308 /** Returns all registered Scrollables that contain the provided element. */
309 getAncestorScrollContainers(elementOrElementRef) {
310 const scrollingContainers = [];
311 this.scrollContainers.forEach((_subscription, scrollable) => {
312 if (this._scrollableContainsElement(scrollable, elementOrElementRef)) {
313 scrollingContainers.push(scrollable);
314 }
315 });
316 return scrollingContainers;
317 }
318 /** Use defaultView of injected document if available or fallback to global window reference */
319 _getWindow() {
320 return this._document.defaultView || window;
321 }
322 /** Returns true if the element is contained within the provided Scrollable. */
323 _scrollableContainsElement(scrollable, elementOrElementRef) {
324 let element = coerceElement(elementOrElementRef);
325 let scrollableElement = scrollable.getElementRef().nativeElement;
326 // Traverse through the element parents until we reach null, checking if any of the elements
327 // are the scrollable's element.
328 do {
329 if (element == scrollableElement) {
330 return true;
331 }
332 } while (element = element.parentElement);
333 return false;
334 }
335 /** Sets up the global scroll listeners. */
336 _addGlobalListener() {
337 this._globalSubscription = this._ngZone.runOutsideAngular(() => {
338 const window = this._getWindow();
339 return fromEvent(window.document, 'scroll').subscribe(() => this._scrolled.next());
340 });
341 }
342 /** Cleans up the global scroll listener. */
343 _removeGlobalListener() {
344 if (this._globalSubscription) {
345 this._globalSubscription.unsubscribe();
346 this._globalSubscription = null;
347 }
348 }
349}
350ScrollDispatcher.ɵprov = i0.ɵɵdefineInjectable({ factory: function ScrollDispatcher_Factory() { return new ScrollDispatcher(i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i1.Platform), i0.ɵɵinject(i2.DOCUMENT, 8)); }, token: ScrollDispatcher, providedIn: "root" });
351ScrollDispatcher.decorators = [
352 { type: Injectable, args: [{ providedIn: 'root' },] }
353];
354ScrollDispatcher.ctorParameters = () => [
355 { type: NgZone },
356 { type: Platform },
357 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] }
358];
359
360/**
361 * @license
362 * Copyright Google LLC All Rights Reserved.
363 *
364 * Use of this source code is governed by an MIT-style license that can be
365 * found in the LICENSE file at https://angular.io/license
366 */
367/**
368 * Sends an event when the directive's element is scrolled. Registers itself with the
369 * ScrollDispatcher service to include itself as part of its collection of scrolling events that it
370 * can be listened to through the service.
371 */
372class CdkScrollable {
373 constructor(elementRef, scrollDispatcher, ngZone, dir) {
374 this.elementRef = elementRef;
375 this.scrollDispatcher = scrollDispatcher;
376 this.ngZone = ngZone;
377 this.dir = dir;
378 this._destroyed = new Subject();
379 this._elementScrolled = new Observable((observer) => this.ngZone.runOutsideAngular(() => fromEvent(this.elementRef.nativeElement, 'scroll').pipe(takeUntil(this._destroyed))
380 .subscribe(observer)));
381 }
382 ngOnInit() {
383 this.scrollDispatcher.register(this);
384 }
385 ngOnDestroy() {
386 this.scrollDispatcher.deregister(this);
387 this._destroyed.next();
388 this._destroyed.complete();
389 }
390 /** Returns observable that emits when a scroll event is fired on the host element. */
391 elementScrolled() {
392 return this._elementScrolled;
393 }
394 /** Gets the ElementRef for the viewport. */
395 getElementRef() {
396 return this.elementRef;
397 }
398 /**
399 * Scrolls to the specified offsets. This is a normalized version of the browser's native scrollTo
400 * method, since browsers are not consistent about what scrollLeft means in RTL. For this method
401 * left and right always refer to the left and right side of the scrolling container irrespective
402 * of the layout direction. start and end refer to left and right in an LTR context and vice-versa
403 * in an RTL context.
404 * @param options specified the offsets to scroll to.
405 */
406 scrollTo(options) {
407 const el = this.elementRef.nativeElement;
408 const isRtl = this.dir && this.dir.value == 'rtl';
409 // Rewrite start & end offsets as right or left offsets.
410 if (options.left == null) {
411 options.left = isRtl ? options.end : options.start;
412 }
413 if (options.right == null) {
414 options.right = isRtl ? options.start : options.end;
415 }
416 // Rewrite the bottom offset as a top offset.
417 if (options.bottom != null) {
418 options.top =
419 el.scrollHeight - el.clientHeight - options.bottom;
420 }
421 // Rewrite the right offset as a left offset.
422 if (isRtl && getRtlScrollAxisType() != 0 /* NORMAL */) {
423 if (options.left != null) {
424 options.right =
425 el.scrollWidth - el.clientWidth - options.left;
426 }
427 if (getRtlScrollAxisType() == 2 /* INVERTED */) {
428 options.left = options.right;
429 }
430 else if (getRtlScrollAxisType() == 1 /* NEGATED */) {
431 options.left = options.right ? -options.right : options.right;
432 }
433 }
434 else {
435 if (options.right != null) {
436 options.left =
437 el.scrollWidth - el.clientWidth - options.right;
438 }
439 }
440 this._applyScrollToOptions(options);
441 }
442 _applyScrollToOptions(options) {
443 const el = this.elementRef.nativeElement;
444 if (supportsScrollBehavior()) {
445 el.scrollTo(options);
446 }
447 else {
448 if (options.top != null) {
449 el.scrollTop = options.top;
450 }
451 if (options.left != null) {
452 el.scrollLeft = options.left;
453 }
454 }
455 }
456 /**
457 * Measures the scroll offset relative to the specified edge of the viewport. This method can be
458 * used instead of directly checking scrollLeft or scrollTop, since browsers are not consistent
459 * about what scrollLeft means in RTL. The values returned by this method are normalized such that
460 * left and right always refer to the left and right side of the scrolling container irrespective
461 * of the layout direction. start and end refer to left and right in an LTR context and vice-versa
462 * in an RTL context.
463 * @param from The edge to measure from.
464 */
465 measureScrollOffset(from) {
466 const LEFT = 'left';
467 const RIGHT = 'right';
468 const el = this.elementRef.nativeElement;
469 if (from == 'top') {
470 return el.scrollTop;
471 }
472 if (from == 'bottom') {
473 return el.scrollHeight - el.clientHeight - el.scrollTop;
474 }
475 // Rewrite start & end as left or right offsets.
476 const isRtl = this.dir && this.dir.value == 'rtl';
477 if (from == 'start') {
478 from = isRtl ? RIGHT : LEFT;
479 }
480 else if (from == 'end') {
481 from = isRtl ? LEFT : RIGHT;
482 }
483 if (isRtl && getRtlScrollAxisType() == 2 /* INVERTED */) {
484 // For INVERTED, scrollLeft is (scrollWidth - clientWidth) when scrolled all the way left and
485 // 0 when scrolled all the way right.
486 if (from == LEFT) {
487 return el.scrollWidth - el.clientWidth - el.scrollLeft;
488 }
489 else {
490 return el.scrollLeft;
491 }
492 }
493 else if (isRtl && getRtlScrollAxisType() == 1 /* NEGATED */) {
494 // For NEGATED, scrollLeft is -(scrollWidth - clientWidth) when scrolled all the way left and
495 // 0 when scrolled all the way right.
496 if (from == LEFT) {
497 return el.scrollLeft + el.scrollWidth - el.clientWidth;
498 }
499 else {
500 return -el.scrollLeft;
501 }
502 }
503 else {
504 // For NORMAL, as well as non-RTL contexts, scrollLeft is 0 when scrolled all the way left and
505 // (scrollWidth - clientWidth) when scrolled all the way right.
506 if (from == LEFT) {
507 return el.scrollLeft;
508 }
509 else {
510 return el.scrollWidth - el.clientWidth - el.scrollLeft;
511 }
512 }
513 }
514}
515CdkScrollable.decorators = [
516 { type: Directive, args: [{
517 selector: '[cdk-scrollable], [cdkScrollable]'
518 },] }
519];
520CdkScrollable.ctorParameters = () => [
521 { type: ElementRef },
522 { type: ScrollDispatcher },
523 { type: NgZone },
524 { type: Directionality, decorators: [{ type: Optional }] }
525];
526
527/**
528 * @license
529 * Copyright Google LLC All Rights Reserved.
530 *
531 * Use of this source code is governed by an MIT-style license that can be
532 * found in the LICENSE file at https://angular.io/license
533 */
534/** Time in ms to throttle the resize events by default. */
535const DEFAULT_RESIZE_TIME = 20;
536/**
537 * Simple utility for getting the bounds of the browser viewport.
538 * @docs-private
539 */
540class ViewportRuler {
541 constructor(_platform, ngZone, document) {
542 this._platform = _platform;
543 /** Stream of viewport change events. */
544 this._change = new Subject();
545 /** Event listener that will be used to handle the viewport change events. */
546 this._changeListener = (event) => {
547 this._change.next(event);
548 };
549 this._document = document;
550 ngZone.runOutsideAngular(() => {
551 if (_platform.isBrowser) {
552 const window = this._getWindow();
553 // Note that bind the events ourselves, rather than going through something like RxJS's
554 // `fromEvent` so that we can ensure that they're bound outside of the NgZone.
555 window.addEventListener('resize', this._changeListener);
556 window.addEventListener('orientationchange', this._changeListener);
557 }
558 // Clear the cached position so that the viewport is re-measured next time it is required.
559 // We don't need to keep track of the subscription, because it is completed on destroy.
560 this.change().subscribe(() => this._viewportSize = null);
561 });
562 }
563 ngOnDestroy() {
564 if (this._platform.isBrowser) {
565 const window = this._getWindow();
566 window.removeEventListener('resize', this._changeListener);
567 window.removeEventListener('orientationchange', this._changeListener);
568 }
569 this._change.complete();
570 }
571 /** Returns the viewport's width and height. */
572 getViewportSize() {
573 if (!this._viewportSize) {
574 this._updateViewportSize();
575 }
576 const output = { width: this._viewportSize.width, height: this._viewportSize.height };
577 // If we're not on a browser, don't cache the size since it'll be mocked out anyway.
578 if (!this._platform.isBrowser) {
579 this._viewportSize = null;
580 }
581 return output;
582 }
583 /** Gets a ClientRect for the viewport's bounds. */
584 getViewportRect() {
585 // Use the document element's bounding rect rather than the window scroll properties
586 // (e.g. pageYOffset, scrollY) due to in issue in Chrome and IE where window scroll
587 // properties and client coordinates (boundingClientRect, clientX/Y, etc.) are in different
588 // conceptual viewports. Under most circumstances these viewports are equivalent, but they
589 // can disagree when the page is pinch-zoomed (on devices that support touch).
590 // See https://bugs.chromium.org/p/chromium/issues/detail?id=489206#c4
591 // We use the documentElement instead of the body because, by default (without a css reset)
592 // browsers typically give the document body an 8px margin, which is not included in
593 // getBoundingClientRect().
594 const scrollPosition = this.getViewportScrollPosition();
595 const { width, height } = this.getViewportSize();
596 return {
597 top: scrollPosition.top,
598 left: scrollPosition.left,
599 bottom: scrollPosition.top + height,
600 right: scrollPosition.left + width,
601 height,
602 width,
603 };
604 }
605 /** Gets the (top, left) scroll position of the viewport. */
606 getViewportScrollPosition() {
607 // While we can get a reference to the fake document
608 // during SSR, it doesn't have getBoundingClientRect.
609 if (!this._platform.isBrowser) {
610 return { top: 0, left: 0 };
611 }
612 // The top-left-corner of the viewport is determined by the scroll position of the document
613 // body, normally just (scrollLeft, scrollTop). However, Chrome and Firefox disagree about
614 // whether `document.body` or `document.documentElement` is the scrolled element, so reading
615 // `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding rect of
616 // `document.documentElement` works consistently, where the `top` and `left` values will
617 // equal negative the scroll position.
618 const document = this._document;
619 const window = this._getWindow();
620 const documentElement = document.documentElement;
621 const documentRect = documentElement.getBoundingClientRect();
622 const top = -documentRect.top || document.body.scrollTop || window.scrollY ||
623 documentElement.scrollTop || 0;
624 const left = -documentRect.left || document.body.scrollLeft || window.scrollX ||
625 documentElement.scrollLeft || 0;
626 return { top, left };
627 }
628 /**
629 * Returns a stream that emits whenever the size of the viewport changes.
630 * This stream emits outside of the Angular zone.
631 * @param throttleTime Time in milliseconds to throttle the stream.
632 */
633 change(throttleTime = DEFAULT_RESIZE_TIME) {
634 return throttleTime > 0 ? this._change.pipe(auditTime(throttleTime)) : this._change;
635 }
636 /** Use defaultView of injected document if available or fallback to global window reference */
637 _getWindow() {
638 return this._document.defaultView || window;
639 }
640 /** Updates the cached viewport size. */
641 _updateViewportSize() {
642 const window = this._getWindow();
643 this._viewportSize = this._platform.isBrowser ?
644 { width: window.innerWidth, height: window.innerHeight } :
645 { width: 0, height: 0 };
646 }
647}
648ViewportRuler.ɵprov = i0.ɵɵdefineInjectable({ factory: function ViewportRuler_Factory() { return new ViewportRuler(i0.ɵɵinject(i1.Platform), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i2.DOCUMENT, 8)); }, token: ViewportRuler, providedIn: "root" });
649ViewportRuler.decorators = [
650 { type: Injectable, args: [{ providedIn: 'root' },] }
651];
652ViewportRuler.ctorParameters = () => [
653 { type: Platform },
654 { type: NgZone },
655 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DOCUMENT,] }] }
656];
657
658/**
659 * @license
660 * Copyright Google LLC All Rights Reserved.
661 *
662 * Use of this source code is governed by an MIT-style license that can be
663 * found in the LICENSE file at https://angular.io/license
664 */
665/** Checks if the given ranges are equal. */
666function rangesEqual(r1, r2) {
667 return r1.start == r2.start && r1.end == r2.end;
668}
669/**
670 * Scheduler to be used for scroll events. Needs to fall back to
671 * something that doesn't rely on requestAnimationFrame on environments
672 * that don't support it (e.g. server-side rendering).
673 */
674const SCROLL_SCHEDULER = typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
675/** A viewport that virtualizes its scrolling with the help of `CdkVirtualForOf`. */
676class CdkVirtualScrollViewport extends CdkScrollable {
677 constructor(elementRef, _changeDetectorRef, ngZone, _scrollStrategy, dir, scrollDispatcher, viewportRuler) {
678 super(elementRef, scrollDispatcher, ngZone, dir);
679 this.elementRef = elementRef;
680 this._changeDetectorRef = _changeDetectorRef;
681 this._scrollStrategy = _scrollStrategy;
682 /** Emits when the viewport is detached from a CdkVirtualForOf. */
683 this._detachedSubject = new Subject();
684 /** Emits when the rendered range changes. */
685 this._renderedRangeSubject = new Subject();
686 this._orientation = 'vertical';
687 this._appendOnly = false;
688 // Note: we don't use the typical EventEmitter here because we need to subscribe to the scroll
689 // strategy lazily (i.e. only if the user is actually listening to the events). We do this because
690 // depending on how the strategy calculates the scrolled index, it may come at a cost to
691 // performance.
692 /** Emits when the index of the first element visible in the viewport changes. */
693 this.scrolledIndexChange = new Observable((observer) => this._scrollStrategy.scrolledIndexChange.subscribe(index => Promise.resolve().then(() => this.ngZone.run(() => observer.next(index)))));
694 /** A stream that emits whenever the rendered range changes. */
695 this.renderedRangeStream = this._renderedRangeSubject;
696 /**
697 * The total size of all content (in pixels), including content that is not currently rendered.
698 */
699 this._totalContentSize = 0;
700 /** A string representing the `style.width` property value to be used for the spacer element. */
701 this._totalContentWidth = '';
702 /** A string representing the `style.height` property value to be used for the spacer element. */
703 this._totalContentHeight = '';
704 /** The currently rendered range of indices. */
705 this._renderedRange = { start: 0, end: 0 };
706 /** The length of the data bound to this viewport (in number of items). */
707 this._dataLength = 0;
708 /** The size of the viewport (in pixels). */
709 this._viewportSize = 0;
710 /** The last rendered content offset that was set. */
711 this._renderedContentOffset = 0;
712 /**
713 * Whether the last rendered content offset was to the end of the content (and therefore needs to
714 * be rewritten as an offset to the start of the content).
715 */
716 this._renderedContentOffsetNeedsRewrite = false;
717 /** Whether there is a pending change detection cycle. */
718 this._isChangeDetectionPending = false;
719 /** A list of functions to run after the next change detection cycle. */
720 this._runAfterChangeDetection = [];
721 /** Subscription to changes in the viewport size. */
722 this._viewportChanges = Subscription.EMPTY;
723 if (!_scrollStrategy && (typeof ngDevMode === 'undefined' || ngDevMode)) {
724 throw Error('Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.');
725 }
726 this._viewportChanges = viewportRuler.change().subscribe(() => {
727 this.checkViewportSize();
728 });
729 }
730 /** The direction the viewport scrolls. */
731 get orientation() {
732 return this._orientation;
733 }
734 set orientation(orientation) {
735 if (this._orientation !== orientation) {
736 this._orientation = orientation;
737 this._calculateSpacerSize();
738 }
739 }
740 /**
741 * Whether rendered items should persist in the DOM after scrolling out of view. By default, items
742 * will be removed.
743 */
744 get appendOnly() {
745 return this._appendOnly;
746 }
747 set appendOnly(value) {
748 this._appendOnly = coerceBooleanProperty(value);
749 }
750 ngOnInit() {
751 super.ngOnInit();
752 // It's still too early to measure the viewport at this point. Deferring with a promise allows
753 // the Viewport to be rendered with the correct size before we measure. We run this outside the
754 // zone to avoid causing more change detection cycles. We handle the change detection loop
755 // ourselves instead.
756 this.ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
757 this._measureViewportSize();
758 this._scrollStrategy.attach(this);
759 this.elementScrolled()
760 .pipe(
761 // Start off with a fake scroll event so we properly detect our initial position.
762 startWith(null),
763 // Collect multiple events into one until the next animation frame. This way if
764 // there are multiple scroll events in the same frame we only need to recheck
765 // our layout once.
766 auditTime(0, SCROLL_SCHEDULER))
767 .subscribe(() => this._scrollStrategy.onContentScrolled());
768 this._markChangeDetectionNeeded();
769 }));
770 }
771 ngOnDestroy() {
772 this.detach();
773 this._scrollStrategy.detach();
774 // Complete all subjects
775 this._renderedRangeSubject.complete();
776 this._detachedSubject.complete();
777 this._viewportChanges.unsubscribe();
778 super.ngOnDestroy();
779 }
780 /** Attaches a `CdkVirtualScrollRepeater` to this viewport. */
781 attach(forOf) {
782 if (this._forOf && (typeof ngDevMode === 'undefined' || ngDevMode)) {
783 throw Error('CdkVirtualScrollViewport is already attached.');
784 }
785 // Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length
786 // changes. Run outside the zone to avoid triggering change detection, since we're managing the
787 // change detection loop ourselves.
788 this.ngZone.runOutsideAngular(() => {
789 this._forOf = forOf;
790 this._forOf.dataStream.pipe(takeUntil(this._detachedSubject)).subscribe(data => {
791 const newLength = data.length;
792 if (newLength !== this._dataLength) {
793 this._dataLength = newLength;
794 this._scrollStrategy.onDataLengthChanged();
795 }
796 this._doChangeDetection();
797 });
798 });
799 }
800 /** Detaches the current `CdkVirtualForOf`. */
801 detach() {
802 this._forOf = null;
803 this._detachedSubject.next();
804 }
805 /** Gets the length of the data bound to this viewport (in number of items). */
806 getDataLength() {
807 return this._dataLength;
808 }
809 /** Gets the size of the viewport (in pixels). */
810 getViewportSize() {
811 return this._viewportSize;
812 }
813 // TODO(mmalerba): This is technically out of sync with what's really rendered until a render
814 // cycle happens. I'm being careful to only call it after the render cycle is complete and before
815 // setting it to something else, but its error prone and should probably be split into
816 // `pendingRange` and `renderedRange`, the latter reflecting whats actually in the DOM.
817 /** Get the current rendered range of items. */
818 getRenderedRange() {
819 return this._renderedRange;
820 }
821 /**
822 * Sets the total size of all content (in pixels), including content that is not currently
823 * rendered.
824 */
825 setTotalContentSize(size) {
826 if (this._totalContentSize !== size) {
827 this._totalContentSize = size;
828 this._calculateSpacerSize();
829 this._markChangeDetectionNeeded();
830 }
831 }
832 /** Sets the currently rendered range of indices. */
833 setRenderedRange(range) {
834 if (!rangesEqual(this._renderedRange, range)) {
835 if (this.appendOnly) {
836 range = { start: 0, end: Math.max(this._renderedRange.end, range.end) };
837 }
838 this._renderedRangeSubject.next(this._renderedRange = range);
839 this._markChangeDetectionNeeded(() => this._scrollStrategy.onContentRendered());
840 }
841 }
842 /**
843 * Gets the offset from the start of the viewport to the start of the rendered data (in pixels).
844 */
845 getOffsetToRenderedContentStart() {
846 return this._renderedContentOffsetNeedsRewrite ? null : this._renderedContentOffset;
847 }
848 /**
849 * Sets the offset from the start of the viewport to either the start or end of the rendered data
850 * (in pixels).
851 */
852 setRenderedContentOffset(offset, to = 'to-start') {
853 // For a horizontal viewport in a right-to-left language we need to translate along the x-axis
854 // in the negative direction.
855 const isRtl = this.dir && this.dir.value == 'rtl';
856 const isHorizontal = this.orientation == 'horizontal';
857 const axis = isHorizontal ? 'X' : 'Y';
858 const axisDirection = isHorizontal && isRtl ? -1 : 1;
859 let transform = `translate${axis}(${Number(axisDirection * offset)}px)`;
860 this._renderedContentOffset = offset;
861 if (to === 'to-end') {
862 transform += ` translate${axis}(-100%)`;
863 // The viewport should rewrite this as a `to-start` offset on the next render cycle. Otherwise
864 // elements will appear to expand in the wrong direction (e.g. `mat-expansion-panel` would
865 // expand upward).
866 this._renderedContentOffsetNeedsRewrite = true;
867 }
868 if (this._renderedContentTransform != transform) {
869 // We know this value is safe because we parse `offset` with `Number()` before passing it
870 // into the string.
871 this._renderedContentTransform = transform;
872 this._markChangeDetectionNeeded(() => {
873 if (this._renderedContentOffsetNeedsRewrite) {
874 this._renderedContentOffset -= this.measureRenderedContentSize();
875 this._renderedContentOffsetNeedsRewrite = false;
876 this.setRenderedContentOffset(this._renderedContentOffset);
877 }
878 else {
879 this._scrollStrategy.onRenderedOffsetChanged();
880 }
881 });
882 }
883 }
884 /**
885 * Scrolls to the given offset from the start of the viewport. Please note that this is not always
886 * the same as setting `scrollTop` or `scrollLeft`. In a horizontal viewport with right-to-left
887 * direction, this would be the equivalent of setting a fictional `scrollRight` property.
888 * @param offset The offset to scroll to.
889 * @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
890 */
891 scrollToOffset(offset, behavior = 'auto') {
892 const options = { behavior };
893 if (this.orientation === 'horizontal') {
894 options.start = offset;
895 }
896 else {
897 options.top = offset;
898 }
899 this.scrollTo(options);
900 }
901 /**
902 * Scrolls to the offset for the given index.
903 * @param index The index of the element to scroll to.
904 * @param behavior The ScrollBehavior to use when scrolling. Default is behavior is `auto`.
905 */
906 scrollToIndex(index, behavior = 'auto') {
907 this._scrollStrategy.scrollToIndex(index, behavior);
908 }
909 /**
910 * Gets the current scroll offset from the start of the viewport (in pixels).
911 * @param from The edge to measure the offset from. Defaults to 'top' in vertical mode and 'start'
912 * in horizontal mode.
913 */
914 measureScrollOffset(from) {
915 return from ?
916 super.measureScrollOffset(from) :
917 super.measureScrollOffset(this.orientation === 'horizontal' ? 'start' : 'top');
918 }
919 /** Measure the combined size of all of the rendered items. */
920 measureRenderedContentSize() {
921 const contentEl = this._contentWrapper.nativeElement;
922 return this.orientation === 'horizontal' ? contentEl.offsetWidth : contentEl.offsetHeight;
923 }
924 /**
925 * Measure the total combined size of the given range. Throws if the range includes items that are
926 * not rendered.
927 */
928 measureRangeSize(range) {
929 if (!this._forOf) {
930 return 0;
931 }
932 return this._forOf.measureRangeSize(range, this.orientation);
933 }
934 /** Update the viewport dimensions and re-render. */
935 checkViewportSize() {
936 // TODO: Cleanup later when add logic for handling content resize
937 this._measureViewportSize();
938 this._scrollStrategy.onDataLengthChanged();
939 }
940 /** Measure the viewport size. */
941 _measureViewportSize() {
942 const viewportEl = this.elementRef.nativeElement;
943 this._viewportSize = this.orientation === 'horizontal' ?
944 viewportEl.clientWidth : viewportEl.clientHeight;
945 }
946 /** Queue up change detection to run. */
947 _markChangeDetectionNeeded(runAfter) {
948 if (runAfter) {
949 this._runAfterChangeDetection.push(runAfter);
950 }
951 // Use a Promise to batch together calls to `_doChangeDetection`. This way if we set a bunch of
952 // properties sequentially we only have to run `_doChangeDetection` once at the end.
953 if (!this._isChangeDetectionPending) {
954 this._isChangeDetectionPending = true;
955 this.ngZone.runOutsideAngular(() => Promise.resolve().then(() => {
956 this._doChangeDetection();
957 }));
958 }
959 }
960 /** Run change detection. */
961 _doChangeDetection() {
962 this._isChangeDetectionPending = false;
963 // Apply the content transform. The transform can't be set via an Angular binding because
964 // bypassSecurityTrustStyle is banned in Google. However the value is safe, it's composed of
965 // string literals, a variable that can only be 'X' or 'Y', and user input that is run through
966 // the `Number` function first to coerce it to a numeric value.
967 this._contentWrapper.nativeElement.style.transform = this._renderedContentTransform;
968 // Apply changes to Angular bindings. Note: We must call `markForCheck` to run change detection
969 // from the root, since the repeated items are content projected in. Calling `detectChanges`
970 // instead does not properly check the projected content.
971 this.ngZone.run(() => this._changeDetectorRef.markForCheck());
972 const runAfterChangeDetection = this._runAfterChangeDetection;
973 this._runAfterChangeDetection = [];
974 for (const fn of runAfterChangeDetection) {
975 fn();
976 }
977 }
978 /** Calculates the `style.width` and `style.height` for the spacer element. */
979 _calculateSpacerSize() {
980 this._totalContentHeight =
981 this.orientation === 'horizontal' ? '' : `${this._totalContentSize}px`;
982 this._totalContentWidth =
983 this.orientation === 'horizontal' ? `${this._totalContentSize}px` : '';
984 }
985}
986CdkVirtualScrollViewport.decorators = [
987 { type: Component, args: [{
988 selector: 'cdk-virtual-scroll-viewport',
989 template: "<!--\n Wrap the rendered content in an element that will be used to offset it based on the scroll\n position.\n-->\n<div #contentWrapper class=\"cdk-virtual-scroll-content-wrapper\">\n <ng-content></ng-content>\n</div>\n<!--\n Spacer used to force the scrolling container to the correct size for the *total* number of items\n so that the scrollbar captures the size of the entire data set.\n-->\n<div class=\"cdk-virtual-scroll-spacer\"\n [style.width]=\"_totalContentWidth\" [style.height]=\"_totalContentHeight\"></div>\n",
990 host: {
991 'class': 'cdk-virtual-scroll-viewport',
992 '[class.cdk-virtual-scroll-orientation-horizontal]': 'orientation === "horizontal"',
993 '[class.cdk-virtual-scroll-orientation-vertical]': 'orientation !== "horizontal"',
994 },
995 encapsulation: ViewEncapsulation.None,
996 changeDetection: ChangeDetectionStrategy.OnPush,
997 providers: [{
998 provide: CdkScrollable,
999 useExisting: CdkVirtualScrollViewport,
1000 }],
1001 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:none}.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:none}.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}\n"]
1002 },] }
1003];
1004CdkVirtualScrollViewport.ctorParameters = () => [
1005 { type: ElementRef },
1006 { type: ChangeDetectorRef },
1007 { type: NgZone },
1008 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [VIRTUAL_SCROLL_STRATEGY,] }] },
1009 { type: Directionality, decorators: [{ type: Optional }] },
1010 { type: ScrollDispatcher },
1011 { type: ViewportRuler }
1012];
1013CdkVirtualScrollViewport.propDecorators = {
1014 orientation: [{ type: Input }],
1015 appendOnly: [{ type: Input }],
1016 scrolledIndexChange: [{ type: Output }],
1017 _contentWrapper: [{ type: ViewChild, args: ['contentWrapper', { static: true },] }]
1018};
1019
1020/**
1021 * @license
1022 * Copyright Google LLC All Rights Reserved.
1023 *
1024 * Use of this source code is governed by an MIT-style license that can be
1025 * found in the LICENSE file at https://angular.io/license
1026 */
1027/** Helper to extract the offset of a DOM Node in a certain direction. */
1028function getOffset(orientation, direction, node) {
1029 const el = node;
1030 if (!el.getBoundingClientRect) {
1031 return 0;
1032 }
1033 const rect = el.getBoundingClientRect();
1034 if (orientation === 'horizontal') {
1035 return direction === 'start' ? rect.left : rect.right;
1036 }
1037 return direction === 'start' ? rect.top : rect.bottom;
1038}
1039/**
1040 * A directive similar to `ngForOf` to be used for rendering data inside a virtual scrolling
1041 * container.
1042 */
1043class CdkVirtualForOf {
1044 constructor(
1045 /** The view container to add items to. */
1046 _viewContainerRef,
1047 /** The template to use when stamping out new items. */
1048 _template,
1049 /** The set of available differs. */
1050 _differs,
1051 /** The strategy used to render items in the virtual scroll viewport. */
1052 _viewRepeater,
1053 /** The virtual scrolling viewport that these items are being rendered in. */
1054 _viewport, ngZone) {
1055 this._viewContainerRef = _viewContainerRef;
1056 this._template = _template;
1057 this._differs = _differs;
1058 this._viewRepeater = _viewRepeater;
1059 this._viewport = _viewport;
1060 /** Emits when the rendered view of the data changes. */
1061 this.viewChange = new Subject();
1062 /** Subject that emits when a new DataSource instance is given. */
1063 this._dataSourceChanges = new Subject();
1064 /** Emits whenever the data in the current DataSource changes. */
1065 this.dataStream = this._dataSourceChanges
1066 .pipe(
1067 // Start off with null `DataSource`.
1068 startWith(null),
1069 // Bundle up the previous and current data sources so we can work with both.
1070 pairwise(),
1071 // Use `_changeDataSource` to disconnect from the previous data source and connect to the
1072 // new one, passing back a stream of data changes which we run through `switchMap` to give
1073 // us a data stream that emits the latest data from whatever the current `DataSource` is.
1074 switchMap(([prev, cur]) => this._changeDataSource(prev, cur)),
1075 // Replay the last emitted data when someone subscribes.
1076 shareReplay(1));
1077 /** The differ used to calculate changes to the data. */
1078 this._differ = null;
1079 /** Whether the rendered data should be updated during the next ngDoCheck cycle. */
1080 this._needsUpdate = false;
1081 this._destroyed = new Subject();
1082 this.dataStream.subscribe(data => {
1083 this._data = data;
1084 this._onRenderedDataChange();
1085 });
1086 this._viewport.renderedRangeStream.pipe(takeUntil(this._destroyed)).subscribe(range => {
1087 this._renderedRange = range;
1088 ngZone.run(() => this.viewChange.next(this._renderedRange));
1089 this._onRenderedDataChange();
1090 });
1091 this._viewport.attach(this);
1092 }
1093 /** The DataSource to display. */
1094 get cdkVirtualForOf() {
1095 return this._cdkVirtualForOf;
1096 }
1097 set cdkVirtualForOf(value) {
1098 this._cdkVirtualForOf = value;
1099 if (isDataSource(value)) {
1100 this._dataSourceChanges.next(value);
1101 }
1102 else {
1103 // If value is an an NgIterable, convert it to an array.
1104 this._dataSourceChanges.next(new ArrayDataSource(isObservable(value) ? value : Array.from(value || [])));
1105 }
1106 }
1107 /**
1108 * The `TrackByFunction` to use for tracking changes. The `TrackByFunction` takes the index and
1109 * the item and produces a value to be used as the item's identity when tracking changes.
1110 */
1111 get cdkVirtualForTrackBy() {
1112 return this._cdkVirtualForTrackBy;
1113 }
1114 set cdkVirtualForTrackBy(fn) {
1115 this._needsUpdate = true;
1116 this._cdkVirtualForTrackBy = fn ?
1117 (index, item) => fn(index + (this._renderedRange ? this._renderedRange.start : 0), item) :
1118 undefined;
1119 }
1120 /** The template used to stamp out new elements. */
1121 set cdkVirtualForTemplate(value) {
1122 if (value) {
1123 this._needsUpdate = true;
1124 this._template = value;
1125 }
1126 }
1127 /**
1128 * The size of the cache used to store templates that are not being used for re-use later.
1129 * Setting the cache size to `0` will disable caching. Defaults to 20 templates.
1130 */
1131 get cdkVirtualForTemplateCacheSize() {
1132 return this._viewRepeater.viewCacheSize;
1133 }
1134 set cdkVirtualForTemplateCacheSize(size) {
1135 this._viewRepeater.viewCacheSize = coerceNumberProperty(size);
1136 }
1137 /**
1138 * Measures the combined size (width for horizontal orientation, height for vertical) of all items
1139 * in the specified range. Throws an error if the range includes items that are not currently
1140 * rendered.
1141 */
1142 measureRangeSize(range, orientation) {
1143 if (range.start >= range.end) {
1144 return 0;
1145 }
1146 if ((range.start < this._renderedRange.start || range.end > this._renderedRange.end) &&
1147 (typeof ngDevMode === 'undefined' || ngDevMode)) {
1148 throw Error(`Error: attempted to measure an item that isn't rendered.`);
1149 }
1150 // The index into the list of rendered views for the first item in the range.
1151 const renderedStartIndex = range.start - this._renderedRange.start;
1152 // The length of the range we're measuring.
1153 const rangeLen = range.end - range.start;
1154 // Loop over all the views, find the first and land node and compute the size by subtracting
1155 // the top of the first node from the bottom of the last one.
1156 let firstNode;
1157 let lastNode;
1158 // Find the first node by starting from the beginning and going forwards.
1159 for (let i = 0; i < rangeLen; i++) {
1160 const view = this._viewContainerRef.get(i + renderedStartIndex);
1161 if (view && view.rootNodes.length) {
1162 firstNode = lastNode = view.rootNodes[0];
1163 break;
1164 }
1165 }
1166 // Find the last node by starting from the end and going backwards.
1167 for (let i = rangeLen - 1; i > -1; i--) {
1168 const view = this._viewContainerRef.get(i + renderedStartIndex);
1169 if (view && view.rootNodes.length) {
1170 lastNode = view.rootNodes[view.rootNodes.length - 1];
1171 break;
1172 }
1173 }
1174 return firstNode && lastNode ?
1175 getOffset(orientation, 'end', lastNode) - getOffset(orientation, 'start', firstNode) : 0;
1176 }
1177 ngDoCheck() {
1178 if (this._differ && this._needsUpdate) {
1179 // TODO(mmalerba): We should differentiate needs update due to scrolling and a new portion of
1180 // this list being rendered (can use simpler algorithm) vs needs update due to data actually
1181 // changing (need to do this diff).
1182 const changes = this._differ.diff(this._renderedItems);
1183 if (!changes) {
1184 this._updateContext();
1185 }
1186 else {
1187 this._applyChanges(changes);
1188 }
1189 this._needsUpdate = false;
1190 }
1191 }
1192 ngOnDestroy() {
1193 this._viewport.detach();
1194 this._dataSourceChanges.next(undefined);
1195 this._dataSourceChanges.complete();
1196 this.viewChange.complete();
1197 this._destroyed.next();
1198 this._destroyed.complete();
1199 this._viewRepeater.detach();
1200 }
1201 /** React to scroll state changes in the viewport. */
1202 _onRenderedDataChange() {
1203 if (!this._renderedRange) {
1204 return;
1205 }
1206 this._renderedItems = this._data.slice(this._renderedRange.start, this._renderedRange.end);
1207 if (!this._differ) {
1208 // Use a wrapper function for the `trackBy` so any new values are
1209 // picked up automatically without having to recreate the differ.
1210 this._differ = this._differs.find(this._renderedItems).create((index, item) => {
1211 return this.cdkVirtualForTrackBy ? this.cdkVirtualForTrackBy(index, item) : item;
1212 });
1213 }
1214 this._needsUpdate = true;
1215 }
1216 /** Swap out one `DataSource` for another. */
1217 _changeDataSource(oldDs, newDs) {
1218 if (oldDs) {
1219 oldDs.disconnect(this);
1220 }
1221 this._needsUpdate = true;
1222 return newDs ? newDs.connect(this) : of();
1223 }
1224 /** Update the `CdkVirtualForOfContext` for all views. */
1225 _updateContext() {
1226 const count = this._data.length;
1227 let i = this._viewContainerRef.length;
1228 while (i--) {
1229 const view = this._viewContainerRef.get(i);
1230 view.context.index = this._renderedRange.start + i;
1231 view.context.count = count;
1232 this._updateComputedContextProperties(view.context);
1233 view.detectChanges();
1234 }
1235 }
1236 /** Apply changes to the DOM. */
1237 _applyChanges(changes) {
1238 this._viewRepeater.applyChanges(changes, this._viewContainerRef, (record, _adjustedPreviousIndex, currentIndex) => this._getEmbeddedViewArgs(record, currentIndex), (record) => record.item);
1239 // Update $implicit for any items that had an identity change.
1240 changes.forEachIdentityChange((record) => {
1241 const view = this._viewContainerRef.get(record.currentIndex);
1242 view.context.$implicit = record.item;
1243 });
1244 // Update the context variables on all items.
1245 const count = this._data.length;
1246 let i = this._viewContainerRef.length;
1247 while (i--) {
1248 const view = this._viewContainerRef.get(i);
1249 view.context.index = this._renderedRange.start + i;
1250 view.context.count = count;
1251 this._updateComputedContextProperties(view.context);
1252 }
1253 }
1254 /** Update the computed properties on the `CdkVirtualForOfContext`. */
1255 _updateComputedContextProperties(context) {
1256 context.first = context.index === 0;
1257 context.last = context.index === context.count - 1;
1258 context.even = context.index % 2 === 0;
1259 context.odd = !context.even;
1260 }
1261 _getEmbeddedViewArgs(record, index) {
1262 // Note that it's important that we insert the item directly at the proper index,
1263 // rather than inserting it and the moving it in place, because if there's a directive
1264 // on the same node that injects the `ViewContainerRef`, Angular will insert another
1265 // comment node which can throw off the move when it's being repeated for all items.
1266 return {
1267 templateRef: this._template,
1268 context: {
1269 $implicit: record.item,
1270 // It's guaranteed that the iterable is not "undefined" or "null" because we only
1271 // generate views for elements if the "cdkVirtualForOf" iterable has elements.
1272 cdkVirtualForOf: this._cdkVirtualForOf,
1273 index: -1,
1274 count: -1,
1275 first: false,
1276 last: false,
1277 odd: false,
1278 even: false
1279 },
1280 index,
1281 };
1282 }
1283}
1284CdkVirtualForOf.decorators = [
1285 { type: Directive, args: [{
1286 selector: '[cdkVirtualFor][cdkVirtualForOf]',
1287 providers: [
1288 { provide: _VIEW_REPEATER_STRATEGY, useClass: _RecycleViewRepeaterStrategy },
1289 ]
1290 },] }
1291];
1292CdkVirtualForOf.ctorParameters = () => [
1293 { type: ViewContainerRef },
1294 { type: TemplateRef },
1295 { type: IterableDiffers },
1296 { type: _RecycleViewRepeaterStrategy, decorators: [{ type: Inject, args: [_VIEW_REPEATER_STRATEGY,] }] },
1297 { type: CdkVirtualScrollViewport, decorators: [{ type: SkipSelf }] },
1298 { type: NgZone }
1299];
1300CdkVirtualForOf.propDecorators = {
1301 cdkVirtualForOf: [{ type: Input }],
1302 cdkVirtualForTrackBy: [{ type: Input }],
1303 cdkVirtualForTemplate: [{ type: Input }],
1304 cdkVirtualForTemplateCacheSize: [{ type: Input }]
1305};
1306
1307/**
1308 * @license
1309 * Copyright Google LLC All Rights Reserved.
1310 *
1311 * Use of this source code is governed by an MIT-style license that can be
1312 * found in the LICENSE file at https://angular.io/license
1313 */
1314class CdkScrollableModule {
1315}
1316CdkScrollableModule.decorators = [
1317 { type: NgModule, args: [{
1318 exports: [CdkScrollable],
1319 declarations: [CdkScrollable]
1320 },] }
1321];
1322/**
1323 * @docs-primary-export
1324 */
1325class ScrollingModule {
1326}
1327ScrollingModule.decorators = [
1328 { type: NgModule, args: [{
1329 imports: [
1330 BidiModule,
1331 PlatformModule,
1332 CdkScrollableModule
1333 ],
1334 exports: [
1335 BidiModule,
1336 CdkScrollableModule,
1337 CdkFixedSizeVirtualScroll,
1338 CdkVirtualForOf,
1339 CdkVirtualScrollViewport,
1340 ],
1341 declarations: [
1342 CdkFixedSizeVirtualScroll,
1343 CdkVirtualForOf,
1344 CdkVirtualScrollViewport,
1345 ],
1346 },] }
1347];
1348
1349/**
1350 * @license
1351 * Copyright Google LLC All Rights Reserved.
1352 *
1353 * Use of this source code is governed by an MIT-style license that can be
1354 * found in the LICENSE file at https://angular.io/license
1355 */
1356
1357/**
1358 * @license
1359 * Copyright Google LLC All Rights Reserved.
1360 *
1361 * Use of this source code is governed by an MIT-style license that can be
1362 * found in the LICENSE file at https://angular.io/license
1363 */
1364
1365/**
1366 * Generated bundle index. Do not edit.
1367 */
1368
1369export { CdkFixedSizeVirtualScroll, CdkScrollable, CdkScrollableModule, CdkVirtualForOf, CdkVirtualScrollViewport, DEFAULT_RESIZE_TIME, DEFAULT_SCROLL_TIME, FixedSizeVirtualScrollStrategy, ScrollDispatcher, ScrollingModule, VIRTUAL_SCROLL_STRATEGY, ViewportRuler, _fixedSizeVirtualScrollStrategyFactory };
1370//# sourceMappingURL=scrolling.js.map
Note: See TracBrowser for help on using the repository browser.