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 +25 -24
- package/fesm2022/ng-virtual-list.mjs +137 -123
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/models/scroll-direction.model.d.ts +1 -1
- package/lib/ng-virtual-list.component.d.ts +3 -0
- package/lib/utils/cacheMap.d.ts +2 -1
- package/lib/utils/collection.d.ts +2 -1
- package/lib/utils/trackBox.d.ts +14 -13
- package/package.json +1 -1
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
<div class="list__h-group-container">
|
|
73
|
+
<span>{{data.name}}</span>
|
|
74
|
+
</div>
|
|
74
75
|
}
|
|
75
76
|
@default {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
<div class="list__group-container">
|
|
172
|
+
<p>{{data.name}}</p>
|
|
173
|
+
</div>
|
|
173
174
|
}
|
|
174
175
|
@default {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
<div class="list__group-container">
|
|
199
|
+
<p>{{data.name}}</p>
|
|
200
|
+
</div>
|
|
200
201
|
}
|
|
201
202
|
@default {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 = {
|
|
512
|
+
const result = { deleted: new Array(), updated: new Array(), added: new Array(), notChanged: new Array() };
|
|
509
513
|
if (!currentCollection || currentCollection.length === 0) {
|
|
510
|
-
return {
|
|
514
|
+
return { deleted: (previousCollection ? [...previousCollection] : []), updated: [], added: [], notChanged: [] };
|
|
511
515
|
}
|
|
512
516
|
if (!previousCollection || previousCollection.length === 0) {
|
|
513
|
-
return {
|
|
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 = {},
|
|
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.
|
|
534
|
-
|
|
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 && !
|
|
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 {
|
|
602
|
-
this.clearCache(
|
|
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(
|
|
619
|
-
if (
|
|
620
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
this._map.
|
|
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,
|
|
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,
|
|
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
|
|
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(
|
|
718
|
-
const bounds = map.get(
|
|
719
|
-
componentSize = bounds
|
|
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 (
|
|
757
|
+
if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
|
|
728
758
|
stickyComponentSize = componentSize;
|
|
729
759
|
stickyCollectionItem = collectionItem;
|
|
730
760
|
}
|
|
731
|
-
if (
|
|
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 (
|
|
767
|
-
|
|
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 (
|
|
781
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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)
|
|
1467
|
-
this._trackBox.deltaDirection = this._scrollSize()
|
|
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);
|