ng-virtual-list 16.0.2 → 16.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +7 -7
  2. package/esm2022/lib/components/ng-virtual-list-item.component.mjs +2 -2
  3. package/esm2022/lib/const/index.mjs +2 -1
  4. package/esm2022/lib/enums/direction.mjs +1 -1
  5. package/esm2022/lib/enums/directions.mjs +2 -2
  6. package/esm2022/lib/models/collection.model.mjs +1 -1
  7. package/esm2022/lib/models/index.mjs +1 -1
  8. package/esm2022/lib/models/item.model.mjs +1 -1
  9. package/esm2022/lib/models/render-collection.model.mjs +1 -1
  10. package/esm2022/lib/models/render-item-config.model.mjs +1 -1
  11. package/esm2022/lib/models/render-item.model.mjs +1 -1
  12. package/esm2022/lib/models/scroll-direction.model.mjs +2 -0
  13. package/esm2022/lib/models/scroll-event.model.mjs +2 -0
  14. package/esm2022/lib/models/sticky-map.model.mjs +1 -1
  15. package/esm2022/lib/ng-virtual-list.component.mjs +45 -48
  16. package/esm2022/lib/types/id.mjs +1 -1
  17. package/esm2022/lib/types/rect.mjs +1 -1
  18. package/esm2022/lib/types/size.mjs +1 -1
  19. package/esm2022/lib/utils/cacheMap.mjs +43 -2
  20. package/esm2022/lib/utils/debounce.mjs +2 -2
  21. package/esm2022/lib/utils/eventEmitter.mjs +2 -2
  22. package/esm2022/lib/utils/isDirection.mjs +2 -2
  23. package/esm2022/lib/utils/toggleClassName.mjs +2 -2
  24. package/esm2022/lib/utils/trackBox.mjs +56 -22
  25. package/esm2022/lib/utils/tracker.mjs +2 -2
  26. package/fesm2022/ng-virtual-list.mjs +147 -75
  27. package/fesm2022/ng-virtual-list.mjs.map +1 -1
  28. package/lib/components/ng-virtual-list-item.component.d.ts +1 -1
  29. package/lib/const/index.d.ts +1 -0
  30. package/lib/enums/direction.d.ts +1 -1
  31. package/lib/enums/directions.d.ts +1 -1
  32. package/lib/models/collection.model.d.ts +1 -1
  33. package/lib/models/index.d.ts +3 -1
  34. package/lib/models/item.model.d.ts +1 -1
  35. package/lib/models/render-collection.model.d.ts +1 -1
  36. package/lib/models/render-item-config.model.d.ts +1 -1
  37. package/lib/models/render-item.model.d.ts +1 -1
  38. package/lib/models/scroll-direction.model.d.ts +5 -0
  39. package/lib/models/scroll-event.model.d.ts +18 -0
  40. package/lib/models/sticky-map.model.d.ts +1 -1
  41. package/lib/ng-virtual-list.component.d.ts +7 -10
  42. package/lib/types/id.d.ts +1 -1
  43. package/lib/types/rect.d.ts +1 -1
  44. package/lib/types/size.d.ts +1 -1
  45. package/lib/utils/cacheMap.d.ts +11 -1
  46. package/lib/utils/debounce.d.ts +1 -1
  47. package/lib/utils/eventEmitter.d.ts +1 -1
  48. package/lib/utils/isDirection.d.ts +1 -1
  49. package/lib/utils/toggleClassName.d.ts +1 -1
  50. package/lib/utils/trackBox.d.ts +4 -4
  51. package/lib/utils/tracker.d.ts +1 -1
  52. package/package.json +1 -1
@@ -3,11 +3,11 @@ import { Component, ChangeDetectionStrategy, EventEmitter as EventEmitter$1, Vie
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
- import { BehaviorSubject, filter, map, tap, combineLatest, distinctUntilChanged, debounceTime, switchMap, of } from 'rxjs';
6
+ import { BehaviorSubject, filter, map, tap, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
7
7
 
8
8
  /**
9
9
  * Axis of the arrangement of virtual list elements.
10
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/enums/directions.ts
10
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/enums/directions.ts
11
11
  * @author Evgenii Grebennikov
12
12
  * @email djonnyx@gmail.com
13
13
  */
@@ -32,6 +32,7 @@ const DEFAULT_DYNAMIC_SIZE = false;
32
32
  const TRACK_BY_PROPERTY_NAME = 'id';
33
33
  const DEFAULT_DIRECTION = Directions.VERTICAL;
34
34
  const DISPLAY_OBJECTS_LENGTH_MESUREMENT_ERROR = 1;
35
+ const MAX_SCROLL_TO_ITERATIONS = 5;
35
36
  // presets
36
37
  const BEHAVIOR_AUTO = 'auto';
37
38
  const BEHAVIOR_INSTANT = 'instant';
@@ -58,7 +59,7 @@ const CLASS_LIST_HORIZONTAL = 'horizontal';
58
59
 
59
60
  /**
60
61
  * Virtual list item component
61
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/components/ng-virtual-list-item.component.ts
62
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/components/ng-virtual-list-item.component.ts
62
63
  * @author Evgenii Grebennikov
63
64
  * @email djonnyx@gmail.com
64
65
  */
@@ -140,7 +141,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
140
141
  const HORIZONTAL_ALIASES = [Directions.HORIZONTAL, 'horizontal'], VERTICAL_ALIASES = [Directions.VERTICAL, 'vertical'];
141
142
  /**
142
143
  * Determines the axis membership of a virtual list
143
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/isDirection.ts
144
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/isDirection.ts
144
145
  * @author Evgenii Grebennikov
145
146
  * @email djonnyx@gmail.com
146
147
  */
@@ -153,7 +154,7 @@ const isDirection = (src, expected) => {
153
154
 
154
155
  /**
155
156
  * Simple debounce function.
156
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/debounce.ts
157
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/debounce.ts
157
158
  * @author Evgenii Grebennikov
158
159
  * @email djonnyx@gmail.com
159
160
  */
@@ -184,7 +185,7 @@ const debounce = (cb, debounceTime = 0) => {
184
185
 
185
186
  /**
186
187
  * Switch css classes
187
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/toggleClassName.ts
188
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/toggleClassName.ts
188
189
  * @author Evgenii Grebennikov
189
190
  * @email djonnyx@gmail.com
190
191
  */
@@ -199,7 +200,7 @@ const toggleClassName = (el, className, remove = false) => {
199
200
 
200
201
  /**
201
202
  * Tracks display items by property
202
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/tracker.ts
203
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/tracker.ts
203
204
  * @author Evgenii Grebennikov
204
205
  * @email djonnyx@gmail.com
205
206
  */
@@ -308,7 +309,7 @@ class Tracker {
308
309
 
309
310
  /**
310
311
  * Simple event emitter
311
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/eventEmitter.ts
312
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/eventEmitter.ts
312
313
  * @author Evgenii Grebennikov
313
314
  * @email djonnyx@gmail.com
314
315
  */
@@ -411,10 +412,11 @@ class EventEmitter {
411
412
  }
412
413
  }
413
414
 
415
+ const MAX_SCROLL_DIRECTION_POOL = 10, CLEAR_SCROLL_DIRECTION_TO = 0;
414
416
  /**
415
417
  * Cache map.
416
418
  * Emits a change event on each mutation.
417
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/cacheMap.ts
419
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/cacheMap.ts
418
420
  * @author Evgenii Grebennikov
419
421
  * @email djonnyx@gmail.com
420
422
  */
@@ -426,12 +428,51 @@ class CacheMap extends EventEmitter {
426
428
  get delta() {
427
429
  return this._delta;
428
430
  }
431
+ _deltaDirection = 1;
432
+ set deltaDirection(v) {
433
+ this._deltaDirection = v;
434
+ this._scrollDirection = this.calcScrollDirection(v);
435
+ }
436
+ get deltaDirection() {
437
+ return this._deltaDirection;
438
+ }
439
+ _scrollDirectionCache = [];
440
+ _scrollDirection = 1;
441
+ get scrollDirection() {
442
+ return this._scrollDirection;
443
+ }
429
444
  get version() {
430
445
  return this._version;
431
446
  }
447
+ _clearScrollDirectionDebounce = debounce(() => {
448
+ while (this._scrollDirectionCache.length > CLEAR_SCROLL_DIRECTION_TO) {
449
+ this._scrollDirectionCache.shift();
450
+ }
451
+ }, 10);
432
452
  constructor() {
433
453
  super();
434
454
  }
455
+ clearScrollDirectionCache() {
456
+ this._clearScrollDirectionDebounce.execute();
457
+ }
458
+ calcScrollDirection(v) {
459
+ while (this._scrollDirectionCache.length >= MAX_SCROLL_DIRECTION_POOL) {
460
+ this._scrollDirectionCache.shift();
461
+ }
462
+ this._scrollDirectionCache.push(v);
463
+ const dict = { [-1]: 0, [0]: 0, [1]: 0 };
464
+ for (let i = 0, l = this._scrollDirectionCache.length; i < l; i++) {
465
+ const dir = this._scrollDirectionCache[i];
466
+ dict[dir] += 1;
467
+ }
468
+ if (dict[-1] > dict[1]) {
469
+ return -1;
470
+ }
471
+ else if (dict[1] > dict[-1]) {
472
+ return 1;
473
+ }
474
+ return -1;
475
+ }
435
476
  bumpVersion() {
436
477
  this._version = this._version === Number.MAX_SAFE_INTEGER ? 0 : this._version + 1;
437
478
  }
@@ -465,7 +506,7 @@ class CacheMap extends EventEmitter {
465
506
  const TRACK_BOX_CHANGE_EVENT_NAME = 'change';
466
507
  /**
467
508
  * An object that performs tracking, calculations and caching.
468
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/trackBox.ts
509
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/trackBox.ts
469
510
  * @author Evgenii Grebennikov
470
511
  * @email djonnyx@gmail.com
471
512
  */
@@ -506,12 +547,12 @@ class TrackBox extends CacheMap {
506
547
  this._debounceChanges.execute(this._version);
507
548
  }
508
549
  getItemPosition(id, stickyMap, options) {
509
- const opt = { fromItemId: id, stickyMap, scrollDirection: undefined, ...options };
550
+ const opt = { fromItemId: id, stickyMap, ...options };
510
551
  const { scrollSize } = this.recalculateMetrics(opt);
511
552
  return scrollSize;
512
553
  }
513
554
  updateCollection(items, stickyMap, options) {
514
- const opt = { stickyMap, scrollDirection: undefined, ...options };
555
+ const opt = { stickyMap, ...options };
515
556
  this.cacheElements();
516
557
  const metrics = this.recalculateMetrics({
517
558
  ...opt,
@@ -520,14 +561,35 @@ class TrackBox extends CacheMap {
520
561
  const displayItems = this.generateDisplayCollection(items, stickyMap, metrics);
521
562
  return { displayItems, totalSize: metrics.totalSize, delta: metrics.delta };
522
563
  }
564
+ getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical) {
565
+ const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME;
566
+ let offset = 0, num = 0;
567
+ for (let j = collection.length - 1; j >= i; j--) {
568
+ const item = collection[j];
569
+ let itemSize = 0;
570
+ if (map.has(item.id)) {
571
+ const bounds = map.get(item.id);
572
+ itemSize = bounds ? bounds[sizeProperty] : typicalItemSize;
573
+ }
574
+ else {
575
+ itemSize = typicalItemSize;
576
+ }
577
+ offset += itemSize;
578
+ num++;
579
+ if (offset > size) {
580
+ return { num: 0, offset };
581
+ }
582
+ }
583
+ return { num, offset };
584
+ }
523
585
  /**
524
586
  * Calculates list metrics
525
587
  */
526
588
  recalculateMetrics(options) {
527
- const { scrollDirection = 0, fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap } = options;
528
- const { width, height } = bounds, sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, size = isVertical ? height : width, totalLength = collection.length, typicalItemSize = itemSize, w = isVertical ? width : typicalItemSize, h = isVertical ? typicalItemSize : height, totalSize = dynamicSize ? this.getBoundsFromCache(collection, typicalItemSize, isVertical) : totalLength * typicalItemSize, snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
589
+ const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap } = options;
590
+ const { width, height } = bounds, sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, size = isVertical ? height : width, totalLength = collection.length, typicalItemSize = itemSize, w = isVertical ? width : typicalItemSize, h = isVertical ? typicalItemSize : height, snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
529
591
  || (typeof fromItemId === 'string' && fromItemId > '-1');
530
- let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, itemById = undefined, itemByIdPos = 0, lastDisplayItemId = undefined, actualScrollSize = itemByIdPos, fullHeight = 0, startIndex;
592
+ let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, itemById = undefined, itemByIdPos = 0, lastDisplayItemId = undefined, actualScrollSize = itemByIdPos, totalSize = 0, startIndex;
531
593
  if (dynamicSize) {
532
594
  let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
533
595
  for (let i = 0, l = collection.length; i < l; i++) {
@@ -540,21 +602,31 @@ class TrackBox extends CacheMap {
540
602
  else {
541
603
  componentSize = typicalItemSize;
542
604
  }
605
+ totalSize += componentSize;
543
606
  if (isFromId) {
544
607
  if (itemById === undefined) {
545
- leftItemsWeights.push(componentSize);
546
- leftHiddenItemsWeight += componentSize;
547
- itemsFromStartToScrollEnd = ii;
548
608
  if (stickyMap && stickyMap[collectionItem.id] > 0) {
549
609
  stickyComponentSize = componentSize;
550
610
  stickyCollectionItem = collectionItem;
551
611
  }
552
612
  if (collectionItem.id === fromItemId) {
553
- itemById = collectionItem;
554
- itemByIdPos = y;
555
613
  if (stickyCollectionItem && stickyMap && stickyMap[stickyCollectionItem.id] > 0) {
556
- itemByIdPos = itemByIdPos - stickyComponentSize;
614
+ const { num } = this.getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical);
615
+ if (num > 0) {
616
+ y -= size - componentSize;
617
+ }
618
+ else {
619
+ y -= stickyComponentSize;
620
+ leftHiddenItemsWeight -= stickyComponentSize;
621
+ }
557
622
  }
623
+ itemById = collectionItem;
624
+ itemByIdPos = y;
625
+ }
626
+ else {
627
+ leftItemsWeights.push(componentSize);
628
+ leftHiddenItemsWeight += componentSize;
629
+ itemsFromStartToScrollEnd = ii;
558
630
  }
559
631
  }
560
632
  }
@@ -582,7 +654,6 @@ class TrackBox extends CacheMap {
582
654
  }
583
655
  y += componentSize;
584
656
  }
585
- fullHeight = y;
586
657
  if (itemsFromStartToScrollEnd === -1) {
587
658
  itemsFromStartToScrollEnd = 0;
588
659
  }
@@ -608,12 +679,12 @@ class TrackBox extends CacheMap {
608
679
  rightItemsWeight = rightItemLength * typicalItemSize,
609
680
  leftHiddenItemsWeight = itemsFromStartToScrollEnd * typicalItemSize,
610
681
  totalItemsToDisplayEndWeight = itemsFromStartToDisplayEnd * typicalItemSize;
611
- actualScrollSize = scrollSize,
612
- fullHeight = totalLength * typicalItemSize;
682
+ actualScrollSize = scrollSize;
683
+ totalSize = totalLength * typicalItemSize;
613
684
  }
614
685
  startIndex = Math.min(itemsFromStartToScrollEnd - leftItemLength, totalLength > 0 ? totalLength - 1 : 0);
615
- const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = fullHeight - this._previouseFullHeigh;
616
- if (scrollDirection === -1) {
686
+ const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = totalSize - this._previouseFullHeigh;
687
+ if (this.scrollDirection === -1) {
617
688
  this._delta += delta;
618
689
  }
619
690
  const metrics = {
@@ -646,13 +717,17 @@ class TrackBox extends CacheMap {
646
717
  totalSize,
647
718
  typicalItemSize,
648
719
  };
649
- if (scrollDirection !== 0) {
650
- this._previouseFullHeigh = fullHeight;
651
- }
720
+ this._previouseFullHeigh = totalSize;
652
721
  return metrics;
653
722
  }
654
- clearDelta() {
723
+ clearDeltaDirection() {
724
+ this.clearScrollDirectionCache();
725
+ }
726
+ clearDelta(clearDirectionDetector = false) {
655
727
  this._delta = 0;
728
+ if (clearDirectionDetector) {
729
+ this.clearScrollDirectionCache();
730
+ }
656
731
  }
657
732
  generateDisplayCollection(items, stickyMap, metrics) {
658
733
  const {
@@ -817,7 +892,7 @@ class TrackBox extends CacheMap {
817
892
  * Virtual list component.
818
893
  * Maximum performance for extremely large lists.
819
894
  * It is based on algorithms for virtualization of screen objects.
820
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/ng-virtual-list.component.ts
895
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/ng-virtual-list.component.ts
821
896
  * @author Evgenii Grebennikov
822
897
  * @email djonnyx@gmail.com
823
898
  */
@@ -976,27 +1051,20 @@ class NgVirtualListComponent {
976
1051
  }
977
1052
  ;
978
1053
  get itemsOffset() { return this._$itemsOffset.getValue(); }
979
- _scrollToTimeout;
980
1054
  _isVertical = this.getIsVertical();
981
1055
  _displayComponents = [];
982
1056
  _$bounds = new BehaviorSubject(null);
983
1057
  _$scrollSize = new BehaviorSubject(0);
984
1058
  _resizeObserver = null;
985
- /**
986
- * only dynamic
987
- */
988
- _$scrolledItemId = new BehaviorSubject(undefined);
989
1059
  _onResizeHandler = () => {
990
1060
  this._$bounds.next(this._container?.nativeElement?.getBoundingClientRect() ?? null);
991
1061
  };
992
- _scrollDirection = 0;
993
1062
  _onScrollHandler = (e) => {
994
- this._$scrolledItemId.next(undefined);
995
1063
  const container = this._container?.nativeElement;
996
1064
  if (container) {
997
1065
  const dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft), previouseScrollSize = this._$scrollSize.getValue();
998
1066
  let actualScrollSize = scrollSize;
999
- this._scrollDirection = previouseScrollSize > scrollSize ? -1 : 1;
1067
+ this._trackBox.deltaDirection = previouseScrollSize > scrollSize ? -1 : 1;
1000
1068
  if (dynamicSize && delta !== 0) {
1001
1069
  actualScrollSize = scrollSize + delta;
1002
1070
  const params = {
@@ -1010,7 +1078,7 @@ class NgVirtualListComponent {
1010
1078
  }
1011
1079
  }
1012
1080
  this._$scrollSize.next(actualScrollSize);
1013
- this.onScroll.emit(actualScrollSize);
1081
+ this.onScroll.emit({ scrollSize: actualScrollSize, direction: this._trackBox.scrollDirection });
1014
1082
  }
1015
1083
  };
1016
1084
  scrollImmediately(container, params) {
@@ -1037,9 +1105,11 @@ class NgVirtualListComponent {
1037
1105
  container.nativeElement.removeEventListener(SCROLL_END, this._scrollImmediatelyHandler);
1038
1106
  }
1039
1107
  }
1040
- _onScrollEndHandler = (e, fireEvent = true) => {
1108
+ _onScrollEndHandler = (e) => {
1041
1109
  const container = this._container;
1042
1110
  if (container) {
1111
+ this._trackBox.clearDelta();
1112
+ this._trackBox.clearDeltaDirection();
1043
1113
  const itemSize = this.itemSize, snapToItem = this.snapToItem, dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
1044
1114
  let actualScrollSize = scrollSize;
1045
1115
  if (dynamicSize && delta !== 0) {
@@ -1049,7 +1119,6 @@ class NgVirtualListComponent {
1049
1119
  [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1050
1120
  behavior: BEHAVIOR_INSTANT
1051
1121
  };
1052
- this._trackBox.clearDelta();
1053
1122
  this._$scrollSize.next(actualScrollSize);
1054
1123
  container.nativeElement.scroll(params);
1055
1124
  return;
@@ -1067,9 +1136,7 @@ class NgVirtualListComponent {
1067
1136
  }
1068
1137
  }
1069
1138
  this._$scrollSize.next(actualScrollSize);
1070
- if (fireEvent) {
1071
- this.onScrollEnd.emit(actualScrollSize);
1072
- }
1139
+ this.onScroll.emit({ scrollSize: actualScrollSize, direction: this._trackBox.scrollDirection });
1073
1140
  }
1074
1141
  };
1075
1142
  _$initialized = new BehaviorSubject(false);
@@ -1092,7 +1159,7 @@ class NgVirtualListComponent {
1092
1159
  this._$initialized = new BehaviorSubject(false);
1093
1160
  this.$initialized = this._$initialized.asObservable();
1094
1161
  this._trackBox.displayComponents = this._displayComponents;
1095
- const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = this.$itemsOffset.pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = this.$stickyMap.pipe(map(v => !v ? {} : v)), $snap = this.$snap, $isVertical = this.$direction.pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = this.$dynamicSize, $cacheVersion = this.$cacheVersion, $scrolledItemId = this._$scrolledItemId.asObservable();
1162
+ const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = this.$itemsOffset.pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = this.$stickyMap.pipe(map(v => !v ? {} : v)), $snap = this.$snap, $isVertical = this.$direction.pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = this.$dynamicSize, $cacheVersion = this.$cacheVersion;
1096
1163
  $isVertical.pipe(takeUntilDestroyed(), tap(v => {
1097
1164
  this._isVertical = v;
1098
1165
  const el = this._elementRef.nativeElement;
@@ -1101,33 +1168,21 @@ class NgVirtualListComponent {
1101
1168
  $dynamicSize.pipe(takeUntilDestroyed(), tap(dynamicSize => {
1102
1169
  this.listenCacheChangesIfNeed(dynamicSize);
1103
1170
  })).subscribe();
1104
- combineLatest([this.$initialized, $scrolledItemId, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1171
+ combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1105
1172
  $itemsOffset, $snap, $isVertical, $dynamicSize, $cacheVersion,
1106
- ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), debounceTime(0), filter(([initialized]) => !!initialized), switchMap(([, scrolledItemId, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, cacheVersion,]) => {
1173
+ ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, cacheVersion,]) => {
1107
1174
  const { width, height } = bounds;
1108
1175
  let actualScrollSize = scrollSize;
1109
1176
  const opts = {
1110
1177
  bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
1111
- itemsOffset, scrollSize: scrollSize, snap, fromItemId: scrolledItemId,
1178
+ itemsOffset, scrollSize: scrollSize, snap,
1112
1179
  };
1113
- if (dynamicSize && scrolledItemId !== undefined) {
1114
- const scrollSize = this._trackBox.getItemPosition(scrolledItemId, stickyMap, { ...opts, scrollSize: actualScrollSize });
1115
- actualScrollSize = scrollSize;
1116
- this._$scrollSize.next(actualScrollSize);
1117
- }
1118
- const scrollDirection = this._scrollDirection, { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1119
- ...opts, scrollSize: actualScrollSize, scrollDirection
1180
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1181
+ ...opts, scrollSize: actualScrollSize,
1120
1182
  });
1121
1183
  this.resetBoundsSize(isVertical, totalSize);
1122
1184
  this.createDisplayComponentsIfNeed(displayItems);
1123
1185
  this.tracking();
1124
- if (dynamicSize && scrolledItemId !== undefined) {
1125
- const container = this._container;
1126
- if (container) {
1127
- const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior: BEHAVIOR_AUTO };
1128
- this.scrollImmediately(container, params);
1129
- }
1130
- }
1131
1186
  return of(displayItems);
1132
1187
  })).subscribe();
1133
1188
  combineLatest([this.$initialized, this.$itemRenderer]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
@@ -1203,6 +1258,14 @@ class NgVirtualListComponent {
1203
1258
  * Behavior accepts the values ​​"auto", "instant" and "smooth".
1204
1259
  */
1205
1260
  scrollTo(id, behavior = BEHAVIOR_AUTO) {
1261
+ this.scrollToExecutor(id, behavior);
1262
+ }
1263
+ _scrollToRepeatExecutionTimeout;
1264
+ clearScrollToRepeatExecutionTimeout() {
1265
+ clearTimeout(this._scrollToRepeatExecutionTimeout);
1266
+ }
1267
+ scrollToExecutor(id, behavior, iteration = 0) {
1268
+ this.clearScrollToRepeatExecutionTimeout();
1206
1269
  const items = this.items;
1207
1270
  if (!items || !items.length) {
1208
1271
  return;
@@ -1214,24 +1277,35 @@ class NgVirtualListComponent {
1214
1277
  container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1215
1278
  container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1216
1279
  }
1217
- const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, opts = {
1280
+ const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, isVertical = this._isVertical, opts = {
1218
1281
  bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
1219
- itemsOffset: this.itemsOffset, scrollSize: this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1282
+ itemsOffset: this.itemsOffset, scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1220
1283
  snap: this.snap, fromItemId: id,
1221
- }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1222
- this._$scrolledItemId.next(id);
1284
+ }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1223
1285
  this._$scrollSize.next(scrollSize);
1224
1286
  if (container) {
1225
1287
  const handler = () => {
1226
1288
  if (container) {
1227
1289
  container.nativeElement.removeEventListener(SCROLL_END, handler);
1228
- clearTimeout(this._scrollToTimeout);
1229
- this._scrollToTimeout = setTimeout(() => {
1290
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1291
+ ...opts, scrollSize, fromItemId: id,
1292
+ });
1293
+ this.resetBoundsSize(isVertical, totalSize);
1294
+ this.createDisplayComponentsIfNeed(displayItems);
1295
+ this.tracking();
1296
+ const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize, fromItemId: id });
1297
+ if (scrollSize < _scrollSize && iteration < MAX_SCROLL_TO_ITERATIONS) {
1298
+ this.clearScrollToRepeatExecutionTimeout();
1299
+ this._scrollToRepeatExecutionTimeout = setTimeout(() => {
1300
+ this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1);
1301
+ });
1302
+ }
1303
+ else {
1304
+ this._$scrollSize.next(scrollSize);
1305
+ this.onScroll.emit({ scrollSize, direction: this._trackBox.scrollDirection });
1230
1306
  container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1231
1307
  container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1232
- }, 100);
1233
- this.listenCacheChangesIfNeed(dynamicSize);
1234
- this.onScroll.emit(scrollSize);
1308
+ }
1235
1309
  }
1236
1310
  };
1237
1311
  container.nativeElement.addEventListener(SCROLL_END, handler);
@@ -1260,9 +1334,7 @@ class NgVirtualListComponent {
1260
1334
  }
1261
1335
  }
1262
1336
  ngOnDestroy() {
1263
- if (this._scrollToTimeout) {
1264
- clearTimeout(this._scrollToTimeout);
1265
- }
1337
+ this.clearScrollToRepeatExecutionTimeout();
1266
1338
  if (this._trackBox) {
1267
1339
  this._trackBox.dispose();
1268
1340
  }