ng-virtual-list 16.0.11 → 16.0.13

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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { Component, ChangeDetectionStrategy, EventEmitter as EventEmitter$1, ViewContainerRef, ElementRef, ViewEncapsulation, ViewChild, Output, Input, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
- import { BehaviorSubject, tap, filter, map, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
5
+ import { BehaviorSubject, filter, map, tap, combineLatest, distinctUntilChanged, debounceTime, switchMap, of } from 'rxjs';
6
6
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
7
 
8
8
  /**
@@ -28,7 +28,6 @@ const DEFAULT_ITEMS_OFFSET = 2;
28
28
  const DEFAULT_LIST_SIZE = 400;
29
29
  const DEFAULT_SNAP = false;
30
30
  const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = true;
31
- const DEFAULT_OPTIMIZE_FOR_END = false;
32
31
  const DEFAULT_SNAP_TO_ITEM = false;
33
32
  const DEFAULT_DYNAMIC_SIZE = false;
34
33
  const TRACK_BY_PROPERTY_NAME = 'id';
@@ -424,13 +423,13 @@ const MAX_SCROLL_DIRECTION_POOL = 8, CLEAR_SCROLL_DIRECTION_TO = 0;
424
423
  */
425
424
  class CacheMap extends EventEmitter {
426
425
  _map = new Map();
426
+ _snapshot = new Map();
427
427
  _version = 0;
428
- _previouseFullSize = 0;
429
428
  _delta = 0;
430
429
  get delta() {
431
430
  return this._delta;
432
431
  }
433
- _deltaDirection = 1;
432
+ _deltaDirection = 0;
434
433
  set deltaDirection(v) {
435
434
  this._deltaDirection = v;
436
435
  this._scrollDirection = this.calcScrollDirection(v);
@@ -438,19 +437,8 @@ class CacheMap extends EventEmitter {
438
437
  get deltaDirection() {
439
438
  return this._deltaDirection;
440
439
  }
441
- _likeAChat = false;
442
- set likeAChat(v) {
443
- if (this._likeAChat === v) {
444
- return;
445
- }
446
- if (v) {
447
- this._scrollDirection = -1;
448
- }
449
- this._scrollDirectionCache = [];
450
- this._likeAChat = v;
451
- }
452
440
  _scrollDirectionCache = [];
453
- _scrollDirection = 1;
441
+ _scrollDirection = 0;
454
442
  get scrollDirection() {
455
443
  return this._scrollDirection;
456
444
  }
@@ -478,13 +466,13 @@ class CacheMap extends EventEmitter {
478
466
  const dir = this._scrollDirectionCache[i];
479
467
  dict[dir] += 1;
480
468
  }
481
- if (dict[-1] > dict[1]) {
469
+ if (dict[-1] > dict[0] && dict[-1] > dict[1]) {
482
470
  return -1;
483
471
  }
484
- else if (dict[1] > dict[-1]) {
472
+ else if (dict[1] > dict[-1] && dict[1] > dict[0]) {
485
473
  return 1;
486
474
  }
487
- return -1;
475
+ return 0;
488
476
  }
489
477
  bumpVersion() {
490
478
  this._version = this._version === Number.MAX_SAFE_INTEGER ? 0 : this._version + 1;
@@ -510,8 +498,12 @@ class CacheMap extends EventEmitter {
510
498
  forEach(callbackfn, thisArg) {
511
499
  return this._map.forEach(callbackfn, thisArg);
512
500
  }
501
+ snapshot() {
502
+ this._snapshot = new Map(this._map);
503
+ }
513
504
  dispose() {
514
505
  super.dispose();
506
+ this._snapshot.clear();
515
507
  this._map.clear();
516
508
  }
517
509
  }
@@ -523,12 +515,12 @@ class CacheMap extends EventEmitter {
523
515
  * @email djonnyx@gmail.com
524
516
  */
525
517
  const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollection) => {
526
- const result = { deletedOrUpdated: new Array(), added: new Array(), notChanged: new Array() };
518
+ const result = { deleted: new Array(), updated: new Array(), added: new Array(), notChanged: new Array() };
527
519
  if (!currentCollection || currentCollection.length === 0) {
528
- return { deletedOrUpdated: (previousCollection ? [...previousCollection] : []), added: [], notChanged: [] };
520
+ return { deleted: (previousCollection ? [...previousCollection] : []), updated: [], added: [], notChanged: [] };
529
521
  }
530
522
  if (!previousCollection || previousCollection.length === 0) {
531
- return { deletedOrUpdated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
523
+ return { deleted: [], updated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
532
524
  }
533
525
  const collectionDict = {};
534
526
  for (let i = 0, l = currentCollection.length; i < l; i++) {
@@ -537,7 +529,7 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
537
529
  collectionDict[item.id] = item;
538
530
  }
539
531
  }
540
- const notChangedMap = {}, deletedOrUpdatedMap = {};
532
+ const notChangedMap = {}, deletedMap = {}, updatedMap = {};
541
533
  for (let i = 0, l = previousCollection.length; i < l; i++) {
542
534
  const item = previousCollection[i], id = item.id;
543
535
  if (item) {
@@ -547,14 +539,19 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
547
539
  notChangedMap[item.id] = item;
548
540
  continue;
549
541
  }
542
+ else {
543
+ result.updated.push(item);
544
+ updatedMap[item.id] = item;
545
+ continue;
546
+ }
550
547
  }
551
- result.deletedOrUpdated.push(item);
552
- deletedOrUpdatedMap[item.id] = item;
548
+ result.deleted.push(item);
549
+ deletedMap[item.id] = item;
553
550
  }
554
551
  }
555
552
  for (let i = 0, l = currentCollection.length; i < l; i++) {
556
- const item = currentCollection[i];
557
- if (item && !deletedOrUpdatedMap.hasOwnProperty(item.id) && !notChangedMap.hasOwnProperty(item.id)) {
553
+ const item = currentCollection[i], id = item.id;
554
+ if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) && !notChangedMap.hasOwnProperty(id)) {
558
555
  result.added.push(item);
559
556
  }
560
557
  }
@@ -562,6 +559,13 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
562
559
  };
563
560
 
564
561
  const TRACK_BOX_CHANGE_EVENT_NAME = 'change';
562
+ var ItemDisplayMethods;
563
+ (function (ItemDisplayMethods) {
564
+ ItemDisplayMethods[ItemDisplayMethods["CREATE"] = 0] = "CREATE";
565
+ ItemDisplayMethods[ItemDisplayMethods["UPDATE"] = 1] = "UPDATE";
566
+ ItemDisplayMethods[ItemDisplayMethods["DELETE"] = 2] = "DELETE";
567
+ ItemDisplayMethods[ItemDisplayMethods["NOT_CHANGED"] = 3] = "NOT_CHANGED";
568
+ })(ItemDisplayMethods || (ItemDisplayMethods = {}));
565
569
  /**
566
570
  * An object that performs tracking, calculations and caching.
567
571
  * @link https://github.com/DjonnyX/ng-virtual-list/blob/16.x/projects/ng-virtual-list/src/lib/utils/trackBox.ts
@@ -584,7 +588,6 @@ class TrackBox extends CacheMap {
584
588
  }
585
589
  this._displayComponents = v;
586
590
  }
587
- enabledBufferOptimization = false;
588
591
  constructor(trackingPropertyName) {
589
592
  super();
590
593
  this._tracker = new Tracker(trackingPropertyName);
@@ -601,8 +604,6 @@ class TrackBox extends CacheMap {
601
604
  _fireChanges = (version) => {
602
605
  this.dispatch(TRACK_BOX_CHANGE_EVENT_NAME, version);
603
606
  };
604
- _isExistAddedItems = false;
605
- _addedItemsMap = {};
606
607
  _previousCollection;
607
608
  _debounceChanges = debounce(this._fireChanges, 0);
608
609
  fireChange() {
@@ -611,36 +612,37 @@ class TrackBox extends CacheMap {
611
612
  /**
612
613
  * Scans the collection for deleted items and flushes the deleted item cache.
613
614
  */
614
- resetCollection(currentCollection) {
615
+ resetCollection(currentCollection, itemSize) {
615
616
  if (currentCollection !== undefined && currentCollection !== null && currentCollection === this._previousCollection) {
616
617
  console.warn('Attention! The collection must be immutable.');
617
618
  return;
618
619
  }
619
- const { deletedOrUpdated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
620
- this.clearCache(deletedOrUpdated);
621
- this.startScrollDeltaCalculationIfNeed(added);
620
+ const { deleted, updated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
621
+ this.clearCache(deleted, updated, added, itemSize);
622
622
  this._previousCollection = currentCollection;
623
623
  }
624
- startScrollDeltaCalculationIfNeed(added) {
625
- if (added.length > 0) {
626
- this._isExistAddedItems = true;
627
- }
628
- for (let i = 0, l = added.length; i < l; i++) {
629
- const item = added[i];
630
- this._addedItemsMap[item.id] = item;
631
- }
632
- }
633
624
  /**
634
625
  * Clears the cache of items from the list
635
626
  */
636
- clearCache(items) {
637
- if (!items || items.length === 0) {
638
- return;
627
+ clearCache(deleted, updated, added, itemSize) {
628
+ if (deleted) {
629
+ for (let i = 0, l = deleted.length; i < l; i++) {
630
+ const item = deleted[i], id = item.id;
631
+ if (this._map.has(id)) {
632
+ this._map.delete(id);
633
+ }
634
+ }
639
635
  }
640
- for (let i = 0, l = items.length; i < l; i++) {
641
- const item = items[i], id = item.id;
642
- if (this._map.has(id)) {
643
- this._map.delete(id);
636
+ if (updated) {
637
+ for (let i = 0, l = updated.length; i < l; i++) {
638
+ const item = updated[i], id = item.id;
639
+ this._map.set(id, { ...(this._map.get(id) || { x: 0, y: 0, width: itemSize, height: itemSize }), method: ItemDisplayMethods.UPDATE });
640
+ }
641
+ }
642
+ if (added) {
643
+ for (let i = 0, l = added.length; i < l; i++) {
644
+ const item = added[i], id = item.id;
645
+ this._map.set(id, { x: 0, y: 0, width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
644
646
  }
645
647
  }
646
648
  }
@@ -662,6 +664,8 @@ class TrackBox extends CacheMap {
662
664
  ...opt,
663
665
  collection: items,
664
666
  });
667
+ this._delta += metrics.delta;
668
+ this.snapshot();
665
669
  const displayItems = this.generateDisplayCollection(items, stickyMap, metrics);
666
670
  return { displayItems, totalSize: metrics.totalSize, delta: metrics.delta };
667
671
  }
@@ -722,31 +726,45 @@ class TrackBox extends CacheMap {
722
726
  * Calculates list metrics
723
727
  */
724
728
  recalculateMetrics(options) {
725
- const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap } = options;
726
- 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, map = this._map, leftItemsOffset = this.enabledBufferOptimization ? this.deltaDirection === 1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, rightItemsOffset = this.enabledBufferOptimization ? this.deltaDirection === -1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
729
+ const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap, enabledBufferOptimization } = options;
730
+ 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, map = this._map, snapshot = this._snapshot, leftItemsOffset = enabledBufferOptimization ? this.deltaDirection === 1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, rightItemsOffset = enabledBufferOptimization ? this.deltaDirection === -1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
727
731
  || (typeof fromItemId === 'string' && fromItemId > '-1');
728
- let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, sizeOfAddedItems = 0, itemById = undefined, itemByIdPos = 0, targetDisplayItemIndex = -1, isTargetInOverscroll = false, actualScrollSize = itemByIdPos, totalSize = 0, startIndex;
732
+ let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, rightSizeOfAddedItems = 0, leftSizeOfAddedItems = 0, rightSizeOfUpdatedItems = 0, leftSizeOfUpdatedItems = 0, itemById = undefined, itemByIdPos = 0, targetDisplayItemIndex = -1, isTargetInOverscroll = false, actualScrollSize = itemByIdPos, totalSize = 0, startIndex;
729
733
  // If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
730
- if (dynamicSize || this._isExistAddedItems) {
734
+ if (dynamicSize) {
731
735
  let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
732
736
  for (let i = 0, l = collection.length; i < l; i++) {
733
- const ii = i + 1, collectionItem = collection[i];
734
- let componentSize = 0;
735
- if (map.has(collectionItem.id)) {
736
- const bounds = map.get(collectionItem.id);
737
- componentSize = bounds ? bounds[sizeProperty] : typicalItemSize;
737
+ const ii = i + 1, collectionItem = collection[i], id = collectionItem.id;
738
+ let componentSize = 0, componentSizeDelta = 0, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
739
+ if (map.has(id)) {
740
+ const bounds = map.get(id) || { x: 0, y: 0, width: typicalItemSize, height: typicalItemSize };
741
+ componentSize = bounds[sizeProperty];
742
+ itemDisplayMethod = bounds?.method ?? ItemDisplayMethods.UPDATE;
743
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
744
+ const snapshotBounds = snapshot.get(id);
745
+ const componentSnapshotSize = componentSize - (snapshotBounds ? snapshotBounds[sizeProperty] : typicalItemSize);
746
+ componentSizeDelta = componentSnapshotSize;
747
+ map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
748
+ }
749
+ if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
750
+ componentSizeDelta = typicalItemSize;
751
+ map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
752
+ }
738
753
  }
739
754
  else {
740
755
  componentSize = typicalItemSize;
756
+ if (snapshot.has(id)) {
757
+ itemDisplayMethod = ItemDisplayMethods.DELETE;
758
+ }
741
759
  }
742
760
  totalSize += componentSize;
743
761
  if (isFromId) {
744
762
  if (itemById === undefined) {
745
- if (collectionItem.id !== fromItemId && stickyMap && stickyMap[collectionItem.id] > 0) {
763
+ if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
746
764
  stickyComponentSize = componentSize;
747
765
  stickyCollectionItem = collectionItem;
748
766
  }
749
- if (collectionItem.id === fromItemId) {
767
+ if (id === fromItemId) {
750
768
  targetDisplayItemIndex = i;
751
769
  if (stickyCollectionItem && stickyMap && stickyMap[stickyCollectionItem.id] > 0) {
752
770
  const { num } = this.getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical);
@@ -781,8 +799,8 @@ class TrackBox extends CacheMap {
781
799
  itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
782
800
  }
783
801
  if (y > itemByIdPos + size + componentSize) {
784
- if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
785
- sizeOfAddedItems += componentSize;
802
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
803
+ rightSizeOfAddedItems += componentSizeDelta;
786
804
  }
787
805
  }
788
806
  }
@@ -790,13 +808,24 @@ class TrackBox extends CacheMap {
790
808
  itemsFromStartToDisplayEnd = ii;
791
809
  totalItemsToDisplayEndWeight += componentSize;
792
810
  itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
811
+ if (y < scrollSize - componentSize) {
812
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
813
+ leftSizeOfUpdatedItems += componentSizeDelta;
814
+ }
815
+ if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
816
+ leftSizeOfAddedItems += componentSizeDelta;
817
+ }
818
+ }
793
819
  }
794
820
  else {
795
821
  if (i < itemsFromDisplayEndToOffsetEnd) {
796
822
  rightItemsWeight += componentSize;
797
823
  }
798
- if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
799
- sizeOfAddedItems += componentSize;
824
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
825
+ rightSizeOfUpdatedItems += componentSizeDelta;
826
+ }
827
+ if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
828
+ rightSizeOfAddedItems += componentSizeDelta;
800
829
  }
801
830
  }
802
831
  y += componentSize;
@@ -838,15 +867,9 @@ class TrackBox extends CacheMap {
838
867
  totalSize = totalLength * typicalItemSize;
839
868
  }
840
869
  startIndex = Math.min(itemsFromStartToScrollEnd - leftItemLength, totalLength > 0 ? totalLength - 1 : 0);
841
- const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = totalSize - this._previouseFullSize, scrollDelta = sizeOfAddedItems;
842
- if (this.scrollDirection === -1) {
843
- if (this._isExistAddedItems && sizeOfAddedItems > 0) {
844
- this.stopScrollDeltaCalculation();
845
- }
846
- this._delta += delta - scrollDelta;
847
- }
870
+ const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = leftSizeOfUpdatedItems + leftSizeOfAddedItems;
848
871
  const metrics = {
849
- delta: this._delta,
872
+ delta,
850
873
  normalizedItemWidth: w,
851
874
  normalizedItemHeight: h,
852
875
  width,
@@ -865,7 +888,8 @@ class TrackBox extends CacheMap {
865
888
  rightItemLength,
866
889
  rightItemsWeight,
867
890
  scrollSize: actualScrollSize,
868
- sizeOfAddedItems,
891
+ leftSizeOfAddedItems,
892
+ rightSizeOfAddedItems,
869
893
  sizeProperty,
870
894
  snap,
871
895
  snippedPos,
@@ -876,7 +900,6 @@ class TrackBox extends CacheMap {
876
900
  totalSize,
877
901
  typicalItemSize,
878
902
  };
879
- this._previouseFullSize = totalSize;
880
903
  return metrics;
881
904
  }
882
905
  _scrollDelta = 0;
@@ -886,39 +909,12 @@ class TrackBox extends CacheMap {
886
909
  }
887
910
  clearDelta(clearDirectionDetector = false) {
888
911
  this._delta = 0;
889
- this.stopScrollDeltaCalculation();
890
912
  if (clearDirectionDetector) {
891
913
  this.clearScrollDirectionCache();
892
914
  }
893
915
  }
894
- stopScrollDeltaCalculation() {
895
- this._isExistAddedItems = false;
896
- this._addedItemsMap = {};
897
- }
898
916
  generateDisplayCollection(items, stickyMap, metrics) {
899
- const {
900
- // delta,
901
- normalizedItemWidth, normalizedItemHeight,
902
- // width,
903
- // height,
904
- dynamicSize,
905
- // itemSize,
906
- itemsFromStartToScrollEnd,
907
- // itemsFromStartToDisplayEnd,
908
- // itemsOnDisplay,
909
- // itemsOnDisplayLength,
910
- isVertical,
911
- // leftHiddenItemsWeight,
912
- // leftItemLength,
913
- // leftItemsWeight,
914
- renderItems: renderItemsLength,
915
- // rightItemLength,
916
- // rightItemsWeight,
917
- scrollSize, sizeProperty, snap, snippedPos, startPosition,
918
- // totalItemsToDisplayEndWeight,
919
- totalLength,
920
- // totalSize,
921
- startIndex, typicalItemSize, } = metrics, displayItems = [];
917
+ const { normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
922
918
  if (items.length) {
923
919
  const actualSnippedPosition = snippedPos;
924
920
  let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0;
@@ -1031,24 +1027,6 @@ class TrackBox extends CacheMap {
1031
1027
  this.set(itemId, bounds);
1032
1028
  }
1033
1029
  }
1034
- /**
1035
- * Returns calculated bounds from cache
1036
- */
1037
- getBoundsFromCache(items, typicalItemSize, isVertical) {
1038
- const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, map = this._map;
1039
- let size = 0;
1040
- for (let i = 0, l = items.length; i < l; i++) {
1041
- const item = items[i];
1042
- if (map.has(item.id)) {
1043
- const bounds = map.get(item.id);
1044
- size += bounds ? bounds[sizeProperty] : typicalItemSize;
1045
- }
1046
- else {
1047
- size += typicalItemSize;
1048
- }
1049
- }
1050
- return size;
1051
- }
1052
1030
  dispose() {
1053
1031
  super.dispose();
1054
1032
  if (this._debounceChanges) {
@@ -1133,7 +1111,7 @@ class NgVirtualListComponent {
1133
1111
  _$items = new BehaviorSubject(undefined);
1134
1112
  $items = this._$items.asObservable();
1135
1113
  _itemsTransform = (v) => {
1136
- this._trackBox.resetCollection(v);
1114
+ this._trackBox.resetCollection(v, this._$itemSize.getValue());
1137
1115
  return v;
1138
1116
  };
1139
1117
  /**
@@ -1193,20 +1171,6 @@ class NgVirtualListComponent {
1193
1171
  }
1194
1172
  ;
1195
1173
  get enabledBufferOptimization() { return this._$enabledBufferOptimization.getValue(); }
1196
- _$likeAChat = new BehaviorSubject(DEFAULT_OPTIMIZE_FOR_END);
1197
- $likeAChat = this._$likeAChat.asObservable();
1198
- /**
1199
- * If true, optimization for lists that start from the end is enabled (chat mode enabled).
1200
- */
1201
- set likeAChat(v) {
1202
- if (this._$likeAChat.getValue() === v) {
1203
- return;
1204
- }
1205
- this._$likeAChat.next(v);
1206
- this._cdr.markForCheck();
1207
- }
1208
- ;
1209
- get likeAChat() { return this._$likeAChat.getValue(); }
1210
1174
  _$itemRenderer = new BehaviorSubject(undefined);
1211
1175
  $itemRenderer = this._$itemRenderer.asObservable();
1212
1176
  /**
@@ -1304,11 +1268,17 @@ class NgVirtualListComponent {
1304
1268
  _displayComponents = [];
1305
1269
  _$bounds = new BehaviorSubject(null);
1306
1270
  _$scrollSize = new BehaviorSubject(0);
1271
+ _isScrollingDebounces = debounce((v) => {
1272
+ this._isScrolling = v;
1273
+ }, 250);
1274
+ _isScrolling = false;
1275
+ get isScrolling() { return this._isScrolling; }
1307
1276
  _resizeObserver = null;
1308
1277
  _onResizeHandler = () => {
1309
1278
  this._$bounds.next(this._container?.nativeElement?.getBoundingClientRect() ?? null);
1310
1279
  };
1311
1280
  _onScrollHandler = (e) => {
1281
+ this._isScrollingDebounces.dispose();
1312
1282
  this.clearScrollToRepeatExecutionTimeout();
1313
1283
  const container = this._container?.nativeElement;
1314
1284
  if (container) {
@@ -1416,15 +1386,7 @@ class NgVirtualListComponent {
1416
1386
  this._$initialized = new BehaviorSubject(false);
1417
1387
  this.$initialized = this._$initialized.asObservable();
1418
1388
  this._trackBox.displayComponents = this._displayComponents;
1419
- const $enabledBufferOptimization = this.$enabledBufferOptimization;
1420
- $enabledBufferOptimization.pipe(takeUntilDestroyed(), tap(v => {
1421
- this._trackBox.enabledBufferOptimization = v;
1422
- })).subscribe();
1423
- const $likeAChat = this.$likeAChat;
1424
- $likeAChat.pipe(takeUntilDestroyed(), tap(v => {
1425
- this._trackBox.likeAChat = v;
1426
- })).subscribe();
1427
- 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;
1389
+ 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, $snapToItem = this.$snapToItem, $isVertical = this.$direction.pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = this.$dynamicSize, $enabledBufferOptimization = this.$enabledBufferOptimization, $cacheVersion = this.$cacheVersion;
1428
1390
  $isVertical.pipe(takeUntilDestroyed(), tap(v => {
1429
1391
  this._isVertical = v;
1430
1392
  const el = this._elementRef.nativeElement;
@@ -1434,20 +1396,35 @@ class NgVirtualListComponent {
1434
1396
  this.listenCacheChangesIfNeed(dynamicSize);
1435
1397
  })).subscribe();
1436
1398
  combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1437
- $itemsOffset, $snap, $isVertical, $dynamicSize, $cacheVersion,
1438
- ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, cacheVersion,]) => {
1399
+ $itemsOffset, $snap, $snapToItem, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1400
+ ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), debounceTime(0), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, snapToItem, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
1439
1401
  const { width, height } = bounds;
1440
1402
  let actualScrollSize = scrollSize;
1441
1403
  const opts = {
1442
1404
  bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
1443
- itemsOffset, scrollSize: scrollSize, snap,
1405
+ itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
1444
1406
  };
1445
- const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1407
+ const { displayItems, totalSize, delta } = this._trackBox.updateCollection(items, stickyMap, {
1446
1408
  ...opts, scrollSize: actualScrollSize,
1447
1409
  });
1448
1410
  this.resetBoundsSize(isVertical, totalSize);
1449
1411
  this.createDisplayComponentsIfNeed(displayItems);
1450
1412
  this.tracking();
1413
+ const container = this._container;
1414
+ if (!this.isScrolling && dynamicSize && container) {
1415
+ actualScrollSize = scrollSize + delta;
1416
+ if (snapToItem) {
1417
+ // etc
1418
+ }
1419
+ else if (scrollSize !== actualScrollSize) {
1420
+ const params = {
1421
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1422
+ behavior: BEHAVIOR_INSTANT
1423
+ };
1424
+ this.scrollImmediately(container, params);
1425
+ this._trackBox.clearDelta();
1426
+ }
1427
+ }
1451
1428
  return of(displayItems);
1452
1429
  })).subscribe();
1453
1430
  combineLatest([this.$initialized, this.$itemRenderer]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
@@ -1536,6 +1513,7 @@ class NgVirtualListComponent {
1536
1513
  clearTimeout(this._scrollToRepeatExecutionTimeout);
1537
1514
  }
1538
1515
  scrollToExecutor(id, behavior, iteration = 0) {
1516
+ this._isScrolling = true;
1539
1517
  this.clearScrollToRepeatExecutionTimeout();
1540
1518
  const items = this.items;
1541
1519
  if (!items || !items.length) {
@@ -1551,7 +1529,7 @@ class NgVirtualListComponent {
1551
1529
  const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, isVertical = this._isVertical, opts = {
1552
1530
  bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
1553
1531
  itemsOffset: this.itemsOffset, scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1554
- snap: this.snap, fromItemId: id,
1532
+ snap: this.snap, fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization,
1555
1533
  }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1556
1534
  this._$scrollSize.next(scrollSize);
1557
1535
  if (container) {
@@ -1594,10 +1572,11 @@ class NgVirtualListComponent {
1594
1572
  this.scrollTo(latItem.id, behavior);
1595
1573
  }
1596
1574
  _onContainerScrollHandler = (e) => {
1575
+ this._isScrolling = true;
1597
1576
  const containerEl = this._container;
1598
1577
  if (containerEl) {
1599
- const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft), offsetSize = (this._isVertical ? containerEl.nativeElement.offsetHeight : containerEl.nativeElement.offsetWidth), listSize = (this._isVertical ? this._list?.nativeElement.offsetHeight ?? 0 : this._list?.nativeElement.offsetLeft ?? 0);
1600
- this._trackBox.deltaDirection = this._$scrollSize.getValue() >= scrollSize ? -1 : this.likeAChat && (scrollSize + offsetSize) >= listSize ? -1 : 1;
1578
+ const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
1579
+ this._trackBox.deltaDirection = this._$scrollSize.getValue() > scrollSize ? -1 : this._$scrollSize.getValue() < scrollSize ? 1 : 0;
1601
1580
  const event = new ScrollEvent({
1602
1581
  direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
1603
1582
  list: this._list.nativeElement, delta: this._trackBox.delta,
@@ -1607,9 +1586,11 @@ class NgVirtualListComponent {
1607
1586
  }
1608
1587
  };
1609
1588
  _onContainerScrollEndHandler = (e) => {
1610
- this._trackBox.deltaDirection = this.likeAChat ? -1 : 1;
1589
+ this._isScrollingDebounces.execute(false);
1611
1590
  const containerEl = this._container;
1612
1591
  if (containerEl) {
1592
+ const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
1593
+ this._trackBox.deltaDirection = this._$scrollSize.getValue() > scrollSize ? -1 : 0;
1613
1594
  const event = new ScrollEvent({
1614
1595
  direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
1615
1596
  list: this._list.nativeElement, delta: this._trackBox.delta,
@@ -1636,6 +1617,9 @@ class NgVirtualListComponent {
1636
1617
  if (this._trackBox) {
1637
1618
  this._trackBox.dispose();
1638
1619
  }
1620
+ if (this._isScrollingDebounces) {
1621
+ this._isScrollingDebounces.dispose();
1622
+ }
1639
1623
  const containerEl = this._container;
1640
1624
  if (containerEl) {
1641
1625
  containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
@@ -1654,7 +1638,7 @@ class NgVirtualListComponent {
1654
1638
  }
1655
1639
  }
1656
1640
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NgVirtualListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1657
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", snap: "snap", snapToItem: "snapToItem", enabledBufferOptimization: "enabledBufferOptimization", likeAChat: "likeAChat", itemRenderer: "itemRenderer", stickyMap: "stickyMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", itemsOffset: "itemsOffset" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
1641
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", snap: "snap", snapToItem: "snapToItem", enabledBufferOptimization: "enabledBufferOptimization", itemRenderer: "itemRenderer", stickyMap: "stickyMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", itemsOffset: "itemsOffset" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
1658
1642
  }
1659
1643
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NgVirtualListComponent, decorators: [{
1660
1644
  type: Component,
@@ -1680,8 +1664,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImpo
1680
1664
  type: Input
1681
1665
  }], enabledBufferOptimization: [{
1682
1666
  type: Input
1683
- }], likeAChat: [{
1684
- type: Input
1685
1667
  }], itemRenderer: [{
1686
1668
  type: Input
1687
1669
  }], stickyMap: [{