ng-virtual-list 19.1.20 → 19.1.22
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 +2 -0
- package/fesm2022/ng-virtual-list.mjs +164 -40
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/const/index.d.ts +2 -0
- package/lib/models/scroll-event.model.d.ts +28 -0
- package/lib/ng-virtual-list.component.d.ts +18 -2
- package/lib/utils/cacheMap.d.ts +3 -1
- package/lib/utils/collection.d.ts +12 -1
- package/lib/utils/scrollEvent.d.ts +18 -1
- package/lib/utils/trackBox.d.ts +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -451,6 +451,8 @@ Inputs
|
|
|
451
451
|
| snapToItem | boolean? = false | Determines whether scroll positions will be snapped to the element. Default value is "false". |
|
|
452
452
|
| direction | [Direction? = 'vertical'](https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/enums/direction.ts) | Determines the direction in which elements are placed. Default value is "vertical". |
|
|
453
453
|
| dynamicSize | boolean? = false | If true then the items in the list can have different sizes and the itemSize property is ignored. If false then the items in the list have a fixed size specified by the itemSize property. The default value is false. |
|
|
454
|
+
| enabledBufferOptimization | boolean? = true | Enables buffer optimization. Can only be used if items in the collection are not added or updated. |
|
|
455
|
+
| likeAChat | boolean? = false | If true, optimization for lists that start from the end is enabled. |
|
|
454
456
|
|
|
455
457
|
<br/>
|
|
456
458
|
|
|
@@ -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, filter, map,
|
|
6
|
+
import { BehaviorSubject, tap, filter, map, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Axis of the arrangement of virtual list elements.
|
|
@@ -27,6 +27,8 @@ const DEFAULT_ITEM_SIZE = 24;
|
|
|
27
27
|
const DEFAULT_ITEMS_OFFSET = 2;
|
|
28
28
|
const DEFAULT_LIST_SIZE = 400;
|
|
29
29
|
const DEFAULT_SNAP = false;
|
|
30
|
+
const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = true;
|
|
31
|
+
const DEFAULT_OPTIMIZE_FOR_END = false;
|
|
30
32
|
const DEFAULT_SNAP_TO_ITEM = false;
|
|
31
33
|
const DEFAULT_DYNAMIC_SIZE = false;
|
|
32
34
|
const TRACK_BY_PROPERTY_NAME = 'id';
|
|
@@ -406,7 +408,7 @@ class EventEmitter {
|
|
|
406
408
|
}
|
|
407
409
|
}
|
|
408
410
|
|
|
409
|
-
const MAX_SCROLL_DIRECTION_POOL =
|
|
411
|
+
const MAX_SCROLL_DIRECTION_POOL = 8, CLEAR_SCROLL_DIRECTION_TO = 0;
|
|
410
412
|
/**
|
|
411
413
|
* Cache map.
|
|
412
414
|
* Emits a change event on each mutation.
|
|
@@ -417,7 +419,7 @@ const MAX_SCROLL_DIRECTION_POOL = 10, CLEAR_SCROLL_DIRECTION_TO = 0;
|
|
|
417
419
|
class CacheMap extends EventEmitter {
|
|
418
420
|
_map = new Map();
|
|
419
421
|
_version = 0;
|
|
420
|
-
|
|
422
|
+
_previouseFullSize = 0;
|
|
421
423
|
_delta = 0;
|
|
422
424
|
get delta() {
|
|
423
425
|
return this._delta;
|
|
@@ -430,6 +432,17 @@ class CacheMap extends EventEmitter {
|
|
|
430
432
|
get deltaDirection() {
|
|
431
433
|
return this._deltaDirection;
|
|
432
434
|
}
|
|
435
|
+
_likeAChat = false;
|
|
436
|
+
set likeAChat(v) {
|
|
437
|
+
if (this._likeAChat === v) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (v) {
|
|
441
|
+
this._scrollDirection = -1;
|
|
442
|
+
}
|
|
443
|
+
this._scrollDirectionCache = [];
|
|
444
|
+
this._likeAChat = v;
|
|
445
|
+
}
|
|
433
446
|
_scrollDirectionCache = [];
|
|
434
447
|
_scrollDirection = 1;
|
|
435
448
|
get scrollDirection() {
|
|
@@ -499,11 +512,17 @@ class CacheMap extends EventEmitter {
|
|
|
499
512
|
|
|
500
513
|
/**
|
|
501
514
|
* Returns the removed or updated elements of a collection.
|
|
515
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/collection.ts
|
|
516
|
+
* @author Evgenii Grebennikov
|
|
517
|
+
* @email djonnyx@gmail.com
|
|
502
518
|
*/
|
|
503
519
|
const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollection) => {
|
|
504
|
-
const result = new Array();
|
|
505
|
-
if (!currentCollection || currentCollection.length === 0
|
|
506
|
-
return (previousCollection ? [...previousCollection] : []);
|
|
520
|
+
const result = { deletedOrUpdated: new Array(), added: new Array(), notChanged: new Array() };
|
|
521
|
+
if (!currentCollection || currentCollection.length === 0) {
|
|
522
|
+
return { deletedOrUpdated: (previousCollection ? [...previousCollection] : []), added: [], notChanged: [] };
|
|
523
|
+
}
|
|
524
|
+
if (!previousCollection || previousCollection.length === 0) {
|
|
525
|
+
return { deletedOrUpdated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
|
|
507
526
|
}
|
|
508
527
|
const collectionDict = {};
|
|
509
528
|
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
@@ -512,15 +531,25 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
|
|
|
512
531
|
collectionDict[item.id] = item;
|
|
513
532
|
}
|
|
514
533
|
}
|
|
534
|
+
const notChangedMap = {}, deletedOrUpdatedMap = {};
|
|
515
535
|
for (let i = 0, l = previousCollection.length; i < l; i++) {
|
|
516
536
|
const item = previousCollection[i], id = item.id;
|
|
517
537
|
if (item) {
|
|
518
538
|
if (collectionDict.hasOwnProperty(id)) {
|
|
519
539
|
if (item === collectionDict[id]) {
|
|
540
|
+
result.notChanged.push(item);
|
|
541
|
+
notChangedMap[item.id] = item;
|
|
520
542
|
continue;
|
|
521
543
|
}
|
|
522
544
|
}
|
|
523
|
-
result.push(item);
|
|
545
|
+
result.deletedOrUpdated.push(item);
|
|
546
|
+
deletedOrUpdatedMap[item.id] = item;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
550
|
+
const item = currentCollection[i];
|
|
551
|
+
if (item && !deletedOrUpdatedMap.hasOwnProperty(item.id) && !notChangedMap.hasOwnProperty(item.id)) {
|
|
552
|
+
result.added.push(item);
|
|
524
553
|
}
|
|
525
554
|
}
|
|
526
555
|
return result;
|
|
@@ -549,6 +578,7 @@ class TrackBox extends CacheMap {
|
|
|
549
578
|
}
|
|
550
579
|
this._displayComponents = v;
|
|
551
580
|
}
|
|
581
|
+
enabledBufferOptimization = false;
|
|
552
582
|
constructor(trackingPropertyName) {
|
|
553
583
|
super();
|
|
554
584
|
this._tracker = new Tracker(trackingPropertyName);
|
|
@@ -565,6 +595,8 @@ class TrackBox extends CacheMap {
|
|
|
565
595
|
_fireChanges = (version) => {
|
|
566
596
|
this.dispatch(TRACK_BOX_CHANGE_EVENT_NAME, version);
|
|
567
597
|
};
|
|
598
|
+
_isExistAddedItems = false;
|
|
599
|
+
_addedItemsMap = {};
|
|
568
600
|
_previousCollection;
|
|
569
601
|
_debounceChanges = debounce(this._fireChanges, 0);
|
|
570
602
|
fireChange() {
|
|
@@ -578,10 +610,20 @@ class TrackBox extends CacheMap {
|
|
|
578
610
|
console.warn('Attention! The collection must be immutable.');
|
|
579
611
|
return;
|
|
580
612
|
}
|
|
581
|
-
const
|
|
582
|
-
this.clearCache(
|
|
613
|
+
const { deletedOrUpdated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
|
|
614
|
+
this.clearCache(deletedOrUpdated);
|
|
615
|
+
this.startScrollDeltaCalculationIfNeed(added);
|
|
583
616
|
this._previousCollection = currentCollection;
|
|
584
617
|
}
|
|
618
|
+
startScrollDeltaCalculationIfNeed(added) {
|
|
619
|
+
if (added.length > 0) {
|
|
620
|
+
this._isExistAddedItems = true;
|
|
621
|
+
}
|
|
622
|
+
for (let i = 0, l = added.length; i < l; i++) {
|
|
623
|
+
const item = added[i];
|
|
624
|
+
this._addedItemsMap[item.id] = item;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
585
627
|
/**
|
|
586
628
|
* Clears the cache of items from the list
|
|
587
629
|
*/
|
|
@@ -675,10 +717,11 @@ class TrackBox extends CacheMap {
|
|
|
675
717
|
*/
|
|
676
718
|
recalculateMetrics(options) {
|
|
677
719
|
const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap } = options;
|
|
678
|
-
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, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
|
|
720
|
+
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)
|
|
679
721
|
|| (typeof fromItemId === 'string' && fromItemId > '-1');
|
|
680
|
-
let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, itemById = undefined, itemByIdPos = 0, targetDisplayItemIndex = -1, isTargetInOverscroll = false, actualScrollSize = itemByIdPos, totalSize = 0, startIndex;
|
|
681
|
-
|
|
722
|
+
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;
|
|
723
|
+
// If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
|
|
724
|
+
if (dynamicSize || this._isExistAddedItems) {
|
|
682
725
|
let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
|
|
683
726
|
for (let i = 0, l = collection.length; i < l; i++) {
|
|
684
727
|
const ii = i + 1, collectionItem = collection[i];
|
|
@@ -729,16 +772,26 @@ class TrackBox extends CacheMap {
|
|
|
729
772
|
if (itemById === undefined || y < itemByIdPos + size + componentSize) {
|
|
730
773
|
itemsFromStartToDisplayEnd = ii;
|
|
731
774
|
totalItemsToDisplayEndWeight += componentSize;
|
|
732
|
-
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd +
|
|
775
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
|
|
776
|
+
}
|
|
777
|
+
if (y > itemByIdPos + size + componentSize) {
|
|
778
|
+
if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
|
|
779
|
+
sizeOfAddedItems += componentSize;
|
|
780
|
+
}
|
|
733
781
|
}
|
|
734
782
|
}
|
|
735
783
|
else if (y < scrollSize + size + componentSize) {
|
|
736
784
|
itemsFromStartToDisplayEnd = ii;
|
|
737
785
|
totalItemsToDisplayEndWeight += componentSize;
|
|
738
|
-
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd +
|
|
786
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
|
|
739
787
|
}
|
|
740
|
-
else
|
|
741
|
-
|
|
788
|
+
else {
|
|
789
|
+
if (i < itemsFromDisplayEndToOffsetEnd) {
|
|
790
|
+
rightItemsWeight += componentSize;
|
|
791
|
+
}
|
|
792
|
+
if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
|
|
793
|
+
sizeOfAddedItems += componentSize;
|
|
794
|
+
}
|
|
742
795
|
}
|
|
743
796
|
y += componentSize;
|
|
744
797
|
}
|
|
@@ -755,15 +808,17 @@ class TrackBox extends CacheMap {
|
|
|
755
808
|
itemsFromStartToDisplayEnd = 0;
|
|
756
809
|
}
|
|
757
810
|
actualScrollSize = isFromId ? itemByIdPos : scrollSize;
|
|
758
|
-
leftItemsWeights.splice(0, leftItemsWeights.length -
|
|
811
|
+
leftItemsWeights.splice(0, leftItemsWeights.length - leftItemsOffset);
|
|
759
812
|
leftItemsWeights.forEach(v => {
|
|
760
813
|
leftItemsWeight += v;
|
|
761
814
|
});
|
|
762
|
-
leftItemLength = Math.min(itemsFromStartToScrollEnd,
|
|
763
|
-
rightItemLength = itemsFromStartToDisplayEnd +
|
|
764
|
-
? totalLength - itemsFromStartToDisplayEnd :
|
|
815
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, leftItemsOffset);
|
|
816
|
+
rightItemLength = itemsFromStartToDisplayEnd + rightItemsOffset > totalLength
|
|
817
|
+
? totalLength - itemsFromStartToDisplayEnd : rightItemsOffset;
|
|
765
818
|
}
|
|
766
|
-
else
|
|
819
|
+
else
|
|
820
|
+
// Buffer optimization does not work on fast linear algorithm
|
|
821
|
+
{
|
|
767
822
|
itemsFromStartToScrollEnd = Math.floor(scrollSize / typicalItemSize);
|
|
768
823
|
itemsFromStartToDisplayEnd = Math.ceil((scrollSize + size) / typicalItemSize);
|
|
769
824
|
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
@@ -777,9 +832,12 @@ class TrackBox extends CacheMap {
|
|
|
777
832
|
totalSize = totalLength * typicalItemSize;
|
|
778
833
|
}
|
|
779
834
|
startIndex = Math.min(itemsFromStartToScrollEnd - leftItemLength, totalLength > 0 ? totalLength - 1 : 0);
|
|
780
|
-
const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = totalSize - this.
|
|
835
|
+
const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = totalSize - this._previouseFullSize, scrollDelta = sizeOfAddedItems;
|
|
781
836
|
if (this.scrollDirection === -1) {
|
|
782
|
-
this.
|
|
837
|
+
if (this._isExistAddedItems && sizeOfAddedItems > 0) {
|
|
838
|
+
this.stopScrollDeltaCalculation();
|
|
839
|
+
}
|
|
840
|
+
this._delta += delta - scrollDelta;
|
|
783
841
|
}
|
|
784
842
|
const metrics = {
|
|
785
843
|
delta: this._delta,
|
|
@@ -801,6 +859,7 @@ class TrackBox extends CacheMap {
|
|
|
801
859
|
rightItemLength,
|
|
802
860
|
rightItemsWeight,
|
|
803
861
|
scrollSize: actualScrollSize,
|
|
862
|
+
sizeOfAddedItems,
|
|
804
863
|
sizeProperty,
|
|
805
864
|
snap,
|
|
806
865
|
snippedPos,
|
|
@@ -811,18 +870,25 @@ class TrackBox extends CacheMap {
|
|
|
811
870
|
totalSize,
|
|
812
871
|
typicalItemSize,
|
|
813
872
|
};
|
|
814
|
-
this.
|
|
873
|
+
this._previouseFullSize = totalSize;
|
|
815
874
|
return metrics;
|
|
816
875
|
}
|
|
876
|
+
_scrollDelta = 0;
|
|
877
|
+
get scrollDelta() { return this._scrollDelta; }
|
|
817
878
|
clearDeltaDirection() {
|
|
818
879
|
this.clearScrollDirectionCache();
|
|
819
880
|
}
|
|
820
881
|
clearDelta(clearDirectionDetector = false) {
|
|
821
882
|
this._delta = 0;
|
|
883
|
+
this.stopScrollDeltaCalculation();
|
|
822
884
|
if (clearDirectionDetector) {
|
|
823
885
|
this.clearScrollDirectionCache();
|
|
824
886
|
}
|
|
825
887
|
}
|
|
888
|
+
stopScrollDeltaCalculation() {
|
|
889
|
+
this._isExistAddedItems = false;
|
|
890
|
+
this._addedItemsMap = {};
|
|
891
|
+
}
|
|
826
892
|
generateDisplayCollection(items, stickyMap, metrics) {
|
|
827
893
|
const {
|
|
828
894
|
// delta,
|
|
@@ -940,6 +1006,12 @@ class TrackBox extends CacheMap {
|
|
|
940
1006
|
untrackComponentByIdProperty(component) {
|
|
941
1007
|
this._tracker.untrackComponentByIdProperty(component);
|
|
942
1008
|
}
|
|
1009
|
+
getItemBounds(id) {
|
|
1010
|
+
if (this.has(id)) {
|
|
1011
|
+
return this.get(id);
|
|
1012
|
+
}
|
|
1013
|
+
return undefined;
|
|
1014
|
+
}
|
|
943
1015
|
cacheElements() {
|
|
944
1016
|
if (!this._displayComponents) {
|
|
945
1017
|
return;
|
|
@@ -982,6 +1054,12 @@ class TrackBox extends CacheMap {
|
|
|
982
1054
|
}
|
|
983
1055
|
}
|
|
984
1056
|
|
|
1057
|
+
/**
|
|
1058
|
+
* Scroll event.
|
|
1059
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/scrollEvent.ts
|
|
1060
|
+
* @author Evgenii Grebennikov
|
|
1061
|
+
* @email djonnyx@gmail.com
|
|
1062
|
+
*/
|
|
985
1063
|
class ScrollEvent {
|
|
986
1064
|
_direction = 1;
|
|
987
1065
|
get direction() { return this._direction; }
|
|
@@ -1001,7 +1079,10 @@ class ScrollEvent {
|
|
|
1001
1079
|
get isEnd() { return this._isEnd; }
|
|
1002
1080
|
_delta = 0;
|
|
1003
1081
|
get delta() { return this._delta; }
|
|
1004
|
-
|
|
1082
|
+
_scrollDelta = 0;
|
|
1083
|
+
get scrollDelta() { return this._scrollDelta; }
|
|
1084
|
+
constructor(params) {
|
|
1085
|
+
const { direction, isVertical, container, list, delta, scrollDelta } = params;
|
|
1005
1086
|
this._direction = direction;
|
|
1006
1087
|
this._isVertical = isVertical;
|
|
1007
1088
|
this._scrollSize = isVertical ? container.scrollTop : container.scrollLeft;
|
|
@@ -1010,6 +1091,7 @@ class ScrollEvent {
|
|
|
1010
1091
|
this._size = isVertical ? container.offsetHeight : container.offsetWidth;
|
|
1011
1092
|
this._isEnd = (this._scrollSize + this._size) === this._scrollWeight;
|
|
1012
1093
|
this._delta = delta;
|
|
1094
|
+
this._scrollDelta = scrollDelta;
|
|
1013
1095
|
this._isStart = this._scrollSize === 0;
|
|
1014
1096
|
}
|
|
1015
1097
|
}
|
|
@@ -1060,6 +1142,16 @@ class NgVirtualListComponent {
|
|
|
1060
1142
|
* Determines whether scroll positions will be snapped to the element. Default value is "false".
|
|
1061
1143
|
*/
|
|
1062
1144
|
snapToItem = input(DEFAULT_SNAP_TO_ITEM);
|
|
1145
|
+
/**
|
|
1146
|
+
* Enables buffer optimization.
|
|
1147
|
+
* Can only be used if items in the collection are not added or updated. Otherwise, artifacts in the form of twitching of the scroll area are possible.
|
|
1148
|
+
* Works only if the property dynamic = true
|
|
1149
|
+
*/
|
|
1150
|
+
enabledBufferOptimization = input(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
|
|
1151
|
+
/**
|
|
1152
|
+
* If true, optimization for lists that start from the end is enabled (chat mode enabled).
|
|
1153
|
+
*/
|
|
1154
|
+
likeAChat = input(DEFAULT_OPTIMIZE_FOR_END);
|
|
1063
1155
|
/**
|
|
1064
1156
|
* Rendering element template.
|
|
1065
1157
|
*/
|
|
@@ -1108,9 +1200,8 @@ class NgVirtualListComponent {
|
|
|
1108
1200
|
this.clearScrollToRepeatExecutionTimeout();
|
|
1109
1201
|
const container = this._container()?.nativeElement;
|
|
1110
1202
|
if (container) {
|
|
1111
|
-
const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft)
|
|
1203
|
+
const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
|
|
1112
1204
|
let actualScrollSize = scrollSize, isImmediateScroll = false;
|
|
1113
|
-
this._trackBox.deltaDirection = previouseScrollSize > scrollSize ? -1 : 1;
|
|
1114
1205
|
if (dynamicSize && delta !== 0) {
|
|
1115
1206
|
actualScrollSize = scrollSize + delta;
|
|
1116
1207
|
const params = {
|
|
@@ -1120,18 +1211,11 @@ class NgVirtualListComponent {
|
|
|
1120
1211
|
const container = this._container();
|
|
1121
1212
|
if (container) {
|
|
1122
1213
|
isImmediateScroll = true;
|
|
1123
|
-
this.scrollImmediately(container, params
|
|
1124
|
-
const event = new ScrollEvent(this._trackBox.scrollDirection, container.nativeElement, this._list().nativeElement, delta, this._isVertical);
|
|
1125
|
-
this.onScroll.emit(event);
|
|
1126
|
-
});
|
|
1214
|
+
this.scrollImmediately(container, params);
|
|
1127
1215
|
this._trackBox.clearDelta();
|
|
1128
1216
|
}
|
|
1129
1217
|
}
|
|
1130
1218
|
this._scrollSize.set(actualScrollSize);
|
|
1131
|
-
if (!isImmediateScroll) {
|
|
1132
|
-
const event = new ScrollEvent(this._trackBox.scrollDirection, container, this._list().nativeElement, delta, this._isVertical);
|
|
1133
|
-
this.onScroll.emit(event);
|
|
1134
|
-
}
|
|
1135
1219
|
}
|
|
1136
1220
|
};
|
|
1137
1221
|
scrollImmediately(container, params, cb) {
|
|
@@ -1168,7 +1252,6 @@ class NgVirtualListComponent {
|
|
|
1168
1252
|
this._trackBox.clearDeltaDirection();
|
|
1169
1253
|
const itemSize = this.itemSize(), snapToItem = this.snapToItem(), dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1170
1254
|
let actualScrollSize = scrollSize;
|
|
1171
|
-
const event = new ScrollEvent(this._trackBox.scrollDirection, container.nativeElement, this._list().nativeElement, delta, this._isVertical);
|
|
1172
1255
|
if (dynamicSize) {
|
|
1173
1256
|
actualScrollSize = scrollSize + delta;
|
|
1174
1257
|
if (snapToItem) {
|
|
@@ -1199,7 +1282,6 @@ class NgVirtualListComponent {
|
|
|
1199
1282
|
}
|
|
1200
1283
|
}
|
|
1201
1284
|
this._scrollSize.set(actualScrollSize);
|
|
1202
|
-
this.onScrollEnd.emit(event);
|
|
1203
1285
|
}
|
|
1204
1286
|
};
|
|
1205
1287
|
_elementRef = inject((ElementRef));
|
|
@@ -1221,6 +1303,14 @@ class NgVirtualListComponent {
|
|
|
1221
1303
|
this._initialized = signal(false);
|
|
1222
1304
|
this.$initialized = toObservable(this._initialized);
|
|
1223
1305
|
this._trackBox.displayComponents = this._displayComponents;
|
|
1306
|
+
const $enabledBufferOptimization = toObservable(this.enabledBufferOptimization);
|
|
1307
|
+
$enabledBufferOptimization.pipe(takeUntilDestroyed(), tap(v => {
|
|
1308
|
+
this._trackBox.enabledBufferOptimization = v;
|
|
1309
|
+
})).subscribe();
|
|
1310
|
+
const $likeAChat = toObservable(this.likeAChat);
|
|
1311
|
+
$likeAChat.pipe(takeUntilDestroyed(), tap(v => {
|
|
1312
|
+
this._trackBox.likeAChat = v;
|
|
1313
|
+
})).subscribe();
|
|
1224
1314
|
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;
|
|
1225
1315
|
$isVertical.pipe(takeUntilDestroyed(), tap(v => {
|
|
1226
1316
|
this._isVertical = v;
|
|
@@ -1315,6 +1405,12 @@ class NgVirtualListComponent {
|
|
|
1315
1405
|
l.nativeElement.style[isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME] = `${totalSize}${PX}`;
|
|
1316
1406
|
}
|
|
1317
1407
|
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Returns the bounds of an element with a given id
|
|
1410
|
+
*/
|
|
1411
|
+
getItemBounds(id) {
|
|
1412
|
+
return this._trackBox.getItemBounds(id);
|
|
1413
|
+
}
|
|
1318
1414
|
/**
|
|
1319
1415
|
* The method scrolls the list to the element with the given id and returns the value of the scrolled area.
|
|
1320
1416
|
* Behavior accepts the values "auto", "instant" and "smooth".
|
|
@@ -1364,8 +1460,6 @@ class NgVirtualListComponent {
|
|
|
1364
1460
|
}
|
|
1365
1461
|
else {
|
|
1366
1462
|
this._scrollSize.set(scrollSize);
|
|
1367
|
-
const event = new ScrollEvent(this._trackBox.scrollDirection, container.nativeElement, this._list().nativeElement, this._trackBox.delta, this._isVertical);
|
|
1368
|
-
this.onScroll.emit(event);
|
|
1369
1463
|
container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1370
1464
|
container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1371
1465
|
}
|
|
@@ -1386,9 +1480,37 @@ class NgVirtualListComponent {
|
|
|
1386
1480
|
const items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0];
|
|
1387
1481
|
this.scrollTo(latItem.id, behavior);
|
|
1388
1482
|
}
|
|
1483
|
+
_onContainerScrollHandler = (e) => {
|
|
1484
|
+
const containerEl = this._container();
|
|
1485
|
+
if (containerEl) {
|
|
1486
|
+
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);
|
|
1487
|
+
this._trackBox.deltaDirection = this._scrollSize() >= scrollSize ? -1 : this.likeAChat() && (scrollSize + offsetSize) >= listSize ? -1 : 1;
|
|
1488
|
+
const event = new ScrollEvent({
|
|
1489
|
+
direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
|
|
1490
|
+
list: this._list().nativeElement, delta: this._trackBox.delta,
|
|
1491
|
+
scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
|
|
1492
|
+
});
|
|
1493
|
+
this.onScroll.emit(event);
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
_onContainerScrollEndHandler = (e) => {
|
|
1497
|
+
this._trackBox.deltaDirection = this.likeAChat() ? -1 : 1;
|
|
1498
|
+
const containerEl = this._container();
|
|
1499
|
+
if (containerEl) {
|
|
1500
|
+
const event = new ScrollEvent({
|
|
1501
|
+
direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
|
|
1502
|
+
list: this._list().nativeElement, delta: this._trackBox.delta,
|
|
1503
|
+
scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
|
|
1504
|
+
});
|
|
1505
|
+
this.onScrollEnd.emit(event);
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1389
1508
|
ngAfterViewInit() {
|
|
1390
1509
|
const containerEl = this._container();
|
|
1391
1510
|
if (containerEl) {
|
|
1511
|
+
// for direction calculation
|
|
1512
|
+
containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1513
|
+
containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1392
1514
|
containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1393
1515
|
containerEl.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1394
1516
|
this._resizeObserver = new ResizeObserver(this._onResizeHandler);
|
|
@@ -1405,6 +1527,8 @@ class NgVirtualListComponent {
|
|
|
1405
1527
|
if (containerEl) {
|
|
1406
1528
|
containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1407
1529
|
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1530
|
+
containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1531
|
+
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1408
1532
|
if (this._resizeObserver) {
|
|
1409
1533
|
this._resizeObserver.unobserve(containerEl.nativeElement);
|
|
1410
1534
|
}
|
|
@@ -1417,7 +1541,7 @@ class NgVirtualListComponent {
|
|
|
1417
1541
|
}
|
|
1418
1542
|
}
|
|
1419
1543
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1420
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, snapToItem: { classPropertyName: "snapToItem", publicName: "snapToItem", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, stickyMap: { classPropertyName: "stickyMap", publicName: "stickyMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
1544
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, snapToItem: { classPropertyName: "snapToItem", publicName: "snapToItem", isSignal: true, isRequired: false, transformFunction: null }, enabledBufferOptimization: { classPropertyName: "enabledBufferOptimization", publicName: "enabledBufferOptimization", isSignal: true, isRequired: false, transformFunction: null }, likeAChat: { classPropertyName: "likeAChat", publicName: "likeAChat", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, stickyMap: { classPropertyName: "stickyMap", publicName: "stickyMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
1421
1545
|
}
|
|
1422
1546
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
1423
1547
|
type: Component,
|