ng-virtual-list 19.1.19 → 19.1.21
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 +1 -0
- package/fesm2022/ng-virtual-list.mjs +145 -41
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/const/index.d.ts +1 -0
- package/lib/models/scroll-event.model.d.ts +28 -0
- package/lib/ng-virtual-list.component.d.ts +14 -2
- package/lib/utils/cacheMap.d.ts +1 -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,7 @@ 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. |
|
|
454
455
|
|
|
455
456
|
<br/>
|
|
456
457
|
|
|
@@ -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,7 @@ 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;
|
|
30
31
|
const DEFAULT_SNAP_TO_ITEM = false;
|
|
31
32
|
const DEFAULT_DYNAMIC_SIZE = false;
|
|
32
33
|
const TRACK_BY_PROPERTY_NAME = 'id';
|
|
@@ -406,7 +407,7 @@ class EventEmitter {
|
|
|
406
407
|
}
|
|
407
408
|
}
|
|
408
409
|
|
|
409
|
-
const MAX_SCROLL_DIRECTION_POOL =
|
|
410
|
+
const MAX_SCROLL_DIRECTION_POOL = 8, CLEAR_SCROLL_DIRECTION_TO = 0;
|
|
410
411
|
/**
|
|
411
412
|
* Cache map.
|
|
412
413
|
* Emits a change event on each mutation.
|
|
@@ -417,7 +418,7 @@ const MAX_SCROLL_DIRECTION_POOL = 10, CLEAR_SCROLL_DIRECTION_TO = 0;
|
|
|
417
418
|
class CacheMap extends EventEmitter {
|
|
418
419
|
_map = new Map();
|
|
419
420
|
_version = 0;
|
|
420
|
-
|
|
421
|
+
_previouseFullSize = 0;
|
|
421
422
|
_delta = 0;
|
|
422
423
|
get delta() {
|
|
423
424
|
return this._delta;
|
|
@@ -431,7 +432,7 @@ class CacheMap extends EventEmitter {
|
|
|
431
432
|
return this._deltaDirection;
|
|
432
433
|
}
|
|
433
434
|
_scrollDirectionCache = [];
|
|
434
|
-
_scrollDirection = 1;
|
|
435
|
+
_scrollDirection = -1;
|
|
435
436
|
get scrollDirection() {
|
|
436
437
|
return this._scrollDirection;
|
|
437
438
|
}
|
|
@@ -499,11 +500,17 @@ class CacheMap extends EventEmitter {
|
|
|
499
500
|
|
|
500
501
|
/**
|
|
501
502
|
* Returns the removed or updated elements of a collection.
|
|
503
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/collection.ts
|
|
504
|
+
* @author Evgenii Grebennikov
|
|
505
|
+
* @email djonnyx@gmail.com
|
|
502
506
|
*/
|
|
503
507
|
const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollection) => {
|
|
504
|
-
const result = new Array;
|
|
505
|
-
if (!currentCollection || currentCollection.length === 0
|
|
506
|
-
return (previousCollection ? [...previousCollection] : []);
|
|
508
|
+
const result = { deletedOrUpdated: new Array(), added: new Array(), notChanged: new Array() };
|
|
509
|
+
if (!currentCollection || currentCollection.length === 0) {
|
|
510
|
+
return { deletedOrUpdated: (previousCollection ? [...previousCollection] : []), added: [], notChanged: [] };
|
|
511
|
+
}
|
|
512
|
+
if (!previousCollection || previousCollection.length === 0) {
|
|
513
|
+
return { deletedOrUpdated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
|
|
507
514
|
}
|
|
508
515
|
const collectionDict = {};
|
|
509
516
|
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
@@ -512,15 +519,25 @@ const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollectio
|
|
|
512
519
|
collectionDict[item.id] = item;
|
|
513
520
|
}
|
|
514
521
|
}
|
|
522
|
+
const notChangedMap = {}, deletedOrUpdatedMap = {};
|
|
515
523
|
for (let i = 0, l = previousCollection.length; i < l; i++) {
|
|
516
524
|
const item = previousCollection[i], id = item.id;
|
|
517
525
|
if (item) {
|
|
518
526
|
if (collectionDict.hasOwnProperty(id)) {
|
|
519
527
|
if (item === collectionDict[id]) {
|
|
528
|
+
result.notChanged.push(item);
|
|
529
|
+
notChangedMap[item.id] = item;
|
|
520
530
|
continue;
|
|
521
531
|
}
|
|
522
532
|
}
|
|
523
|
-
result.push(item);
|
|
533
|
+
result.deletedOrUpdated.push(item);
|
|
534
|
+
deletedOrUpdatedMap[item.id] = item;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
538
|
+
const item = currentCollection[i];
|
|
539
|
+
if (item && !deletedOrUpdatedMap.hasOwnProperty(item.id) && !notChangedMap.hasOwnProperty(item.id)) {
|
|
540
|
+
result.added.push(item);
|
|
524
541
|
}
|
|
525
542
|
}
|
|
526
543
|
return result;
|
|
@@ -549,6 +566,7 @@ class TrackBox extends CacheMap {
|
|
|
549
566
|
}
|
|
550
567
|
this._displayComponents = v;
|
|
551
568
|
}
|
|
569
|
+
enabledBufferOptimization = false;
|
|
552
570
|
constructor(trackingPropertyName) {
|
|
553
571
|
super();
|
|
554
572
|
this._tracker = new Tracker(trackingPropertyName);
|
|
@@ -565,6 +583,8 @@ class TrackBox extends CacheMap {
|
|
|
565
583
|
_fireChanges = (version) => {
|
|
566
584
|
this.dispatch(TRACK_BOX_CHANGE_EVENT_NAME, version);
|
|
567
585
|
};
|
|
586
|
+
_isExistAddedItems = false;
|
|
587
|
+
_addedItemsMap = {};
|
|
568
588
|
_previousCollection;
|
|
569
589
|
_debounceChanges = debounce(this._fireChanges, 0);
|
|
570
590
|
fireChange() {
|
|
@@ -578,10 +598,20 @@ class TrackBox extends CacheMap {
|
|
|
578
598
|
console.warn('Attention! The collection must be immutable.');
|
|
579
599
|
return;
|
|
580
600
|
}
|
|
581
|
-
const
|
|
582
|
-
this.clearCache(
|
|
601
|
+
const { deletedOrUpdated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
|
|
602
|
+
this.clearCache(deletedOrUpdated);
|
|
603
|
+
this.startScrollDeltaCalculationIfNeed(added);
|
|
583
604
|
this._previousCollection = currentCollection;
|
|
584
605
|
}
|
|
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
|
+
}
|
|
585
615
|
/**
|
|
586
616
|
* Clears the cache of items from the list
|
|
587
617
|
*/
|
|
@@ -675,10 +705,11 @@ class TrackBox extends CacheMap {
|
|
|
675
705
|
*/
|
|
676
706
|
recalculateMetrics(options) {
|
|
677
707
|
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)
|
|
708
|
+
const { width, height } = bounds, sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, size = isVertical ? height : width, totalLength = collection.length, typicalItemSize = itemSize, w = isVertical ? width : typicalItemSize, h = isVertical ? typicalItemSize : height, map = this._map, leftItemsOffset = this.enabledBufferOptimization ? this.deltaDirection === 1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, rightItemsOffset = this.enabledBufferOptimization ? this.deltaDirection === -1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
|
|
679
709
|
|| (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
|
-
|
|
710
|
+
let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, sizeOfAddedItems = 0, itemById = undefined, itemByIdPos = 0, targetDisplayItemIndex = -1, isTargetInOverscroll = false, actualScrollSize = itemByIdPos, totalSize = 0, startIndex;
|
|
711
|
+
// If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
|
|
712
|
+
if (dynamicSize || this._isExistAddedItems) {
|
|
682
713
|
let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
|
|
683
714
|
for (let i = 0, l = collection.length; i < l; i++) {
|
|
684
715
|
const ii = i + 1, collectionItem = collection[i];
|
|
@@ -729,16 +760,26 @@ class TrackBox extends CacheMap {
|
|
|
729
760
|
if (itemById === undefined || y < itemByIdPos + size + componentSize) {
|
|
730
761
|
itemsFromStartToDisplayEnd = ii;
|
|
731
762
|
totalItemsToDisplayEndWeight += componentSize;
|
|
732
|
-
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd +
|
|
763
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
|
|
764
|
+
}
|
|
765
|
+
if (y > itemByIdPos + size + componentSize) {
|
|
766
|
+
if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
|
|
767
|
+
sizeOfAddedItems += componentSize;
|
|
768
|
+
}
|
|
733
769
|
}
|
|
734
770
|
}
|
|
735
771
|
else if (y < scrollSize + size + componentSize) {
|
|
736
772
|
itemsFromStartToDisplayEnd = ii;
|
|
737
773
|
totalItemsToDisplayEndWeight += componentSize;
|
|
738
|
-
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd +
|
|
774
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
|
|
739
775
|
}
|
|
740
|
-
else
|
|
741
|
-
|
|
776
|
+
else {
|
|
777
|
+
if (i < itemsFromDisplayEndToOffsetEnd) {
|
|
778
|
+
rightItemsWeight += componentSize;
|
|
779
|
+
}
|
|
780
|
+
if (this._addedItemsMap.hasOwnProperty(collectionItem.id)) {
|
|
781
|
+
sizeOfAddedItems += componentSize;
|
|
782
|
+
}
|
|
742
783
|
}
|
|
743
784
|
y += componentSize;
|
|
744
785
|
}
|
|
@@ -755,15 +796,17 @@ class TrackBox extends CacheMap {
|
|
|
755
796
|
itemsFromStartToDisplayEnd = 0;
|
|
756
797
|
}
|
|
757
798
|
actualScrollSize = isFromId ? itemByIdPos : scrollSize;
|
|
758
|
-
leftItemsWeights.splice(0, leftItemsWeights.length -
|
|
799
|
+
leftItemsWeights.splice(0, leftItemsWeights.length - leftItemsOffset);
|
|
759
800
|
leftItemsWeights.forEach(v => {
|
|
760
801
|
leftItemsWeight += v;
|
|
761
802
|
});
|
|
762
|
-
leftItemLength = Math.min(itemsFromStartToScrollEnd,
|
|
763
|
-
rightItemLength = itemsFromStartToDisplayEnd +
|
|
764
|
-
? totalLength - itemsFromStartToDisplayEnd :
|
|
803
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, leftItemsOffset);
|
|
804
|
+
rightItemLength = itemsFromStartToDisplayEnd + rightItemsOffset > totalLength
|
|
805
|
+
? totalLength - itemsFromStartToDisplayEnd : rightItemsOffset;
|
|
765
806
|
}
|
|
766
|
-
else
|
|
807
|
+
else
|
|
808
|
+
// Buffer optimization does not work on fast linear algorithm
|
|
809
|
+
{
|
|
767
810
|
itemsFromStartToScrollEnd = Math.floor(scrollSize / typicalItemSize);
|
|
768
811
|
itemsFromStartToDisplayEnd = Math.ceil((scrollSize + size) / typicalItemSize);
|
|
769
812
|
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
@@ -777,9 +820,12 @@ class TrackBox extends CacheMap {
|
|
|
777
820
|
totalSize = totalLength * typicalItemSize;
|
|
778
821
|
}
|
|
779
822
|
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.
|
|
823
|
+
const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = totalSize - this._previouseFullSize, scrollDelta = sizeOfAddedItems;
|
|
781
824
|
if (this.scrollDirection === -1) {
|
|
782
|
-
this.
|
|
825
|
+
if (this._isExistAddedItems && sizeOfAddedItems > 0) {
|
|
826
|
+
this.stopScrollDeltaCalculation();
|
|
827
|
+
}
|
|
828
|
+
this._delta += delta - scrollDelta;
|
|
783
829
|
}
|
|
784
830
|
const metrics = {
|
|
785
831
|
delta: this._delta,
|
|
@@ -801,6 +847,7 @@ class TrackBox extends CacheMap {
|
|
|
801
847
|
rightItemLength,
|
|
802
848
|
rightItemsWeight,
|
|
803
849
|
scrollSize: actualScrollSize,
|
|
850
|
+
sizeOfAddedItems,
|
|
804
851
|
sizeProperty,
|
|
805
852
|
snap,
|
|
806
853
|
snippedPos,
|
|
@@ -811,18 +858,25 @@ class TrackBox extends CacheMap {
|
|
|
811
858
|
totalSize,
|
|
812
859
|
typicalItemSize,
|
|
813
860
|
};
|
|
814
|
-
this.
|
|
861
|
+
this._previouseFullSize = totalSize;
|
|
815
862
|
return metrics;
|
|
816
863
|
}
|
|
864
|
+
_scrollDelta = 0;
|
|
865
|
+
get scrollDelta() { return this._scrollDelta; }
|
|
817
866
|
clearDeltaDirection() {
|
|
818
867
|
this.clearScrollDirectionCache();
|
|
819
868
|
}
|
|
820
869
|
clearDelta(clearDirectionDetector = false) {
|
|
821
870
|
this._delta = 0;
|
|
871
|
+
this.stopScrollDeltaCalculation();
|
|
822
872
|
if (clearDirectionDetector) {
|
|
823
873
|
this.clearScrollDirectionCache();
|
|
824
874
|
}
|
|
825
875
|
}
|
|
876
|
+
stopScrollDeltaCalculation() {
|
|
877
|
+
this._isExistAddedItems = false;
|
|
878
|
+
this._addedItemsMap = {};
|
|
879
|
+
}
|
|
826
880
|
generateDisplayCollection(items, stickyMap, metrics) {
|
|
827
881
|
const {
|
|
828
882
|
// delta,
|
|
@@ -940,6 +994,12 @@ class TrackBox extends CacheMap {
|
|
|
940
994
|
untrackComponentByIdProperty(component) {
|
|
941
995
|
this._tracker.untrackComponentByIdProperty(component);
|
|
942
996
|
}
|
|
997
|
+
getItemBounds(id) {
|
|
998
|
+
if (this.has(id)) {
|
|
999
|
+
return this.get(id);
|
|
1000
|
+
}
|
|
1001
|
+
return undefined;
|
|
1002
|
+
}
|
|
943
1003
|
cacheElements() {
|
|
944
1004
|
if (!this._displayComponents) {
|
|
945
1005
|
return;
|
|
@@ -982,6 +1042,12 @@ class TrackBox extends CacheMap {
|
|
|
982
1042
|
}
|
|
983
1043
|
}
|
|
984
1044
|
|
|
1045
|
+
/**
|
|
1046
|
+
* Scroll event.
|
|
1047
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/scrollEvent.ts
|
|
1048
|
+
* @author Evgenii Grebennikov
|
|
1049
|
+
* @email djonnyx@gmail.com
|
|
1050
|
+
*/
|
|
985
1051
|
class ScrollEvent {
|
|
986
1052
|
_direction = 1;
|
|
987
1053
|
get direction() { return this._direction; }
|
|
@@ -1001,7 +1067,10 @@ class ScrollEvent {
|
|
|
1001
1067
|
get isEnd() { return this._isEnd; }
|
|
1002
1068
|
_delta = 0;
|
|
1003
1069
|
get delta() { return this._delta; }
|
|
1004
|
-
|
|
1070
|
+
_scrollDelta = 0;
|
|
1071
|
+
get scrollDelta() { return this._scrollDelta; }
|
|
1072
|
+
constructor(params) {
|
|
1073
|
+
const { direction, isVertical, container, list, delta, scrollDelta } = params;
|
|
1005
1074
|
this._direction = direction;
|
|
1006
1075
|
this._isVertical = isVertical;
|
|
1007
1076
|
this._scrollSize = isVertical ? container.scrollTop : container.scrollLeft;
|
|
@@ -1010,6 +1079,7 @@ class ScrollEvent {
|
|
|
1010
1079
|
this._size = isVertical ? container.offsetHeight : container.offsetWidth;
|
|
1011
1080
|
this._isEnd = (this._scrollSize + this._size) === this._scrollWeight;
|
|
1012
1081
|
this._delta = delta;
|
|
1082
|
+
this._scrollDelta = scrollDelta;
|
|
1013
1083
|
this._isStart = this._scrollSize === 0;
|
|
1014
1084
|
}
|
|
1015
1085
|
}
|
|
@@ -1060,6 +1130,12 @@ class NgVirtualListComponent {
|
|
|
1060
1130
|
* Determines whether scroll positions will be snapped to the element. Default value is "false".
|
|
1061
1131
|
*/
|
|
1062
1132
|
snapToItem = input(DEFAULT_SNAP_TO_ITEM);
|
|
1133
|
+
/**
|
|
1134
|
+
* Enables buffer optimization.
|
|
1135
|
+
* 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.
|
|
1136
|
+
* Works only if the property dynamic = true
|
|
1137
|
+
*/
|
|
1138
|
+
enabledBufferOptimization = input(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
|
|
1063
1139
|
/**
|
|
1064
1140
|
* Rendering element template.
|
|
1065
1141
|
*/
|
|
@@ -1108,9 +1184,8 @@ class NgVirtualListComponent {
|
|
|
1108
1184
|
this.clearScrollToRepeatExecutionTimeout();
|
|
1109
1185
|
const container = this._container()?.nativeElement;
|
|
1110
1186
|
if (container) {
|
|
1111
|
-
const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft)
|
|
1187
|
+
const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
|
|
1112
1188
|
let actualScrollSize = scrollSize, isImmediateScroll = false;
|
|
1113
|
-
this._trackBox.deltaDirection = previouseScrollSize > scrollSize ? -1 : 1;
|
|
1114
1189
|
if (dynamicSize && delta !== 0) {
|
|
1115
1190
|
actualScrollSize = scrollSize + delta;
|
|
1116
1191
|
const params = {
|
|
@@ -1120,18 +1195,11 @@ class NgVirtualListComponent {
|
|
|
1120
1195
|
const container = this._container();
|
|
1121
1196
|
if (container) {
|
|
1122
1197
|
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
|
-
});
|
|
1198
|
+
this.scrollImmediately(container, params);
|
|
1127
1199
|
this._trackBox.clearDelta();
|
|
1128
1200
|
}
|
|
1129
1201
|
}
|
|
1130
1202
|
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
1203
|
}
|
|
1136
1204
|
};
|
|
1137
1205
|
scrollImmediately(container, params, cb) {
|
|
@@ -1168,7 +1236,6 @@ class NgVirtualListComponent {
|
|
|
1168
1236
|
this._trackBox.clearDeltaDirection();
|
|
1169
1237
|
const itemSize = this.itemSize(), snapToItem = this.snapToItem(), dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1170
1238
|
let actualScrollSize = scrollSize;
|
|
1171
|
-
const event = new ScrollEvent(this._trackBox.scrollDirection, container.nativeElement, this._list().nativeElement, delta, this._isVertical);
|
|
1172
1239
|
if (dynamicSize) {
|
|
1173
1240
|
actualScrollSize = scrollSize + delta;
|
|
1174
1241
|
if (snapToItem) {
|
|
@@ -1199,7 +1266,6 @@ class NgVirtualListComponent {
|
|
|
1199
1266
|
}
|
|
1200
1267
|
}
|
|
1201
1268
|
this._scrollSize.set(actualScrollSize);
|
|
1202
|
-
this.onScrollEnd.emit(event);
|
|
1203
1269
|
}
|
|
1204
1270
|
};
|
|
1205
1271
|
_elementRef = inject((ElementRef));
|
|
@@ -1221,6 +1287,10 @@ class NgVirtualListComponent {
|
|
|
1221
1287
|
this._initialized = signal(false);
|
|
1222
1288
|
this.$initialized = toObservable(this._initialized);
|
|
1223
1289
|
this._trackBox.displayComponents = this._displayComponents;
|
|
1290
|
+
const $enabledBufferOptimization = toObservable(this.enabledBufferOptimization);
|
|
1291
|
+
$enabledBufferOptimization.pipe(tap(v => {
|
|
1292
|
+
this._trackBox.enabledBufferOptimization = v;
|
|
1293
|
+
})).subscribe();
|
|
1224
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;
|
|
1225
1295
|
$isVertical.pipe(takeUntilDestroyed(), tap(v => {
|
|
1226
1296
|
this._isVertical = v;
|
|
@@ -1315,6 +1385,12 @@ class NgVirtualListComponent {
|
|
|
1315
1385
|
l.nativeElement.style[isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME] = `${totalSize}${PX}`;
|
|
1316
1386
|
}
|
|
1317
1387
|
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Returns the bounds of an element with a given id
|
|
1390
|
+
*/
|
|
1391
|
+
getItemBounds(id) {
|
|
1392
|
+
return this._trackBox.getItemBounds(id);
|
|
1393
|
+
}
|
|
1318
1394
|
/**
|
|
1319
1395
|
* The method scrolls the list to the element with the given id and returns the value of the scrolled area.
|
|
1320
1396
|
* Behavior accepts the values "auto", "instant" and "smooth".
|
|
@@ -1364,8 +1440,6 @@ class NgVirtualListComponent {
|
|
|
1364
1440
|
}
|
|
1365
1441
|
else {
|
|
1366
1442
|
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
1443
|
container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1370
1444
|
container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1371
1445
|
}
|
|
@@ -1386,9 +1460,37 @@ class NgVirtualListComponent {
|
|
|
1386
1460
|
const items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0];
|
|
1387
1461
|
this.scrollTo(latItem.id, behavior);
|
|
1388
1462
|
}
|
|
1463
|
+
_onContainerScrollHandler = (e) => {
|
|
1464
|
+
const containerEl = this._container();
|
|
1465
|
+
if (containerEl) {
|
|
1466
|
+
const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft), offsetSize = (this._isVertical ? containerEl.nativeElement.offsetHeight : containerEl.nativeElement.offsetWidth), listSize = (this._isVertical ? this._list()?.nativeElement.offsetHeight ?? 0 : this._list()?.nativeElement.offsetLeft ?? 0);
|
|
1467
|
+
this._trackBox.deltaDirection = this._scrollSize() >= scrollSize || (scrollSize + offsetSize) >= listSize ? -1 : 1;
|
|
1468
|
+
const event = new ScrollEvent({
|
|
1469
|
+
direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
|
|
1470
|
+
list: this._list().nativeElement, delta: this._trackBox.delta,
|
|
1471
|
+
scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
|
|
1472
|
+
});
|
|
1473
|
+
this.onScroll.emit(event);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
_onContainerScrollEndHandler = (e) => {
|
|
1477
|
+
this._trackBox.deltaDirection = -1;
|
|
1478
|
+
const containerEl = this._container();
|
|
1479
|
+
if (containerEl) {
|
|
1480
|
+
const event = new ScrollEvent({
|
|
1481
|
+
direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
|
|
1482
|
+
list: this._list().nativeElement, delta: this._trackBox.delta,
|
|
1483
|
+
scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
|
|
1484
|
+
});
|
|
1485
|
+
this.onScrollEnd.emit(event);
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1389
1488
|
ngAfterViewInit() {
|
|
1390
1489
|
const containerEl = this._container();
|
|
1391
1490
|
if (containerEl) {
|
|
1491
|
+
// for direction calculation
|
|
1492
|
+
containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1493
|
+
containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1392
1494
|
containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1393
1495
|
containerEl.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1394
1496
|
this._resizeObserver = new ResizeObserver(this._onResizeHandler);
|
|
@@ -1405,6 +1507,8 @@ class NgVirtualListComponent {
|
|
|
1405
1507
|
if (containerEl) {
|
|
1406
1508
|
containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1407
1509
|
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1510
|
+
containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1511
|
+
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1408
1512
|
if (this._resizeObserver) {
|
|
1409
1513
|
this._resizeObserver.unobserve(containerEl.nativeElement);
|
|
1410
1514
|
}
|
|
@@ -1417,7 +1521,7 @@ class NgVirtualListComponent {
|
|
|
1417
1521
|
}
|
|
1418
1522
|
}
|
|
1419
1523
|
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 });
|
|
1524
|
+
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 }, 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
1525
|
}
|
|
1422
1526
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
1423
1527
|
type: Component,
|