ng-virtual-list 20.0.22 → 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 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.25 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.1.25](https://www.npmjs.com/package/ng-virtual-list/v/19.1.25) |
482
- | 18.x | 18.0.14 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.0.14](https://www.npmjs.com/package/ng-virtual-list/v/18.0.14) |
483
- | 17.x | 17.0.11 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.0.11](https://www.npmjs.com/package/ng-virtual-list/v/17.0.11) |
484
- | 16.x | 16.0.14 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.0.14](https://www.npmjs.com/package/ng-virtual-list/v/16.0.14) |
485
- | 15.x | 15.0.12 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.0.12](https://www.npmjs.com/package/ng-virtual-list/v/15.0.12) |
486
- | 14.x | 14.0.12 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.0.12](https://www.npmjs.com/package/ng-virtual-list/v/14.0.12) |
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 = true;
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
- showIfNeed() {
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, afterComponentSetup) {
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 lastIndex = this._displayObjectIndexMapById[diId], el = components[lastIndex];
250
- const elId = el?.instance?.id;
251
- if (el && elId === diId) {
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 === elId;
251
+ return v.instance.id == compId;
254
252
  });
255
253
  if (indexByUntrackedItems > -1) {
256
- if (el.instance.item !== item) {
257
- el.instance.item = item;
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
- if (el.instance.item !== item) {
273
- el.instance.item = item;
274
- if (this._trackMap) {
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
- throw Error('Tracking by id caused an error.');
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 = 8, CLEAR_SCROLL_DIRECTION_TO = 0;
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, leftItemsOffset = enabledBufferOptimization ? this.deltaDirection === 1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, rightItemsOffset = enabledBufferOptimization ? this.deltaDirection === -1 ? DEFAULT_ITEMS_OFFSET : itemsOffset : itemsOffset, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
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 && stickyMap[stickyCollectionItem.id] > 0) {
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 -= stickyComponentSize;
769
- leftHiddenItemsWeight -= stickyComponentSize;
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
- * Determines whether scroll positions will be snapped to the element. Default value is "false".
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,100 +1195,18 @@ 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
1202
  _onScrollHandler = (e) => {
1181
- this._isScrollingDebounces.dispose();
1182
1203
  this.clearScrollToRepeatExecutionTimeout();
1183
1204
  const container = this._container()?.nativeElement;
1184
1205
  if (container) {
1185
1206
  const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
1186
- let actualScrollSize = scrollSize, isImmediateScroll = false;
1207
+ let actualScrollSize = scrollSize, isScrollIUmmediate = false;
1187
1208
  if (dynamicSize && delta !== 0) {
1188
1209
  actualScrollSize = scrollSize + delta;
1189
- const params = {
1190
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1191
- behavior: BEHAVIOR_INSTANT
1192
- };
1193
- const container = this._container();
1194
- if (container) {
1195
- isImmediateScroll = true;
1196
- this.scrollImmediately(container, params);
1197
- this._trackBox.clearDelta();
1198
- }
1199
- }
1200
- this._scrollSize.set(actualScrollSize);
1201
- }
1202
- };
1203
- scrollImmediately(container, params, cb) {
1204
- this.clearScrollImmediately();
1205
- container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1206
- const handler = () => {
1207
- if (container) {
1208
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1209
- container.nativeElement.scroll(params);
1210
- if (cb !== undefined) {
1211
- cb();
1212
- }
1213
- container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1214
- }
1215
- };
1216
- container.nativeElement.addEventListener(SCROLL_END, handler);
1217
- container.nativeElement.scroll(params);
1218
- this._scrollImmediatelyHandler = handler;
1219
- }
1220
- _scrollImmediatelyHandler = undefined;
1221
- clearScrollImmediately() {
1222
- if (this._scrollImmediatelyHandler === undefined) {
1223
- return;
1224
- }
1225
- const container = this._container();
1226
- if (container) {
1227
- container.nativeElement.removeEventListener(SCROLL_END, this._scrollImmediatelyHandler);
1228
- }
1229
- }
1230
- _onScrollEndHandler = (e) => {
1231
- const container = this._container();
1232
- if (container) {
1233
- this._trackBox.clearDelta();
1234
- this._trackBox.clearDeltaDirection();
1235
- const itemSize = this.itemSize(), snapToItem = this.snapToItem(), dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
1236
- let actualScrollSize = scrollSize;
1237
- if (dynamicSize) {
1238
- actualScrollSize = scrollSize + delta;
1239
- if (snapToItem) {
1240
- const items = this.items(), isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1241
- if (targetItem) {
1242
- this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1243
- }
1244
- }
1245
- else if (scrollSize !== actualScrollSize) {
1246
- const params = {
1247
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1248
- behavior: BEHAVIOR_INSTANT
1249
- };
1250
- this._scrollSize.set(actualScrollSize);
1251
- container.nativeElement.scroll(params);
1252
- return;
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
- container.nativeElement.scroll(params);
1264
- }
1265
1210
  }
1266
1211
  this._scrollSize.set(actualScrollSize);
1267
1212
  }
@@ -1293,7 +1238,7 @@ class NgVirtualListComponent {
1293
1238
  $trackBy.pipe(takeUntilDestroyed(), tap(v => {
1294
1239
  this._trackBox.trackingPropertyName = v;
1295
1240
  })).subscribe();
1296
- const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = toObservable(this.itemsOffset).pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = toObservable(this.stickyMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $snapToItem = toObservable(this.snapToItem), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $cacheVersion = this.$cacheVersion;
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;
1297
1242
  $isVertical.pipe(takeUntilDestroyed(), tap(v => {
1298
1243
  this._isVertical = v;
1299
1244
  const el = this._elementRef.nativeElement;
@@ -1303,36 +1248,44 @@ class NgVirtualListComponent {
1303
1248
  this.listenCacheChangesIfNeed(dynamicSize);
1304
1249
  })).subscribe();
1305
1250
  combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1306
- $itemsOffset, $snap, $snapToItem, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1307
- ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, snapToItem, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
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,]) => {
1308
1253
  const { width, height } = bounds;
1309
- let actualScrollSize = scrollSize;
1254
+ let actualScrollSize = (this._isVertical ? this._container()?.nativeElement.scrollTop ?? 0 : this._container()?.nativeElement.scrollLeft) ?? 0;
1310
1255
  const opts = {
1311
1256
  bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
1312
1257
  itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
1313
1258
  };
1314
- const { displayItems, totalSize, delta } = this._trackBox.updateCollection(items, stickyMap, {
1259
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1315
1260
  ...opts, scrollSize: actualScrollSize,
1316
1261
  });
1317
1262
  this.resetBoundsSize(isVertical, totalSize);
1318
1263
  this.createDisplayComponentsIfNeed(displayItems);
1319
1264
  this.tracking();
1320
1265
  const container = this._container();
1321
- if (!this.isScrolling && dynamicSize && container) {
1322
- actualScrollSize = scrollSize + delta;
1323
- if (snapToItem) {
1324
- const items = this.items(), isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1325
- if (targetItem) {
1326
- this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
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);
1327
1276
  }
1328
1277
  }
1329
- else if (scrollSize !== actualScrollSize) {
1330
- const params = {
1331
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1332
- behavior: BEHAVIOR_INSTANT
1333
- };
1334
- this.scrollImmediately(container, params);
1335
- this._trackBox.clearDelta();
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
+ }
1336
1289
  }
1337
1290
  }
1338
1291
  return of(displayItems);
@@ -1370,25 +1323,14 @@ class NgVirtualListComponent {
1370
1323
  }
1371
1324
  this._trackBox.items = displayItems;
1372
1325
  const _listContainerRef = this._listContainerRef;
1373
- while (this._displayComponents.length < displayItems.length) {
1326
+ const maxLength = displayItems.length, components = this._displayComponents;
1327
+ while (components.length < maxLength) {
1374
1328
  if (_listContainerRef) {
1375
1329
  const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
1376
- this._displayComponents.push(comp);
1330
+ components.push(comp);
1377
1331
  this._componentsResizeObserver.observe(comp.instance.element);
1378
1332
  }
1379
1333
  }
1380
- const maxLength = displayItems.length;
1381
- while (this._displayComponents.length > maxLength) {
1382
- const comp = this._displayComponents.pop();
1383
- if (comp) {
1384
- this._componentsResizeObserver.unobserve(comp.instance.element);
1385
- comp?.destroy();
1386
- const id = comp?.instance.item?.id;
1387
- if (id !== undefined) {
1388
- this._trackBox.untrackComponentByIdProperty(comp?.instance);
1389
- }
1390
- }
1391
- }
1392
1334
  this.resetRenderers();
1393
1335
  }
1394
1336
  resetRenderers(itemRenderer) {
@@ -1429,58 +1371,56 @@ class NgVirtualListComponent {
1429
1371
  clearScrollToRepeatExecutionTimeout() {
1430
1372
  clearTimeout(this._scrollToRepeatExecutionTimeout);
1431
1373
  }
1432
- scrollToExecutor(id, behavior, iteration = 0) {
1433
- this._isScrolling = true;
1434
- this.clearScrollToRepeatExecutionTimeout();
1374
+ scrollToExecutor(id, behavior, iteration = 0, isLastIteration = false) {
1435
1375
  const items = this.items();
1436
1376
  if (!items || !items.length) {
1437
1377
  return;
1438
1378
  }
1439
1379
  const dynamicSize = this.dynamicSize(), container = this._container(), itemSize = this.itemSize();
1440
1380
  if (container) {
1381
+ this.clearScrollToRepeatExecutionTimeout();
1441
1382
  if (dynamicSize) {
1442
1383
  if (container) {
1443
1384
  container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1444
- container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1445
1385
  }
1446
- 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 = {
1447
1387
  bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
1448
- itemsOffset: this.itemsOffset(), scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1388
+ itemsOffset: this.itemsOffset(), scrollSize: (isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft) + delta,
1449
1389
  snap: this.snap(), fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization(),
1450
1390
  }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1451
- this._scrollSize.set(scrollSize);
1391
+ this._trackBox.clearDelta();
1452
1392
  if (container) {
1453
- const handler = () => {
1454
- if (container) {
1455
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1456
- const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1457
- ...opts, scrollSize, fromItemId: id,
1458
- });
1459
- this.resetBoundsSize(isVertical, totalSize);
1460
- this.createDisplayComponentsIfNeed(displayItems);
1461
- this.tracking();
1462
- const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize, fromItemId: id });
1463
- if (scrollSize < _scrollSize && iteration < MAX_SCROLL_TO_ITERATIONS) {
1464
- this.clearScrollToRepeatExecutionTimeout();
1465
- this._scrollToRepeatExecutionTimeout = setTimeout(() => {
1466
- this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1);
1467
- });
1468
- }
1469
- else {
1470
- this._scrollSize.set(scrollSize);
1471
- container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1472
- container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1473
- }
1474
- }
1475
- };
1476
- 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
+ }
1477
1416
  }
1478
- container.nativeElement.scroll(params);
1417
+ container.nativeElement.scrollTo(params);
1418
+ this._scrollSize.set(scrollSize);
1479
1419
  }
1480
1420
  else {
1481
1421
  const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize();
1482
1422
  const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1483
- container.nativeElement.scroll(params);
1423
+ container.nativeElement.scrollTo(params);
1484
1424
  }
1485
1425
  }
1486
1426
  }
@@ -1489,7 +1429,6 @@ class NgVirtualListComponent {
1489
1429
  this.scrollTo(latItem.id, behavior);
1490
1430
  }
1491
1431
  _onContainerScrollHandler = (e) => {
1492
- this._isScrolling = true;
1493
1432
  const containerEl = this._container();
1494
1433
  if (containerEl) {
1495
1434
  const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
@@ -1503,7 +1442,6 @@ class NgVirtualListComponent {
1503
1442
  }
1504
1443
  };
1505
1444
  _onContainerScrollEndHandler = (e) => {
1506
- this._isScrollingDebounces.execute(false);
1507
1445
  const containerEl = this._container();
1508
1446
  if (containerEl) {
1509
1447
  const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
@@ -1523,7 +1461,6 @@ class NgVirtualListComponent {
1523
1461
  containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
1524
1462
  containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
1525
1463
  containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1526
- containerEl.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1527
1464
  this._resizeObserver = new ResizeObserver(this._onResizeHandler);
1528
1465
  this._resizeObserver.observe(containerEl.nativeElement);
1529
1466
  this._onResizeHandler();
@@ -1534,13 +1471,9 @@ class NgVirtualListComponent {
1534
1471
  if (this._trackBox) {
1535
1472
  this._trackBox.dispose();
1536
1473
  }
1537
- if (this._isScrollingDebounces) {
1538
- this._isScrollingDebounces.dispose();
1539
- }
1540
1474
  const containerEl = this._container();
1541
1475
  if (containerEl) {
1542
1476
  containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1543
- containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1544
1477
  containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
1545
1478
  containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
1546
1479
  if (this._componentsResizeObserver) {
@@ -1558,7 +1491,7 @@ class NgVirtualListComponent {
1558
1491
  }
1559
1492
  }
1560
1493
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1561
- 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 }, 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 }, 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 });
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 });
1562
1495
  }
1563
1496
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, decorators: [{
1564
1497
  type: Component,