ng-virtual-list 19.1.21 → 19.1.23

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.
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # NgVirtualList
2
2
  Maximum performance for extremely large lists.
3
+ 10x more resource intensive than standard lists
3
4
 
4
5
  Angular version 19.X.X.
5
6
 
@@ -28,9 +29,9 @@ Template:
28
29
 
29
30
  <ng-template #hotizontalItemRenderer let-data="data">
30
31
  @if (data) {
31
- <div class="list__h-container" (click)="onItemClick(data)">
32
- <span>{{data.name}}</span>
33
- </div>
32
+ <div class="list__h-container" (click)="onItemClick(data)">
33
+ <span>{{data.name}}</span>
34
+ </div>
34
35
  }
35
36
  </ng-template>
36
37
  ```
@@ -68,14 +69,14 @@ Template:
68
69
  @if (data) {
69
70
  @switch (data.type) {
70
71
  @case ("group-header") {
71
- <div class="list__h-group-container">
72
- <span>{{data.name}}</span>
73
- </div>
72
+ <div class="list__h-group-container">
73
+ <span>{{data.name}}</span>
74
+ </div>
74
75
  }
75
76
  @default {
76
- <div class="list__h-container" (click)="onItemClick(data)">
77
- <span>{{data.name}}</span>
78
- </div>
77
+ <div class="list__h-container" (click)="onItemClick(data)">
78
+ <span>{{data.name}}</span>
79
+ </div>
79
80
  }
80
81
  }
81
82
  }
@@ -124,9 +125,9 @@ Template:
124
125
 
125
126
  <ng-template #itemRenderer let-data="data">
126
127
  @if (data) {
127
- <div class="list__container">
128
- <p>{{data.name}}</p>
129
- </div>
128
+ <div class="list__container">
129
+ <p>{{data.name}}</p>
130
+ </div>
130
131
  }
131
132
  </ng-template>
132
133
  ```
@@ -167,14 +168,14 @@ Template:
167
168
  @if (data) {
168
169
  @switch (data.type) {
169
170
  @case ("group-header") {
170
- <div class="list__group-container">
171
- <p>{{data.name}}</p>
172
- </div>
171
+ <div class="list__group-container">
172
+ <p>{{data.name}}</p>
173
+ </div>
173
174
  }
174
175
  @default {
175
- <div class="list__container">
176
- <p>{{data.name}}</p>
177
- </div>
176
+ <div class="list__container">
177
+ <p>{{data.name}}</p>
178
+ </div>
178
179
  }
179
180
  }
180
181
  }
@@ -194,14 +195,14 @@ Template (with snapping):
194
195
  @if (data) {
195
196
  @switch (data.type) {
196
197
  @case ("group-header") {
197
- <div class="list__group-container">
198
- <p>{{data.name}}</p>
199
- </div>
198
+ <div class="list__group-container">
199
+ <p>{{data.name}}</p>
200
+ </div>
200
201
  }
201
202
  @default {
202
- <div class="list__container">
203
- <p>{{data.name}}</p>
204
- </div>
203
+ <div class="list__container">
204
+ <p>{{data.name}}</p>
205
+ </div>
205
206
  }
206
207
  }
207
208
  }
@@ -3,7 +3,7 @@ import { signal, inject, ElementRef, ChangeDetectionStrategy, Component, viewChi
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
- import { BehaviorSubject, tap, filter, map, combineLatest, distinctUntilChanged, 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.
@@ -417,13 +417,13 @@ const MAX_SCROLL_DIRECTION_POOL = 8, CLEAR_SCROLL_DIRECTION_TO = 0;
417
417
  */
418
418
  class CacheMap extends EventEmitter {
419
419
  _map = new Map();
420
+ _snapshot = new Map();
420
421
  _version = 0;
421
- _previouseFullSize = 0;
422
422
  _delta = 0;
423
423
  get delta() {
424
424
  return this._delta;
425
425
  }
426
- _deltaDirection = 1;
426
+ _deltaDirection = 0;
427
427
  set deltaDirection(v) {
428
428
  this._deltaDirection = v;
429
429
  this._scrollDirection = this.calcScrollDirection(v);
@@ -432,7 +432,7 @@ class CacheMap extends EventEmitter {
432
432
  return this._deltaDirection;
433
433
  }
434
434
  _scrollDirectionCache = [];
435
- _scrollDirection = -1;
435
+ _scrollDirection = 0;
436
436
  get scrollDirection() {
437
437
  return this._scrollDirection;
438
438
  }
@@ -460,13 +460,13 @@ class CacheMap extends EventEmitter {
460
460
  const dir = this._scrollDirectionCache[i];
461
461
  dict[dir] += 1;
462
462
  }
463
- if (dict[-1] > dict[1]) {
463
+ if (dict[-1] > dict[0] && dict[-1] > dict[1]) {
464
464
  return -1;
465
465
  }
466
- else if (dict[1] > dict[-1]) {
466
+ else if (dict[1] > dict[-1] && dict[1] > dict[0]) {
467
467
  return 1;
468
468
  }
469
- return -1;
469
+ return 0;
470
470
  }
471
471
  bumpVersion() {
472
472
  this._version = this._version === Number.MAX_SAFE_INTEGER ? 0 : this._version + 1;
@@ -492,8 +492,12 @@ class CacheMap extends EventEmitter {
492
492
  forEach(callbackfn, thisArg) {
493
493
  return this._map.forEach(callbackfn, thisArg);
494
494
  }
495
+ snapshot() {
496
+ this._snapshot = new Map(this._map);
497
+ }
495
498
  dispose() {
496
499
  super.dispose();
500
+ this._snapshot.clear();
497
501
  this._map.clear();
498
502
  }
499
503
  }
@@ -505,12 +509,12 @@ class CacheMap extends EventEmitter {
505
509
  * @email djonnyx@gmail.com
506
510
  */
507
511
  const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollection) => {
508
- const result = { deletedOrUpdated: new Array(), added: new Array(), notChanged: new Array() };
512
+ const result = { deleted: new Array(), updated: new Array(), added: new Array(), notChanged: new Array() };
509
513
  if (!currentCollection || currentCollection.length === 0) {
510
- return { deletedOrUpdated: (previousCollection ? [...previousCollection] : []), added: [], notChanged: [] };
514
+ return { deleted: (previousCollection ? [...previousCollection] : []), updated: [], added: [], notChanged: [] };
511
515
  }
512
516
  if (!previousCollection || previousCollection.length === 0) {
513
- return { deletedOrUpdated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
517
+ return { deleted: [], updated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
514
518
  }
515
519
  const collectionDict = {};
516
520
  for (let i = 0, l = currentCollection.length; i < l; i++) {
@@ -519,7 +523,7 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
519
523
  collectionDict[item.id] = item;
520
524
  }
521
525
  }
522
- const notChangedMap = {}, deletedOrUpdatedMap = {};
526
+ const notChangedMap = {}, deletedMap = {}, updatedMap = {};
523
527
  for (let i = 0, l = previousCollection.length; i < l; i++) {
524
528
  const item = previousCollection[i], id = item.id;
525
529
  if (item) {
@@ -529,14 +533,19 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
529
533
  notChangedMap[item.id] = item;
530
534
  continue;
531
535
  }
536
+ else {
537
+ result.updated.push(item);
538
+ updatedMap[item.id] = item;
539
+ continue;
540
+ }
532
541
  }
533
- result.deletedOrUpdated.push(item);
534
- deletedOrUpdatedMap[item.id] = item;
542
+ result.deleted.push(item);
543
+ deletedMap[item.id] = item;
535
544
  }
536
545
  }
537
546
  for (let i = 0, l = currentCollection.length; i < l; i++) {
538
- const item = currentCollection[i];
539
- if (item && !deletedOrUpdatedMap.hasOwnProperty(item.id) && !notChangedMap.hasOwnProperty(item.id)) {
547
+ const item = currentCollection[i], id = item.id;
548
+ if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) && !notChangedMap.hasOwnProperty(id)) {
540
549
  result.added.push(item);
541
550
  }
542
551
  }
@@ -544,6 +553,13 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
544
553
  };
545
554
 
546
555
  const TRACK_BOX_CHANGE_EVENT_NAME = 'change';
556
+ var ItemDisplayMethods;
557
+ (function (ItemDisplayMethods) {
558
+ ItemDisplayMethods[ItemDisplayMethods["CREATE"] = 0] = "CREATE";
559
+ ItemDisplayMethods[ItemDisplayMethods["UPDATE"] = 1] = "UPDATE";
560
+ ItemDisplayMethods[ItemDisplayMethods["DELETE"] = 2] = "DELETE";
561
+ ItemDisplayMethods[ItemDisplayMethods["NOT_CHANGED"] = 3] = "NOT_CHANGED";
562
+ })(ItemDisplayMethods || (ItemDisplayMethods = {}));
547
563
  /**
548
564
  * An object that performs tracking, calculations and caching.
549
565
  * @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/trackBox.ts
@@ -566,7 +582,6 @@ class TrackBox extends CacheMap {
566
582
  }
567
583
  this._displayComponents = v;
568
584
  }
569
- enabledBufferOptimization = false;
570
585
  constructor(trackingPropertyName) {
571
586
  super();
572
587
  this._tracker = new Tracker(trackingPropertyName);
@@ -583,8 +598,6 @@ class TrackBox extends CacheMap {
583
598
  _fireChanges = (version) => {
584
599
  this.dispatch(TRACK_BOX_CHANGE_EVENT_NAME, version);
585
600
  };
586
- _isExistAddedItems = false;
587
- _addedItemsMap = {};
588
601
  _previousCollection;
589
602
  _debounceChanges = debounce(this._fireChanges, 0);
590
603
  fireChange() {
@@ -593,36 +606,37 @@ class TrackBox extends CacheMap {
593
606
  /**
594
607
  * Scans the collection for deleted items and flushes the deleted item cache.
595
608
  */
596
- resetCollection(currentCollection) {
609
+ resetCollection(currentCollection, itemSize) {
597
610
  if (currentCollection !== undefined && currentCollection !== null && currentCollection === this._previousCollection) {
598
611
  console.warn('Attention! The collection must be immutable.');
599
612
  return;
600
613
  }
601
- const { deletedOrUpdated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
602
- this.clearCache(deletedOrUpdated);
603
- this.startScrollDeltaCalculationIfNeed(added);
614
+ const { deleted, updated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
615
+ this.clearCache(deleted, updated, added, itemSize);
604
616
  this._previousCollection = currentCollection;
605
617
  }
606
- startScrollDeltaCalculationIfNeed(added) {
607
- if (added.length > 0) {
608
- this._isExistAddedItems = true;
609
- }
610
- for (let i = 0, l = added.length; i < l; i++) {
611
- const item = added[i];
612
- this._addedItemsMap[item.id] = item;
613
- }
614
- }
615
618
  /**
616
619
  * Clears the cache of items from the list
617
620
  */
618
- clearCache(items) {
619
- if (!items || items.length === 0) {
620
- return;
621
+ clearCache(deleted, updated, added, itemSize) {
622
+ if (deleted) {
623
+ for (let i = 0, l = deleted.length; i < l; i++) {
624
+ const item = deleted[i], id = item.id;
625
+ if (this._map.has(id)) {
626
+ this._map.delete(id);
627
+ }
628
+ }
621
629
  }
622
- for (let i = 0, l = items.length; i < l; i++) {
623
- const item = items[i], id = item.id;
624
- if (this._map.has(id)) {
625
- this._map.delete(id);
630
+ if (updated) {
631
+ for (let i = 0, l = updated.length; i < l; i++) {
632
+ const item = updated[i], id = item.id;
633
+ this._map.set(id, { ...(this._map.get(id) || { x: 0, y: 0, width: itemSize, height: itemSize }), method: ItemDisplayMethods.UPDATE });
634
+ }
635
+ }
636
+ if (added) {
637
+ for (let i = 0, l = added.length; i < l; i++) {
638
+ const item = added[i], id = item.id;
639
+ this._map.set(id, { x: 0, y: 0, width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
626
640
  }
627
641
  }
628
642
  }
@@ -644,6 +658,8 @@ class TrackBox extends CacheMap {
644
658
  ...opt,
645
659
  collection: items,
646
660
  });
661
+ this._delta += metrics.delta;
662
+ this.snapshot();
647
663
  const displayItems = this.generateDisplayCollection(items, stickyMap, metrics);
648
664
  return { displayItems, totalSize: metrics.totalSize, delta: metrics.delta };
649
665
  }
@@ -704,31 +720,45 @@ class TrackBox extends CacheMap {
704
720
  * Calculates list metrics
705
721
  */
706
722
  recalculateMetrics(options) {
707
- const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap } = options;
708
- 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)
723
+ const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap, enabledBufferOptimization } = options;
724
+ 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)
709
725
  || (typeof fromItemId === 'string' && fromItemId > '-1');
710
- 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;
726
+ 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;
711
727
  // If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
712
- if (dynamicSize || this._isExistAddedItems) {
728
+ if (dynamicSize) {
713
729
  let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
714
730
  for (let i = 0, l = collection.length; i < l; i++) {
715
- const ii = i + 1, collectionItem = collection[i];
716
- let componentSize = 0;
717
- if (map.has(collectionItem.id)) {
718
- const bounds = map.get(collectionItem.id);
719
- componentSize = bounds ? bounds[sizeProperty] : typicalItemSize;
731
+ const ii = i + 1, collectionItem = collection[i], id = collectionItem.id;
732
+ let componentSize = 0, componentSizeDelta = 0, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
733
+ if (map.has(id)) {
734
+ const bounds = map.get(id) || { x: 0, y: 0, width: typicalItemSize, height: typicalItemSize };
735
+ componentSize = bounds[sizeProperty];
736
+ itemDisplayMethod = bounds?.method ?? ItemDisplayMethods.UPDATE;
737
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
738
+ const snapshotBounds = snapshot.get(id);
739
+ const componentSnapshotSize = componentSize - (snapshotBounds ? snapshotBounds[sizeProperty] : typicalItemSize);
740
+ componentSizeDelta = componentSnapshotSize;
741
+ map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
742
+ }
743
+ if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
744
+ componentSizeDelta = typicalItemSize;
745
+ map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
746
+ }
720
747
  }
721
748
  else {
722
749
  componentSize = typicalItemSize;
750
+ if (snapshot.has(id)) {
751
+ itemDisplayMethod = ItemDisplayMethods.DELETE;
752
+ }
723
753
  }
724
754
  totalSize += componentSize;
725
755
  if (isFromId) {
726
756
  if (itemById === undefined) {
727
- if (collectionItem.id !== fromItemId && stickyMap && stickyMap[collectionItem.id] > 0) {
757
+ if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
728
758
  stickyComponentSize = componentSize;
729
759
  stickyCollectionItem = collectionItem;
730
760
  }
731
- if (collectionItem.id === fromItemId) {
761
+ if (id === fromItemId) {
732
762
  targetDisplayItemIndex = i;
733
763
  if (stickyCollectionItem && stickyMap && stickyMap[stickyCollectionItem.id] > 0) {
734
764
  const { num } = this.getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical);
@@ -763,8 +793,8 @@ class TrackBox extends CacheMap {
763
793
  itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
764
794
  }
765
795
  if (y > itemByIdPos + size + componentSize) {
766
- if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
767
- sizeOfAddedItems += componentSize;
796
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
797
+ rightSizeOfAddedItems += componentSizeDelta;
768
798
  }
769
799
  }
770
800
  }
@@ -772,13 +802,24 @@ class TrackBox extends CacheMap {
772
802
  itemsFromStartToDisplayEnd = ii;
773
803
  totalItemsToDisplayEndWeight += componentSize;
774
804
  itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
805
+ if (y < scrollSize - componentSize) {
806
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
807
+ leftSizeOfUpdatedItems += componentSizeDelta;
808
+ }
809
+ if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
810
+ leftSizeOfAddedItems += componentSizeDelta;
811
+ }
812
+ }
775
813
  }
776
814
  else {
777
815
  if (i < itemsFromDisplayEndToOffsetEnd) {
778
816
  rightItemsWeight += componentSize;
779
817
  }
780
- if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
781
- sizeOfAddedItems += componentSize;
818
+ if (itemDisplayMethod === ItemDisplayMethods.UPDATE) {
819
+ rightSizeOfUpdatedItems += componentSizeDelta;
820
+ }
821
+ if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
822
+ rightSizeOfAddedItems += componentSizeDelta;
782
823
  }
783
824
  }
784
825
  y += componentSize;
@@ -820,15 +861,9 @@ class TrackBox extends CacheMap {
820
861
  totalSize = totalLength * typicalItemSize;
821
862
  }
822
863
  startIndex = Math.min(itemsFromStartToScrollEnd - leftItemLength, totalLength > 0 ? totalLength - 1 : 0);
823
- const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = totalSize - this._previouseFullSize, scrollDelta = sizeOfAddedItems;
824
- if (this.scrollDirection === -1) {
825
- if (this._isExistAddedItems && sizeOfAddedItems > 0) {
826
- this.stopScrollDeltaCalculation();
827
- }
828
- this._delta += delta - scrollDelta;
829
- }
864
+ const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = leftSizeOfUpdatedItems + leftSizeOfAddedItems;
830
865
  const metrics = {
831
- delta: this._delta,
866
+ delta,
832
867
  normalizedItemWidth: w,
833
868
  normalizedItemHeight: h,
834
869
  width,
@@ -847,7 +882,8 @@ class TrackBox extends CacheMap {
847
882
  rightItemLength,
848
883
  rightItemsWeight,
849
884
  scrollSize: actualScrollSize,
850
- sizeOfAddedItems,
885
+ leftSizeOfAddedItems,
886
+ rightSizeOfAddedItems,
851
887
  sizeProperty,
852
888
  snap,
853
889
  snippedPos,
@@ -858,7 +894,6 @@ class TrackBox extends CacheMap {
858
894
  totalSize,
859
895
  typicalItemSize,
860
896
  };
861
- this._previouseFullSize = totalSize;
862
897
  return metrics;
863
898
  }
864
899
  _scrollDelta = 0;
@@ -868,39 +903,12 @@ class TrackBox extends CacheMap {
868
903
  }
869
904
  clearDelta(clearDirectionDetector = false) {
870
905
  this._delta = 0;
871
- this.stopScrollDeltaCalculation();
872
906
  if (clearDirectionDetector) {
873
907
  this.clearScrollDirectionCache();
874
908
  }
875
909
  }
876
- stopScrollDeltaCalculation() {
877
- this._isExistAddedItems = false;
878
- this._addedItemsMap = {};
879
- }
880
910
  generateDisplayCollection(items, stickyMap, metrics) {
881
- const {
882
- // delta,
883
- normalizedItemWidth, normalizedItemHeight,
884
- // width,
885
- // height,
886
- dynamicSize,
887
- // itemSize,
888
- itemsFromStartToScrollEnd,
889
- // itemsFromStartToDisplayEnd,
890
- // itemsOnDisplay,
891
- // itemsOnDisplayLength,
892
- isVertical,
893
- // leftHiddenItemsWeight,
894
- // leftItemLength,
895
- // leftItemsWeight,
896
- renderItems: renderItemsLength,
897
- // rightItemLength,
898
- // rightItemsWeight,
899
- scrollSize, sizeProperty, snap, snippedPos, startPosition,
900
- // totalItemsToDisplayEndWeight,
901
- totalLength,
902
- // totalSize,
903
- startIndex, typicalItemSize, } = metrics, displayItems = [];
911
+ const { normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
904
912
  if (items.length) {
905
913
  const actualSnippedPosition = snippedPos;
906
914
  let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0;
@@ -1013,24 +1021,6 @@ class TrackBox extends CacheMap {
1013
1021
  this.set(itemId, bounds);
1014
1022
  }
1015
1023
  }
1016
- /**
1017
- * Returns calculated bounds from cache
1018
- */
1019
- getBoundsFromCache(items, typicalItemSize, isVertical) {
1020
- const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, map = this._map;
1021
- let size = 0;
1022
- for (let i = 0, l = items.length; i < l; i++) {
1023
- const item = items[i];
1024
- if (map.has(item.id)) {
1025
- const bounds = map.get(item.id);
1026
- size += bounds ? bounds[sizeProperty] : typicalItemSize;
1027
- }
1028
- else {
1029
- size += typicalItemSize;
1030
- }
1031
- }
1032
- return size;
1033
- }
1034
1024
  dispose() {
1035
1025
  super.dispose();
1036
1026
  if (this._debounceChanges) {
@@ -1112,7 +1102,7 @@ class NgVirtualListComponent {
1112
1102
  onScrollEnd = output();
1113
1103
  _itemsOptions = {
1114
1104
  transform: (v) => {
1115
- this._trackBox.resetCollection(v);
1105
+ this._trackBox.resetCollection(v, this.itemSize());
1116
1106
  return v;
1117
1107
  },
1118
1108
  };
@@ -1176,11 +1166,18 @@ class NgVirtualListComponent {
1176
1166
  _displayComponents = [];
1177
1167
  _bounds = signal(null);
1178
1168
  _scrollSize = signal(0);
1169
+ _isScrollingDebounces = debounce((v) => {
1170
+ this._isScrolling = v;
1171
+ }, 250);
1172
+ _isScrolling = false;
1173
+ get isScrolling() { return this._isScrolling; }
1179
1174
  _resizeObserver = null;
1180
1175
  _onResizeHandler = () => {
1181
1176
  this._bounds.set(this._container()?.nativeElement?.getBoundingClientRect() ?? null);
1182
1177
  };
1183
1178
  _onScrollHandler = (e) => {
1179
+ this._isScrolling = true;
1180
+ this._isScrollingDebounces.dispose();
1184
1181
  this.clearScrollToRepeatExecutionTimeout();
1185
1182
  const container = this._container()?.nativeElement;
1186
1183
  if (container) {
@@ -1207,6 +1204,7 @@ class NgVirtualListComponent {
1207
1204
  container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1208
1205
  const handler = () => {
1209
1206
  if (container) {
1207
+ this._isScrollingDebounces.execute(false);
1210
1208
  container.nativeElement.removeEventListener(SCROLL_END, handler);
1211
1209
  container.nativeElement.scroll(params);
1212
1210
  if (cb !== undefined) {
@@ -1230,6 +1228,7 @@ class NgVirtualListComponent {
1230
1228
  }
1231
1229
  }
1232
1230
  _onScrollEndHandler = (e) => {
1231
+ this._isScrollingDebounces.execute(false);
1233
1232
  const container = this._container();
1234
1233
  if (container) {
1235
1234
  this._trackBox.clearDelta();
@@ -1287,11 +1286,7 @@ class NgVirtualListComponent {
1287
1286
  this._initialized = signal(false);
1288
1287
  this.$initialized = toObservable(this._initialized);
1289
1288
  this._trackBox.displayComponents = this._displayComponents;
1290
- const $enabledBufferOptimization = toObservable(this.enabledBufferOptimization);
1291
- $enabledBufferOptimization.pipe(tap(v => {
1292
- this._trackBox.enabledBufferOptimization = v;
1293
- })).subscribe();
1294
- const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = toObservable(this.itemsOffset).pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = toObservable(this.stickyMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $cacheVersion = this.$cacheVersion;
1289
+ const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = toObservable(this.itemsOffset).pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = toObservable(this.stickyMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $snapToItem = toObservable(this.snapToItem), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $cacheVersion = this.$cacheVersion;
1295
1290
  $isVertical.pipe(takeUntilDestroyed(), tap(v => {
1296
1291
  this._isVertical = v;
1297
1292
  const el = this._elementRef.nativeElement;
@@ -1301,20 +1296,35 @@ class NgVirtualListComponent {
1301
1296
  this.listenCacheChangesIfNeed(dynamicSize);
1302
1297
  })).subscribe();
1303
1298
  combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1304
- $itemsOffset, $snap, $isVertical, $dynamicSize, $cacheVersion,
1305
- ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, cacheVersion,]) => {
1299
+ $itemsOffset, $snap, $snapToItem, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1300
+ ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, snapToItem, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
1306
1301
  const { width, height } = bounds;
1307
1302
  let actualScrollSize = scrollSize;
1308
1303
  const opts = {
1309
1304
  bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
1310
- itemsOffset, scrollSize: scrollSize, snap,
1305
+ itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
1311
1306
  };
1312
- const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1307
+ const { displayItems, totalSize, delta } = this._trackBox.updateCollection(items, stickyMap, {
1313
1308
  ...opts, scrollSize: actualScrollSize,
1314
1309
  });
1315
1310
  this.resetBoundsSize(isVertical, totalSize);
1316
1311
  this.createDisplayComponentsIfNeed(displayItems);
1317
1312
  this.tracking();
1313
+ const container = this._container();
1314
+ if (!this.isScrolling && dynamicSize && container) {
1315
+ actualScrollSize = scrollSize + delta;
1316
+ if (snapToItem) {
1317
+ // etc
1318
+ }
1319
+ else if (scrollSize !== actualScrollSize) {
1320
+ const params = {
1321
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1322
+ behavior: BEHAVIOR_INSTANT
1323
+ };
1324
+ this.scrollImmediately(container, params);
1325
+ this._trackBox.clearDelta();
1326
+ }
1327
+ }
1318
1328
  return of(displayItems);
1319
1329
  })).subscribe();
1320
1330
  combineLatest([this.$initialized, toObservable(this.itemRenderer)]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
@@ -1418,7 +1428,7 @@ class NgVirtualListComponent {
1418
1428
  const { width, height } = this._bounds() || { width: 0, height: 0 }, stickyMap = this.stickyMap(), items = this.items(), isVertical = this._isVertical, opts = {
1419
1429
  bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
1420
1430
  itemsOffset: this.itemsOffset(), scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1421
- snap: this.snap(), fromItemId: id,
1431
+ snap: this.snap(), fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization(),
1422
1432
  }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1423
1433
  this._scrollSize.set(scrollSize);
1424
1434
  if (container) {
@@ -1463,8 +1473,8 @@ class NgVirtualListComponent {
1463
1473
  _onContainerScrollHandler = (e) => {
1464
1474
  const containerEl = this._container();
1465
1475
  if (containerEl) {
1466
- 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);
1467
- this._trackBox.deltaDirection = this._scrollSize() >= scrollSize || (scrollSize + offsetSize) >= listSize ? -1 : 1;
1476
+ const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
1477
+ this._trackBox.deltaDirection = this._scrollSize() > scrollSize ? -1 : this._scrollSize() < scrollSize ? 1 : 0;
1468
1478
  const event = new ScrollEvent({
1469
1479
  direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
1470
1480
  list: this._list().nativeElement, delta: this._trackBox.delta,
@@ -1474,9 +1484,10 @@ class NgVirtualListComponent {
1474
1484
  }
1475
1485
  };
1476
1486
  _onContainerScrollEndHandler = (e) => {
1477
- this._trackBox.deltaDirection = -1;
1478
1487
  const containerEl = this._container();
1479
1488
  if (containerEl) {
1489
+ const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
1490
+ this._trackBox.deltaDirection = this._scrollSize() > scrollSize ? -1 : 0;
1480
1491
  const event = new ScrollEvent({
1481
1492
  direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
1482
1493
  list: this._list().nativeElement, delta: this._trackBox.delta,
@@ -1503,6 +1514,9 @@ class NgVirtualListComponent {
1503
1514
  if (this._trackBox) {
1504
1515
  this._trackBox.dispose();
1505
1516
  }
1517
+ if (this._isScrollingDebounces) {
1518
+ this._isScrollingDebounces.dispose();
1519
+ }
1506
1520
  const containerEl = this._container();
1507
1521
  if (containerEl) {
1508
1522
  containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);