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.
- package/esm2022/lib/const/index.mjs +1 -2
- package/esm2022/lib/models/scroll-direction.model.mjs +1 -1
- package/esm2022/lib/ng-virtual-list.component.mjs +43 -39
- package/esm2022/lib/utils/cacheMap.mjs +11 -18
- package/esm2022/lib/utils/collection.mjs +14 -9
- package/esm2022/lib/utils/trackBox.mjs +76 -95
- package/fesm2022/ng-virtual-list.mjs +138 -156
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/const/index.d.ts +0 -1
- package/lib/models/scroll-direction.model.d.ts +1 -1
- package/lib/ng-virtual-list.component.d.ts +4 -8
- package/lib/utils/cacheMap.d.ts +2 -3
- package/lib/utils/collection.d.ts +2 -1
- package/lib/utils/trackBox.d.ts +14 -13
- package/package.json +1 -1
|
@@ -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,
|
|
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 =
|
|
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 =
|
|
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
|
|
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 = {
|
|
518
|
+
const result = { deleted: new Array(), updated: new Array(), added: new Array(), notChanged: new Array() };
|
|
527
519
|
if (!currentCollection || currentCollection.length === 0) {
|
|
528
|
-
return {
|
|
520
|
+
return { deleted: (previousCollection ? [...previousCollection] : []), updated: [], added: [], notChanged: [] };
|
|
529
521
|
}
|
|
530
522
|
if (!previousCollection || previousCollection.length === 0) {
|
|
531
|
-
return {
|
|
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 = {},
|
|
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.
|
|
552
|
-
|
|
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 && !
|
|
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 {
|
|
620
|
-
this.clearCache(
|
|
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(
|
|
637
|
-
if (
|
|
638
|
-
|
|
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
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
this._map.
|
|
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,
|
|
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,
|
|
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
|
|
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(
|
|
736
|
-
const bounds = map.get(
|
|
737
|
-
componentSize = bounds
|
|
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 (
|
|
763
|
+
if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
|
|
746
764
|
stickyComponentSize = componentSize;
|
|
747
765
|
stickyCollectionItem = collectionItem;
|
|
748
766
|
}
|
|
749
|
-
if (
|
|
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 (
|
|
785
|
-
|
|
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 (
|
|
799
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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)
|
|
1600
|
-
this._trackBox.deltaDirection = this._$scrollSize.getValue()
|
|
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.
|
|
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",
|
|
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: [{
|