ng-virtual-list 20.0.23 → 20.0.24
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 +7 -8
- package/fesm2022/ng-virtual-list.mjs +129 -208
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/index.d.ts +4 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -449,10 +449,9 @@ Inputs
|
|
|
449
449
|
| itemRenderer | TemplateRef | Rendering element template. |
|
|
450
450
|
| stickyMap | [IVirtualListStickyMap?](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/models/sticky-map.model.ts) | Dictionary zIndex by id of the list element. If the value is not set or equal to 0, then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element. |
|
|
451
451
|
| snap | boolean? = false | Determines whether elements will snap. Default value is "false". |
|
|
452
|
-
| snapToItem | boolean? = false | Determines whether scroll positions will be snapped to the element. Default value is "false". |
|
|
453
452
|
| direction | [Direction? = 'vertical'](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/enums/direction.ts) | Determines the direction in which elements are placed. Default value is "vertical". |
|
|
454
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. |
|
|
455
|
-
| enabledBufferOptimization | boolean? = true | Enables buffer optimization. Can only be used if items in the collection are not added or updated. |
|
|
454
|
+
| enabledBufferOptimization | boolean? = true | Experimental! Enables buffer optimization. Can only be used if items in the collection are not added or updated. |
|
|
456
455
|
| trackBy | string? = 'id' | The name of the property by which tracking is performed. |
|
|
457
456
|
|
|
458
457
|
<br/>
|
|
@@ -478,12 +477,12 @@ Methods
|
|
|
478
477
|
|
|
479
478
|
| Angular version | ng-virtual-list version | git | npm |
|
|
480
479
|
|--|--|--|--|
|
|
481
|
-
| 19.x | 19.1.
|
|
482
|
-
| 18.x | 18.0.
|
|
483
|
-
| 17.x | 17.0.
|
|
484
|
-
| 16.x | 16.0.
|
|
485
|
-
| 15.x | 15.0.
|
|
486
|
-
| 14.x | 14.0.
|
|
480
|
+
| 19.x | 19.1.28 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.1.28](https://www.npmjs.com/package/ng-virtual-list/v/19.1.28) |
|
|
481
|
+
| 18.x | 18.0.16 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.0.16](https://www.npmjs.com/package/ng-virtual-list/v/18.0.16) |
|
|
482
|
+
| 17.x | 17.0.13 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.0.13](https://www.npmjs.com/package/ng-virtual-list/v/17.0.13) |
|
|
483
|
+
| 16.x | 16.0.16 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.0.16](https://www.npmjs.com/package/ng-virtual-list/v/16.0.16) |
|
|
484
|
+
| 15.x | 15.0.14 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.0.14](https://www.npmjs.com/package/ng-virtual-list/v/15.0.14) |
|
|
485
|
+
| 14.x | 14.0.14 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.0.14](https://www.npmjs.com/package/ng-virtual-list/v/14.0.14) |
|
|
487
486
|
|
|
488
487
|
<br/>
|
|
489
488
|
|
|
@@ -27,8 +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 =
|
|
31
|
-
const DEFAULT_SNAP_TO_ITEM = false;
|
|
30
|
+
const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = false;
|
|
32
31
|
const DEFAULT_DYNAMIC_SIZE = false;
|
|
33
32
|
const TRACK_BY_PROPERTY_NAME = 'id';
|
|
34
33
|
const DEFAULT_DIRECTION = Directions.VERTICAL;
|
|
@@ -115,7 +114,7 @@ class NgVirtualListItemComponent {
|
|
|
115
114
|
const el = this._elementRef.nativeElement, { width, height, left, top } = el.getBoundingClientRect();
|
|
116
115
|
return { width, height, x: left, y: top };
|
|
117
116
|
}
|
|
118
|
-
|
|
117
|
+
show() {
|
|
119
118
|
const styles = this._elementRef.nativeElement.style;
|
|
120
119
|
if (styles.visibility === VISIBILITY_VISIBLE) {
|
|
121
120
|
return;
|
|
@@ -236,29 +235,24 @@ class Tracker {
|
|
|
236
235
|
/**
|
|
237
236
|
* tracking by propName
|
|
238
237
|
*/
|
|
239
|
-
track(items, components,
|
|
238
|
+
track(items, components, direction) {
|
|
240
239
|
if (!items) {
|
|
241
240
|
return;
|
|
242
241
|
}
|
|
243
|
-
const idPropName = this._trackingPropertyName, untrackedItems = [...components];
|
|
244
|
-
for (let i = 0, l = items.length; i < l; i++) {
|
|
242
|
+
const idPropName = this._trackingPropertyName, untrackedItems = [...components], isDown = direction === 0 || direction === 1;
|
|
243
|
+
for (let i = isDown ? 0 : items.length - 1, l = isDown ? items.length : 0; isDown ? i < l : i >= l; isDown ? i++ : i--) {
|
|
245
244
|
const item = items[i], itemTrackingProperty = item[idPropName];
|
|
246
245
|
if (this._trackMap) {
|
|
247
|
-
const diId = this._trackMap[itemTrackingProperty];
|
|
248
246
|
if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
247
|
+
const diId = this._trackMap[itemTrackingProperty], compIndex = this._displayObjectIndexMapById[diId], comp = components[compIndex];
|
|
248
|
+
const compId = comp?.instance?.id;
|
|
249
|
+
if (comp !== undefined && compId == diId) {
|
|
252
250
|
const indexByUntrackedItems = untrackedItems.findIndex(v => {
|
|
253
|
-
return v.instance.id
|
|
251
|
+
return v.instance.id == compId;
|
|
254
252
|
});
|
|
255
253
|
if (indexByUntrackedItems > -1) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (afterComponentSetup !== undefined) {
|
|
259
|
-
afterComponentSetup(el.instance, item);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
254
|
+
comp.instance.item = item;
|
|
255
|
+
comp.instance.show();
|
|
262
256
|
untrackedItems.splice(indexByUntrackedItems, 1);
|
|
263
257
|
continue;
|
|
264
258
|
}
|
|
@@ -269,20 +263,18 @@ class Tracker {
|
|
|
269
263
|
if (untrackedItems.length > 0) {
|
|
270
264
|
const el = untrackedItems.shift(), item = items[i];
|
|
271
265
|
if (el) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
this._trackMap[itemTrackingProperty] = el.instance.id;
|
|
276
|
-
}
|
|
277
|
-
if (afterComponentSetup !== undefined) {
|
|
278
|
-
afterComponentSetup(el.instance, item);
|
|
279
|
-
}
|
|
266
|
+
el.instance.item = item;
|
|
267
|
+
if (this._trackMap) {
|
|
268
|
+
this._trackMap[itemTrackingProperty] = el.instance.id;
|
|
280
269
|
}
|
|
281
270
|
}
|
|
282
271
|
}
|
|
283
272
|
}
|
|
284
273
|
if (untrackedItems.length) {
|
|
285
|
-
|
|
274
|
+
for (let i = 0, l = untrackedItems.length; i < l; i++) {
|
|
275
|
+
const comp = untrackedItems[i];
|
|
276
|
+
comp.instance.hide();
|
|
277
|
+
}
|
|
286
278
|
}
|
|
287
279
|
}
|
|
288
280
|
untrackComponentByIdProperty(component) {
|
|
@@ -404,7 +396,7 @@ class EventEmitter {
|
|
|
404
396
|
}
|
|
405
397
|
}
|
|
406
398
|
|
|
407
|
-
const MAX_SCROLL_DIRECTION_POOL =
|
|
399
|
+
const MAX_SCROLL_DIRECTION_POOL = 50, CLEAR_SCROLL_DIRECTION_TO = 10;
|
|
408
400
|
/**
|
|
409
401
|
* Cache map.
|
|
410
402
|
* Emits a change event on each mutation.
|
|
@@ -452,15 +444,23 @@ class CacheMap extends EventEmitter {
|
|
|
452
444
|
this._scrollDirectionCache.shift();
|
|
453
445
|
}
|
|
454
446
|
this._scrollDirectionCache.push(v);
|
|
455
|
-
const dict = { [-1]: 0, [0]: 0, [1]: 0 };
|
|
456
|
-
for (let i = 0, l = this._scrollDirectionCache.length; i < l; i++) {
|
|
457
|
-
const dir = this._scrollDirectionCache[i];
|
|
447
|
+
const dict = { ['-1']: 0, ['0']: 0, ['1']: 0 };
|
|
448
|
+
for (let i = 0, l = this._scrollDirectionCache.length, li = l - 1; i < l; i++) {
|
|
449
|
+
const dir = String(this._scrollDirectionCache[i]);
|
|
458
450
|
dict[dir] += 1;
|
|
451
|
+
if (i === li) {
|
|
452
|
+
for (let d in dict) {
|
|
453
|
+
if (d === String(v)) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
dict[d] -= 1;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
459
|
}
|
|
460
|
-
if (dict[-1] > dict[0] && dict[-1] > dict[1]) {
|
|
460
|
+
if (dict['-1'] > dict['0'] && dict['-1'] > dict['1']) {
|
|
461
461
|
return -1;
|
|
462
462
|
}
|
|
463
|
-
else if (dict[1] > dict[-1] && dict[1] > dict[0]) {
|
|
463
|
+
else if (dict['1'] > dict['-1'] && dict['1'] > dict['0']) {
|
|
464
464
|
return 1;
|
|
465
465
|
}
|
|
466
466
|
return 0;
|
|
@@ -719,12 +719,34 @@ class TrackBox extends CacheMap {
|
|
|
719
719
|
*/
|
|
720
720
|
recalculateMetrics(options) {
|
|
721
721
|
const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap, enabledBufferOptimization } = options;
|
|
722
|
-
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,
|
|
722
|
+
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, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
|
|
723
723
|
|| (typeof fromItemId === 'string' && fromItemId > '-1');
|
|
724
|
+
let leftItemsOffset = 0, rightItemsOffset = 0;
|
|
725
|
+
if (enabledBufferOptimization) {
|
|
726
|
+
switch (this.scrollDirection) {
|
|
727
|
+
case 1: {
|
|
728
|
+
leftItemsOffset = 0;
|
|
729
|
+
rightItemsOffset = itemsOffset;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
case -1: {
|
|
733
|
+
leftItemsOffset = itemsOffset;
|
|
734
|
+
rightItemsOffset = 0;
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
case 0:
|
|
738
|
+
default: {
|
|
739
|
+
leftItemsOffset = rightItemsOffset = itemsOffset;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
leftItemsOffset = rightItemsOffset = itemsOffset;
|
|
745
|
+
}
|
|
724
746
|
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;
|
|
725
747
|
// If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
|
|
726
748
|
if (dynamicSize) {
|
|
727
|
-
let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
|
|
749
|
+
let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0, stickyComponentIndex = -1;
|
|
728
750
|
for (let i = 0, l = collection.length; i < l; i++) {
|
|
729
751
|
const ii = i + 1, collectionItem = collection[i], id = collectionItem.id;
|
|
730
752
|
let componentSize = 0, componentSizeDelta = 0, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
|
|
@@ -755,18 +777,26 @@ class TrackBox extends CacheMap {
|
|
|
755
777
|
if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
|
|
756
778
|
stickyComponentSize = componentSize;
|
|
757
779
|
stickyCollectionItem = collectionItem;
|
|
780
|
+
stickyComponentIndex = i;
|
|
758
781
|
}
|
|
759
782
|
if (id === fromItemId) {
|
|
760
783
|
targetDisplayItemIndex = i;
|
|
761
|
-
if (stickyCollectionItem && stickyMap
|
|
784
|
+
if (stickyCollectionItem && stickyMap) {
|
|
762
785
|
const { num } = this.getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical);
|
|
763
786
|
if (num > 0) {
|
|
764
787
|
isTargetInOverscroll = true;
|
|
765
788
|
y -= size - componentSize;
|
|
766
789
|
}
|
|
767
790
|
else {
|
|
768
|
-
y
|
|
769
|
-
|
|
791
|
+
if (stickyMap && !stickyMap[collectionItem.id] && y >= scrollSize && y < scrollSize + stickyComponentSize) {
|
|
792
|
+
const snappedY = scrollSize - stickyComponentSize;
|
|
793
|
+
leftHiddenItemsWeight -= (snappedY - y);
|
|
794
|
+
y = snappedY;
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
y -= stickyComponentSize;
|
|
798
|
+
leftHiddenItemsWeight -= stickyComponentSize;
|
|
799
|
+
}
|
|
770
800
|
}
|
|
771
801
|
}
|
|
772
802
|
itemById = collectionItem;
|
|
@@ -996,7 +1026,7 @@ class TrackBox extends CacheMap {
|
|
|
996
1026
|
if (!this._items || !this._displayComponents) {
|
|
997
1027
|
return;
|
|
998
1028
|
}
|
|
999
|
-
this._tracker.track(this._items, this._displayComponents);
|
|
1029
|
+
this._tracker.track(this._items, this._displayComponents, this.scrollDirection);
|
|
1000
1030
|
}
|
|
1001
1031
|
setDisplayObjectIndexMapById(v) {
|
|
1002
1032
|
this._tracker.displayObjectIndexMapById = v;
|
|
@@ -1119,10 +1149,7 @@ class NgVirtualListComponent {
|
|
|
1119
1149
|
*/
|
|
1120
1150
|
snap = input(DEFAULT_SNAP);
|
|
1121
1151
|
/**
|
|
1122
|
-
*
|
|
1123
|
-
*/
|
|
1124
|
-
snapToItem = input(DEFAULT_SNAP_TO_ITEM);
|
|
1125
|
-
/**
|
|
1152
|
+
* Experimental!
|
|
1126
1153
|
* Enables buffer optimization.
|
|
1127
1154
|
* 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.
|
|
1128
1155
|
* Works only if the property dynamic = true
|
|
@@ -1168,18 +1195,11 @@ class NgVirtualListComponent {
|
|
|
1168
1195
|
_displayComponents = [];
|
|
1169
1196
|
_bounds = signal(null);
|
|
1170
1197
|
_scrollSize = signal(0);
|
|
1171
|
-
_isScrollingDebounces = debounce((v) => {
|
|
1172
|
-
this._isScrolling = v;
|
|
1173
|
-
}, 250);
|
|
1174
|
-
_isScrolling = false;
|
|
1175
|
-
get isScrolling() { return this._isScrolling; }
|
|
1176
1198
|
_resizeObserver = null;
|
|
1177
1199
|
_onResizeHandler = () => {
|
|
1178
1200
|
this._bounds.set(this._container()?.nativeElement?.getBoundingClientRect() ?? null);
|
|
1179
1201
|
};
|
|
1180
|
-
_scrolls = new Map();
|
|
1181
1202
|
_onScrollHandler = (e) => {
|
|
1182
|
-
this._isScrollingDebounces.dispose();
|
|
1183
1203
|
this.clearScrollToRepeatExecutionTimeout();
|
|
1184
1204
|
const container = this._container()?.nativeElement;
|
|
1185
1205
|
if (container) {
|
|
@@ -1187,85 +1207,8 @@ class NgVirtualListComponent {
|
|
|
1187
1207
|
let actualScrollSize = scrollSize, isScrollIUmmediate = false;
|
|
1188
1208
|
if (dynamicSize && delta !== 0) {
|
|
1189
1209
|
actualScrollSize = scrollSize + delta;
|
|
1190
|
-
const params = {
|
|
1191
|
-
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1192
|
-
behavior: BEHAVIOR_INSTANT
|
|
1193
|
-
};
|
|
1194
|
-
const container = this._container();
|
|
1195
|
-
if (container) {
|
|
1196
|
-
isScrollIUmmediate = true;
|
|
1197
|
-
this.scrollImmediately(container, params);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
if (!isScrollIUmmediate) {
|
|
1201
|
-
this._scrollSize.set(actualScrollSize);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
};
|
|
1205
|
-
scrollImmediately(container, params, cb) {
|
|
1206
|
-
if (this._scrolls.size > 0) {
|
|
1207
|
-
container.nativeElement.scrollTo(params);
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
this._trackBox.clearDelta();
|
|
1211
|
-
container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1212
|
-
container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1213
|
-
const handler = () => {
|
|
1214
|
-
const container = this._container();
|
|
1215
|
-
if (container) {
|
|
1216
|
-
container.nativeElement.removeEventListener(SCROLL_END, handler);
|
|
1217
|
-
this._scrolls.delete(handler);
|
|
1218
|
-
container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1219
|
-
container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1220
|
-
if (cb !== undefined) {
|
|
1221
|
-
cb();
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
};
|
|
1225
|
-
this._scrolls.set(handler, true);
|
|
1226
|
-
container.nativeElement.addEventListener(SCROLL_END, handler);
|
|
1227
|
-
container.nativeElement.scrollTo(params);
|
|
1228
|
-
}
|
|
1229
|
-
_onScrollEndHandler = (e) => {
|
|
1230
|
-
const container = this._container();
|
|
1231
|
-
if (container) {
|
|
1232
|
-
const itemSize = this.itemSize(), snapToItem = this.snapToItem(), dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1233
|
-
let actualScrollSize = scrollSize;
|
|
1234
|
-
this._trackBox.clearDeltaDirection();
|
|
1235
|
-
if (dynamicSize) {
|
|
1236
|
-
actualScrollSize = scrollSize + delta;
|
|
1237
|
-
if (snapToItem) {
|
|
1238
|
-
const items = this.items(), isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
|
|
1239
|
-
if (targetItem) {
|
|
1240
|
-
this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
|
|
1241
|
-
}
|
|
1242
|
-
this._trackBox.clearDelta();
|
|
1243
|
-
}
|
|
1244
|
-
else if (scrollSize !== actualScrollSize) {
|
|
1245
|
-
const params = {
|
|
1246
|
-
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1247
|
-
behavior: BEHAVIOR_INSTANT
|
|
1248
|
-
};
|
|
1249
|
-
const container = this._container();
|
|
1250
|
-
if (container) {
|
|
1251
|
-
this.scrollImmediately(container, params);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
else {
|
|
1256
|
-
const scrollItems = Math.round(scrollSize / itemSize);
|
|
1257
|
-
actualScrollSize = snapToItem ? scrollItems * itemSize : scrollSize;
|
|
1258
|
-
if (scrollSize !== actualScrollSize) {
|
|
1259
|
-
const params = {
|
|
1260
|
-
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1261
|
-
behavior: BEHAVIOR_INSTANT
|
|
1262
|
-
};
|
|
1263
|
-
const container = this._container();
|
|
1264
|
-
if (container) {
|
|
1265
|
-
this.scrollImmediately(container, params);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
1210
|
}
|
|
1211
|
+
this._scrollSize.set(actualScrollSize);
|
|
1269
1212
|
}
|
|
1270
1213
|
};
|
|
1271
1214
|
_elementRef = inject((ElementRef));
|
|
@@ -1295,7 +1238,7 @@ class NgVirtualListComponent {
|
|
|
1295
1238
|
$trackBy.pipe(takeUntilDestroyed(), tap(v => {
|
|
1296
1239
|
this._trackBox.trackingPropertyName = v;
|
|
1297
1240
|
})).subscribe();
|
|
1298
|
-
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), $
|
|
1241
|
+
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), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $cacheVersion = this.$cacheVersion;
|
|
1299
1242
|
$isVertical.pipe(takeUntilDestroyed(), tap(v => {
|
|
1300
1243
|
this._isVertical = v;
|
|
1301
1244
|
const el = this._elementRef.nativeElement;
|
|
@@ -1305,36 +1248,44 @@ class NgVirtualListComponent {
|
|
|
1305
1248
|
this.listenCacheChangesIfNeed(dynamicSize);
|
|
1306
1249
|
})).subscribe();
|
|
1307
1250
|
combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
|
|
1308
|
-
$itemsOffset, $snap, $
|
|
1309
|
-
]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap,
|
|
1251
|
+
$itemsOffset, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
|
|
1252
|
+
]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
|
|
1310
1253
|
const { width, height } = bounds;
|
|
1311
|
-
let actualScrollSize =
|
|
1254
|
+
let actualScrollSize = (this._isVertical ? this._container()?.nativeElement.scrollTop ?? 0 : this._container()?.nativeElement.scrollLeft) ?? 0;
|
|
1312
1255
|
const opts = {
|
|
1313
1256
|
bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
|
|
1314
1257
|
itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
|
|
1315
1258
|
};
|
|
1316
|
-
const { displayItems, totalSize
|
|
1259
|
+
const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
|
|
1317
1260
|
...opts, scrollSize: actualScrollSize,
|
|
1318
1261
|
});
|
|
1319
1262
|
this.resetBoundsSize(isVertical, totalSize);
|
|
1320
1263
|
this.createDisplayComponentsIfNeed(displayItems);
|
|
1321
1264
|
this.tracking();
|
|
1322
1265
|
const container = this._container();
|
|
1323
|
-
if (
|
|
1324
|
-
actualScrollSize =
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (
|
|
1328
|
-
|
|
1266
|
+
if (container) {
|
|
1267
|
+
actualScrollSize = actualScrollSize + this._trackBox.delta;
|
|
1268
|
+
this._trackBox.clearDelta();
|
|
1269
|
+
if (dynamicSize) {
|
|
1270
|
+
if (scrollSize !== actualScrollSize) {
|
|
1271
|
+
const params = {
|
|
1272
|
+
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1273
|
+
behavior: BEHAVIOR_INSTANT
|
|
1274
|
+
};
|
|
1275
|
+
container.nativeElement.scrollTo(params);
|
|
1329
1276
|
}
|
|
1330
1277
|
}
|
|
1331
|
-
else
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1278
|
+
else {
|
|
1279
|
+
if (scrollSize !== actualScrollSize) {
|
|
1280
|
+
const params = {
|
|
1281
|
+
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1282
|
+
behavior: BEHAVIOR_INSTANT
|
|
1283
|
+
};
|
|
1284
|
+
const container = this._container();
|
|
1285
|
+
if (container) {
|
|
1286
|
+
container.nativeElement.scrollTo(params);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1338
1289
|
}
|
|
1339
1290
|
}
|
|
1340
1291
|
return of(displayItems);
|
|
@@ -1372,25 +1323,14 @@ class NgVirtualListComponent {
|
|
|
1372
1323
|
}
|
|
1373
1324
|
this._trackBox.items = displayItems;
|
|
1374
1325
|
const _listContainerRef = this._listContainerRef;
|
|
1375
|
-
|
|
1326
|
+
const maxLength = displayItems.length, components = this._displayComponents;
|
|
1327
|
+
while (components.length < maxLength) {
|
|
1376
1328
|
if (_listContainerRef) {
|
|
1377
1329
|
const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
|
|
1378
|
-
|
|
1330
|
+
components.push(comp);
|
|
1379
1331
|
this._componentsResizeObserver.observe(comp.instance.element);
|
|
1380
1332
|
}
|
|
1381
1333
|
}
|
|
1382
|
-
const maxLength = displayItems.length;
|
|
1383
|
-
while (this._displayComponents.length > maxLength) {
|
|
1384
|
-
const comp = this._displayComponents.pop();
|
|
1385
|
-
if (comp) {
|
|
1386
|
-
this._componentsResizeObserver.unobserve(comp.instance.element);
|
|
1387
|
-
comp?.destroy();
|
|
1388
|
-
const id = comp?.instance.item?.id;
|
|
1389
|
-
if (id !== undefined) {
|
|
1390
|
-
this._trackBox.untrackComponentByIdProperty(comp?.instance);
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
1334
|
this.resetRenderers();
|
|
1395
1335
|
}
|
|
1396
1336
|
resetRenderers(itemRenderer) {
|
|
@@ -1431,53 +1371,51 @@ class NgVirtualListComponent {
|
|
|
1431
1371
|
clearScrollToRepeatExecutionTimeout() {
|
|
1432
1372
|
clearTimeout(this._scrollToRepeatExecutionTimeout);
|
|
1433
1373
|
}
|
|
1434
|
-
scrollToExecutor(id, behavior, iteration = 0) {
|
|
1435
|
-
this._isScrolling = true;
|
|
1436
|
-
this.clearScrollToRepeatExecutionTimeout();
|
|
1374
|
+
scrollToExecutor(id, behavior, iteration = 0, isLastIteration = false) {
|
|
1437
1375
|
const items = this.items();
|
|
1438
1376
|
if (!items || !items.length) {
|
|
1439
1377
|
return;
|
|
1440
1378
|
}
|
|
1441
1379
|
const dynamicSize = this.dynamicSize(), container = this._container(), itemSize = this.itemSize();
|
|
1442
1380
|
if (container) {
|
|
1381
|
+
this.clearScrollToRepeatExecutionTimeout();
|
|
1443
1382
|
if (dynamicSize) {
|
|
1444
1383
|
if (container) {
|
|
1445
1384
|
container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1446
|
-
container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1447
1385
|
}
|
|
1448
|
-
const { width, height } = this._bounds() || { width: 0, height: 0 }, stickyMap = this.stickyMap(), items = this.items(), isVertical = this._isVertical, opts = {
|
|
1386
|
+
const { width, height } = this._bounds() || { width: 0, height: 0 }, stickyMap = this.stickyMap(), items = this.items(), isVertical = this._isVertical, delta = this._trackBox.delta, opts = {
|
|
1449
1387
|
bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
|
|
1450
|
-
itemsOffset: this.itemsOffset(), scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
|
|
1388
|
+
itemsOffset: this.itemsOffset(), scrollSize: (isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft) + delta,
|
|
1451
1389
|
snap: this.snap(), fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization(),
|
|
1452
1390
|
}, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
|
|
1453
|
-
this.
|
|
1391
|
+
this._trackBox.clearDelta();
|
|
1454
1392
|
if (container) {
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
container.nativeElement.addEventListener(SCROLL_END, handler);
|
|
1393
|
+
const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
|
|
1394
|
+
...opts, scrollSize, fromItemId: isLastIteration ? undefined : id,
|
|
1395
|
+
}), delta = this._trackBox.delta;
|
|
1396
|
+
this._trackBox.clearDelta();
|
|
1397
|
+
let actualScrollSize = scrollSize + delta;
|
|
1398
|
+
this.resetBoundsSize(isVertical, totalSize);
|
|
1399
|
+
this.createDisplayComponentsIfNeed(displayItems);
|
|
1400
|
+
this.tracking();
|
|
1401
|
+
const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize: actualScrollSize, fromItemId: id });
|
|
1402
|
+
const notChanged = actualScrollSize === _scrollSize;
|
|
1403
|
+
if (notChanged) {
|
|
1404
|
+
iteration += 1;
|
|
1405
|
+
}
|
|
1406
|
+
if (iteration < MAX_SCROLL_TO_ITERATIONS) {
|
|
1407
|
+
this.clearScrollToRepeatExecutionTimeout();
|
|
1408
|
+
this._scrollToRepeatExecutionTimeout = setTimeout(() => {
|
|
1409
|
+
this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1, notChanged);
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
this._scrollSize.set(actualScrollSize);
|
|
1414
|
+
container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1415
|
+
}
|
|
1479
1416
|
}
|
|
1480
1417
|
container.nativeElement.scrollTo(params);
|
|
1418
|
+
this._scrollSize.set(scrollSize);
|
|
1481
1419
|
}
|
|
1482
1420
|
else {
|
|
1483
1421
|
const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize();
|
|
@@ -1491,7 +1429,6 @@ class NgVirtualListComponent {
|
|
|
1491
1429
|
this.scrollTo(latItem.id, behavior);
|
|
1492
1430
|
}
|
|
1493
1431
|
_onContainerScrollHandler = (e) => {
|
|
1494
|
-
this._isScrolling = true;
|
|
1495
1432
|
const containerEl = this._container();
|
|
1496
1433
|
if (containerEl) {
|
|
1497
1434
|
const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
|
|
@@ -1505,7 +1442,6 @@ class NgVirtualListComponent {
|
|
|
1505
1442
|
}
|
|
1506
1443
|
};
|
|
1507
1444
|
_onContainerScrollEndHandler = (e) => {
|
|
1508
|
-
this._isScrollingDebounces.execute(false);
|
|
1509
1445
|
const containerEl = this._container();
|
|
1510
1446
|
if (containerEl) {
|
|
1511
1447
|
const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
|
|
@@ -1525,34 +1461,19 @@ class NgVirtualListComponent {
|
|
|
1525
1461
|
containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1526
1462
|
containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1527
1463
|
containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1528
|
-
containerEl.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1529
1464
|
this._resizeObserver = new ResizeObserver(this._onResizeHandler);
|
|
1530
1465
|
this._resizeObserver.observe(containerEl.nativeElement);
|
|
1531
1466
|
this._onResizeHandler();
|
|
1532
1467
|
}
|
|
1533
1468
|
}
|
|
1534
|
-
clearScrollImmediately() {
|
|
1535
|
-
const container = this._container();
|
|
1536
|
-
if (container) {
|
|
1537
|
-
this._scrolls.forEach((_, handler) => {
|
|
1538
|
-
container.nativeElement.removeEventListener(SCROLL_END, handler);
|
|
1539
|
-
});
|
|
1540
|
-
}
|
|
1541
|
-
this._scrolls.clear();
|
|
1542
|
-
}
|
|
1543
1469
|
ngOnDestroy() {
|
|
1544
1470
|
this.clearScrollToRepeatExecutionTimeout();
|
|
1545
1471
|
if (this._trackBox) {
|
|
1546
1472
|
this._trackBox.dispose();
|
|
1547
1473
|
}
|
|
1548
|
-
if (this._isScrollingDebounces) {
|
|
1549
|
-
this._isScrollingDebounces.dispose();
|
|
1550
|
-
}
|
|
1551
|
-
this.clearScrollImmediately();
|
|
1552
1474
|
const containerEl = this._container();
|
|
1553
1475
|
if (containerEl) {
|
|
1554
1476
|
containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1555
|
-
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1556
1477
|
containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1557
1478
|
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1558
1479
|
if (this._componentsResizeObserver) {
|
|
@@ -1570,7 +1491,7 @@ class NgVirtualListComponent {
|
|
|
1570
1491
|
}
|
|
1571
1492
|
}
|
|
1572
1493
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1573
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", 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 },
|
|
1494
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", 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 }, 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 }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", 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 });
|
|
1574
1495
|
}
|
|
1575
1496
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
1576
1497
|
type: Component,
|