scrolling.mjs 77 KB

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