ng-virtual-list 14.0.13 → 14.0.14

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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { Component, ChangeDetectionStrategy, EventEmitter as EventEmitter$1, ViewContainerRef, ElementRef, ViewEncapsulation, ViewChild, Output, Input, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
- import { Subject, BehaviorSubject, takeUntil, tap, filter, map, combineLatest, distinctUntilChanged, debounceTime, switchMap, of } from 'rxjs';
5
+ import { Subject, BehaviorSubject, takeUntil, tap, filter, map, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
6
6
 
7
7
  /**
8
8
  * Axis of the arrangement of virtual list elements.
@@ -26,8 +26,7 @@ const DEFAULT_ITEM_SIZE = 24;
26
26
  const DEFAULT_ITEMS_OFFSET = 2;
27
27
  const DEFAULT_LIST_SIZE = 400;
28
28
  const DEFAULT_SNAP = false;
29
- const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = true;
30
- const DEFAULT_SNAP_TO_ITEM = false;
29
+ const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = false;
31
30
  const DEFAULT_DYNAMIC_SIZE = false;
32
31
  const TRACK_BY_PROPERTY_NAME = 'id';
33
32
  const DEFAULT_DIRECTION = Directions.VERTICAL;
@@ -114,7 +113,7 @@ class NgVirtualListItemComponent {
114
113
  const el = this._elementRef.nativeElement, { width, height, left, top } = el.getBoundingClientRect();
115
114
  return { width, height, x: left, y: top };
116
115
  }
117
- showIfNeed() {
116
+ show() {
118
117
  const styles = this._elementRef.nativeElement.style;
119
118
  if (styles.visibility === VISIBILITY_VISIBLE) {
120
119
  return;
@@ -235,29 +234,24 @@ class Tracker {
235
234
  /**
236
235
  * tracking by propName
237
236
  */
238
- track(items, components, afterComponentSetup) {
237
+ track(items, components, direction) {
239
238
  if (!items) {
240
239
  return;
241
240
  }
242
- const idPropName = this._trackingPropertyName, untrackedItems = [...components];
243
- for (let i = 0, l = items.length; i < l; i++) {
241
+ const idPropName = this._trackingPropertyName, untrackedItems = [...components], isDown = direction === 0 || direction === 1;
242
+ for (let i = isDown ? 0 : items.length - 1, l = isDown ? items.length : 0; isDown ? i < l : i >= l; isDown ? i++ : i--) {
244
243
  const item = items[i], itemTrackingProperty = item[idPropName];
245
244
  if (this._trackMap) {
246
- const diId = this._trackMap[itemTrackingProperty];
247
245
  if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
248
- const lastIndex = this._displayObjectIndexMapById[diId], el = components[lastIndex];
249
- const elId = el?.instance?.id;
250
- if (el && elId === diId) {
246
+ const diId = this._trackMap[itemTrackingProperty], compIndex = this._displayObjectIndexMapById[diId], comp = components[compIndex];
247
+ const compId = comp?.instance?.id;
248
+ if (comp !== undefined && compId == diId) {
251
249
  const indexByUntrackedItems = untrackedItems.findIndex(v => {
252
- return v.instance.id === elId;
250
+ return v.instance.id == compId;
253
251
  });
254
252
  if (indexByUntrackedItems > -1) {
255
- if (el.instance.item !== item) {
256
- el.instance.item = item;
257
- if (afterComponentSetup !== undefined) {
258
- afterComponentSetup(el.instance, item);
259
- }
260
- }
253
+ comp.instance.item = item;
254
+ comp.instance.show();
261
255
  untrackedItems.splice(indexByUntrackedItems, 1);
262
256
  continue;
263
257
  }
@@ -268,20 +262,18 @@ class Tracker {
268
262
  if (untrackedItems.length > 0) {
269
263
  const el = untrackedItems.shift(), item = items[i];
270
264
  if (el) {
271
- if (el.instance.item !== item) {
272
- el.instance.item = item;
273
- if (this._trackMap) {
274
- this._trackMap[itemTrackingProperty] = el.instance.id;
275
- }
276
- if (afterComponentSetup !== undefined) {
277
- afterComponentSetup(el.instance, item);
278
- }
265
+ el.instance.item = item;
266
+ if (this._trackMap) {
267
+ this._trackMap[itemTrackingProperty] = el.instance.id;
279
268
  }
280
269
  }
281
270
  }
282
271
  }
283
272
  if (untrackedItems.length) {
284
- throw Error('Tracking by id caused an error.');
273
+ for (let i = 0, l = untrackedItems.length; i < l; i++) {
274
+ const comp = untrackedItems[i];
275
+ comp.instance.hide();
276
+ }
285
277
  }
286
278
  }
287
279
  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;
@@ -716,12 +716,34 @@ class TrackBox extends CacheMap {
716
716
  */
717
717
  recalculateMetrics(options) {
718
718
  const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap, enabledBufferOptimization } = options;
719
- 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)
719
+ 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)
720
720
  || (typeof fromItemId === 'string' && fromItemId > '-1');
721
+ let leftItemsOffset = 0, rightItemsOffset = 0;
722
+ if (enabledBufferOptimization) {
723
+ switch (this.scrollDirection) {
724
+ case 1: {
725
+ leftItemsOffset = 0;
726
+ rightItemsOffset = itemsOffset;
727
+ break;
728
+ }
729
+ case -1: {
730
+ leftItemsOffset = itemsOffset;
731
+ rightItemsOffset = 0;
732
+ break;
733
+ }
734
+ case 0:
735
+ default: {
736
+ leftItemsOffset = rightItemsOffset = itemsOffset;
737
+ }
738
+ }
739
+ }
740
+ else {
741
+ leftItemsOffset = rightItemsOffset = itemsOffset;
742
+ }
721
743
  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;
722
744
  // If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
723
745
  if (dynamicSize) {
724
- let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
746
+ let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0, stickyComponentIndex = -1;
725
747
  for (let i = 0, l = collection.length; i < l; i++) {
726
748
  const ii = i + 1, collectionItem = collection[i], id = collectionItem.id;
727
749
  let componentSize = 0, componentSizeDelta = 0, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
@@ -752,18 +774,26 @@ class TrackBox extends CacheMap {
752
774
  if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
753
775
  stickyComponentSize = componentSize;
754
776
  stickyCollectionItem = collectionItem;
777
+ stickyComponentIndex = i;
755
778
  }
756
779
  if (id === fromItemId) {
757
780
  targetDisplayItemIndex = i;
758
- if (stickyCollectionItem && stickyMap && stickyMap[stickyCollectionItem.id] > 0) {
781
+ if (stickyCollectionItem && stickyMap) {
759
782
  const { num } = this.getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical);
760
783
  if (num > 0) {
761
784
  isTargetInOverscroll = true;
762
785
  y -= size - componentSize;
763
786
  }
764
787
  else {
765
- y -= stickyComponentSize;
766
- leftHiddenItemsWeight -= stickyComponentSize;
788
+ if (stickyMap && !stickyMap[collectionItem.id] && y >= scrollSize && y < scrollSize + stickyComponentSize) {
789
+ const snappedY = scrollSize - stickyComponentSize;
790
+ leftHiddenItemsWeight -= (snappedY - y);
791
+ y = snappedY;
792
+ }
793
+ else {
794
+ y -= stickyComponentSize;
795
+ leftHiddenItemsWeight -= stickyComponentSize;
796
+ }
767
797
  }
768
798
  }
769
799
  itemById = collectionItem;
@@ -992,7 +1022,7 @@ class TrackBox extends CacheMap {
992
1022
  if (!this._items || !this._displayComponents) {
993
1023
  return;
994
1024
  }
995
- this._tracker.track(this._items, this._displayComponents);
1025
+ this._tracker.track(this._items, this._displayComponents, this.scrollDirection);
996
1026
  }
997
1027
  setDisplayObjectIndexMapById(v) {
998
1028
  this._tracker.displayObjectIndexMapById = v;
@@ -1128,8 +1158,6 @@ class NgVirtualListComponent extends DisposableComponent {
1128
1158
  };
1129
1159
  this._$snap = new BehaviorSubject(DEFAULT_SNAP);
1130
1160
  this.$snap = this._$snap.asObservable();
1131
- this._$snapToItem = new BehaviorSubject(DEFAULT_SNAP_TO_ITEM);
1132
- this.$snapToItem = this._$snapToItem.asObservable();
1133
1161
  this._$enabledBufferOptimization = new BehaviorSubject(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
1134
1162
  this.$enabledBufferOptimization = this._$enabledBufferOptimization.asObservable();
1135
1163
  this._$itemRenderer = new BehaviorSubject(undefined);
@@ -1157,79 +1185,20 @@ class NgVirtualListComponent extends DisposableComponent {
1157
1185
  this._displayComponents = [];
1158
1186
  this._$bounds = new BehaviorSubject(null);
1159
1187
  this._$scrollSize = new BehaviorSubject(0);
1160
- this._isScrollingDebounces = debounce((v) => {
1161
- this._isScrolling = v;
1162
- }, 250);
1163
- this._isScrolling = false;
1164
1188
  this._resizeObserver = null;
1165
1189
  this._onResizeHandler = () => {
1166
1190
  this._$bounds.next(this._container?.nativeElement?.getBoundingClientRect() ?? null);
1167
1191
  };
1168
- this._scrolls = new Map();
1169
1192
  this._onScrollHandler = (e) => {
1170
- this._isScrollingDebounces.dispose();
1171
1193
  this.clearScrollToRepeatExecutionTimeout();
1172
1194
  const container = this._container?.nativeElement;
1173
1195
  if (container) {
1174
1196
  const dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
1175
- let actualScrollSize = scrollSize, isScrollIUmmediate = false;
1176
- if (dynamicSize && delta !== 0) {
1177
- actualScrollSize = scrollSize + delta;
1178
- const params = {
1179
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1180
- behavior: BEHAVIOR_INSTANT
1181
- };
1182
- const container = this._container;
1183
- if (container) {
1184
- isScrollIUmmediate = true;
1185
- this.scrollImmediately(container, params);
1186
- }
1187
- }
1188
- if (!isScrollIUmmediate) {
1189
- this._$scrollSize.next(actualScrollSize);
1190
- }
1191
- }
1192
- };
1193
- this._onScrollEndHandler = (e) => {
1194
- const container = this._container;
1195
- if (container) {
1196
- const itemSize = this.itemSize, snapToItem = this.snapToItem, dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
1197
1197
  let actualScrollSize = scrollSize;
1198
- this._trackBox.clearDeltaDirection();
1199
- if (dynamicSize) {
1198
+ if (dynamicSize && delta !== 0) {
1200
1199
  actualScrollSize = scrollSize + delta;
1201
- if (snapToItem) {
1202
- const items = this.items, isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1203
- if (targetItem) {
1204
- this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1205
- }
1206
- this._trackBox.clearDelta();
1207
- }
1208
- else if (scrollSize !== actualScrollSize) {
1209
- const params = {
1210
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1211
- behavior: BEHAVIOR_INSTANT
1212
- };
1213
- const container = this._container;
1214
- if (container) {
1215
- this.scrollImmediately(container, params);
1216
- }
1217
- }
1218
- }
1219
- else {
1220
- const scrollItems = Math.round(scrollSize / itemSize);
1221
- actualScrollSize = snapToItem ? scrollItems * itemSize : scrollSize;
1222
- if (scrollSize !== actualScrollSize) {
1223
- const params = {
1224
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1225
- behavior: BEHAVIOR_INSTANT
1226
- };
1227
- const container = this._container;
1228
- if (container) {
1229
- this.scrollImmediately(container, params);
1230
- }
1231
- }
1232
1200
  }
1201
+ this._$scrollSize.next(actualScrollSize);
1233
1202
  }
1234
1203
  };
1235
1204
  this._$initialized = new BehaviorSubject(false);
@@ -1245,7 +1214,6 @@ class NgVirtualListComponent extends DisposableComponent {
1245
1214
  this._trackBox.changes();
1246
1215
  });
1247
1216
  this._onContainerScrollHandler = (e) => {
1248
- this._isScrolling = true;
1249
1217
  const containerEl = this._container;
1250
1218
  if (containerEl) {
1251
1219
  const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
@@ -1259,7 +1227,6 @@ class NgVirtualListComponent extends DisposableComponent {
1259
1227
  }
1260
1228
  };
1261
1229
  this._onContainerScrollEndHandler = (e) => {
1262
- this._isScrollingDebounces.execute(false);
1263
1230
  const containerEl = this._container;
1264
1231
  if (containerEl) {
1265
1232
  const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
@@ -1282,7 +1249,7 @@ class NgVirtualListComponent extends DisposableComponent {
1282
1249
  $trackBy.pipe(takeUntil(this._$unsubscribe), tap(v => {
1283
1250
  this._trackBox.trackingPropertyName = v;
1284
1251
  })).subscribe();
1285
- const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = this.$itemsOffset.pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = this.$stickyMap.pipe(map(v => !v ? {} : v)), $snap = this.$snap, $snapToItem = this.$snapToItem, $isVertical = this.$direction.pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = this.$dynamicSize, $enabledBufferOptimization = this.$enabledBufferOptimization, $cacheVersion = this.$cacheVersion;
1252
+ const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = this.$itemsOffset.pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = this.$stickyMap.pipe(map(v => !v ? {} : v)), $snap = this.$snap, $isVertical = this.$direction.pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = this.$dynamicSize, $enabledBufferOptimization = this.$enabledBufferOptimization, $cacheVersion = this.$cacheVersion;
1286
1253
  $isVertical.pipe(takeUntil(this._$unsubscribe), tap(v => {
1287
1254
  this._isVertical = v;
1288
1255
  const el = this._elementRef.nativeElement;
@@ -1292,10 +1259,10 @@ class NgVirtualListComponent extends DisposableComponent {
1292
1259
  this.listenCacheChangesIfNeed(dynamicSize);
1293
1260
  })).subscribe();
1294
1261
  combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1295
- $itemsOffset, $snap, $snapToItem, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1296
- ]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), debounceTime(0), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, snapToItem, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
1262
+ $itemsOffset, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1263
+ ]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
1297
1264
  const { width, height } = bounds;
1298
- let actualScrollSize = scrollSize;
1265
+ let actualScrollSize = (this._isVertical ? this._container?.nativeElement.scrollTop ?? 0 : this._container?.nativeElement.scrollLeft) ?? 0;
1299
1266
  const opts = {
1300
1267
  bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
1301
1268
  itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
@@ -1307,21 +1274,29 @@ class NgVirtualListComponent extends DisposableComponent {
1307
1274
  this.createDisplayComponentsIfNeed(displayItems);
1308
1275
  this.tracking();
1309
1276
  const container = this._container;
1310
- if (!this.isScrolling && dynamicSize && container) {
1311
- actualScrollSize = scrollSize + this._trackBox.delta;
1312
- if (snapToItem) {
1313
- const items = this.items, isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1314
- if (targetItem) {
1315
- this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1277
+ if (container) {
1278
+ actualScrollSize = actualScrollSize + this._trackBox.delta;
1279
+ this._trackBox.clearDelta();
1280
+ if (dynamicSize) {
1281
+ if (scrollSize !== actualScrollSize) {
1282
+ const params = {
1283
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1284
+ behavior: BEHAVIOR_INSTANT
1285
+ };
1286
+ container.nativeElement.scrollTo(params);
1316
1287
  }
1317
1288
  }
1318
- else if (scrollSize !== actualScrollSize) {
1319
- const params = {
1320
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1321
- behavior: BEHAVIOR_INSTANT
1322
- };
1323
- this._trackBox.clearDelta();
1324
- container.nativeElement.scrollTo(params);
1289
+ else {
1290
+ if (scrollSize !== actualScrollSize) {
1291
+ const params = {
1292
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1293
+ behavior: BEHAVIOR_INSTANT
1294
+ };
1295
+ const container = this._container;
1296
+ if (container) {
1297
+ container.nativeElement.scrollTo(params);
1298
+ }
1299
+ }
1325
1300
  }
1326
1301
  }
1327
1302
  return of(displayItems);
@@ -1360,18 +1335,7 @@ class NgVirtualListComponent extends DisposableComponent {
1360
1335
  ;
1361
1336
  get snap() { return this._$snap.getValue(); }
1362
1337
  /**
1363
- * Determines whether scroll positions will be snapped to the element. Default value is "false".
1364
- */
1365
- set snapToItem(v) {
1366
- if (this._$snapToItem.getValue() === v) {
1367
- return;
1368
- }
1369
- this._$snapToItem.next(v);
1370
- this._cdr.markForCheck();
1371
- }
1372
- ;
1373
- get snapToItem() { return this._$snapToItem.getValue(); }
1374
- /**
1338
+ * Experimental!
1375
1339
  * Enables buffer optimization.
1376
1340
  * 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.
1377
1341
  * Works only if the property dynamic = true
@@ -1470,31 +1434,6 @@ class NgVirtualListComponent extends DisposableComponent {
1470
1434
  }
1471
1435
  ;
1472
1436
  get trackBy() { return this._$trackBy.getValue(); }
1473
- get isScrolling() { return this._isScrolling; }
1474
- scrollImmediately(container, params, cb) {
1475
- if (this._scrolls.size > 0) {
1476
- container.nativeElement.scrollTo(params);
1477
- return;
1478
- }
1479
- this._trackBox.clearDelta();
1480
- container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1481
- container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1482
- const handler = () => {
1483
- const container = this._container;
1484
- if (container) {
1485
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1486
- this._scrolls.delete(handler);
1487
- container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1488
- container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1489
- if (cb !== undefined) {
1490
- cb();
1491
- }
1492
- }
1493
- };
1494
- this._scrolls.set(handler, true);
1495
- container.nativeElement.addEventListener(SCROLL_END, handler);
1496
- container.nativeElement.scrollTo(params);
1497
- }
1498
1437
  get $cacheVersion() { return this._$cacheVersion.asObservable(); }
1499
1438
  ngOnInit() {
1500
1439
  this._$initialized.next(true);
@@ -1522,25 +1461,14 @@ class NgVirtualListComponent extends DisposableComponent {
1522
1461
  }
1523
1462
  this._trackBox.items = displayItems;
1524
1463
  const _listContainerRef = this._listContainerRef;
1525
- while (this._displayComponents.length < displayItems.length) {
1464
+ const maxLength = displayItems.length, components = this._displayComponents;
1465
+ while (components.length < maxLength) {
1526
1466
  if (_listContainerRef) {
1527
1467
  const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
1528
- this._displayComponents.push(comp);
1468
+ components.push(comp);
1529
1469
  this._componentsResizeObserver.observe(comp.instance.element);
1530
1470
  }
1531
1471
  }
1532
- const maxLength = displayItems.length;
1533
- while (this._displayComponents.length > maxLength) {
1534
- const comp = this._displayComponents.pop();
1535
- if (comp) {
1536
- this._componentsResizeObserver.unobserve(comp.instance.element);
1537
- comp.destroy();
1538
- const id = comp?.instance.item?.id;
1539
- if (id !== undefined) {
1540
- this._trackBox.untrackComponentByIdProperty(comp?.instance);
1541
- }
1542
- }
1543
- }
1544
1472
  this.resetRenderers();
1545
1473
  }
1546
1474
  resetRenderers(itemRenderer) {
@@ -1580,53 +1508,51 @@ class NgVirtualListComponent extends DisposableComponent {
1580
1508
  clearScrollToRepeatExecutionTimeout() {
1581
1509
  clearTimeout(this._scrollToRepeatExecutionTimeout);
1582
1510
  }
1583
- scrollToExecutor(id, behavior, iteration = 0) {
1584
- this._isScrolling = true;
1585
- this.clearScrollToRepeatExecutionTimeout();
1511
+ scrollToExecutor(id, behavior, iteration = 0, isLastIteration = false) {
1586
1512
  const items = this.items;
1587
1513
  if (!items || !items.length) {
1588
1514
  return;
1589
1515
  }
1590
1516
  const dynamicSize = this.dynamicSize, container = this._container, itemSize = this.itemSize;
1591
1517
  if (container) {
1518
+ this.clearScrollToRepeatExecutionTimeout();
1592
1519
  if (dynamicSize) {
1593
1520
  if (container) {
1594
1521
  container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1595
- container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1596
1522
  }
1597
- const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, isVertical = this._isVertical, opts = {
1523
+ const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, isVertical = this._isVertical, delta = this._trackBox.delta, opts = {
1598
1524
  bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
1599
- itemsOffset: this.itemsOffset, scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1525
+ itemsOffset: this.itemsOffset, scrollSize: (isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft) + delta,
1600
1526
  snap: this.snap, fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization,
1601
1527
  }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1602
- this._$scrollSize.next(scrollSize);
1528
+ this._trackBox.clearDelta();
1603
1529
  if (container) {
1604
- const handler = () => {
1605
- if (container) {
1606
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1607
- const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1608
- ...opts, scrollSize, fromItemId: id,
1609
- });
1610
- this.resetBoundsSize(isVertical, totalSize);
1611
- this.createDisplayComponentsIfNeed(displayItems);
1612
- this.tracking();
1613
- const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize, fromItemId: id });
1614
- if (scrollSize < _scrollSize && iteration < MAX_SCROLL_TO_ITERATIONS) {
1615
- this.clearScrollToRepeatExecutionTimeout();
1616
- this._scrollToRepeatExecutionTimeout = setTimeout(() => {
1617
- this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1);
1618
- });
1619
- }
1620
- else {
1621
- this._$scrollSize.next(scrollSize);
1622
- container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1623
- container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1624
- }
1625
- }
1626
- };
1627
- container.nativeElement.addEventListener(SCROLL_END, handler);
1530
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
1531
+ ...opts, scrollSize, fromItemId: isLastIteration ? undefined : id,
1532
+ }), delta = this._trackBox.delta;
1533
+ this._trackBox.clearDelta();
1534
+ let actualScrollSize = scrollSize + delta;
1535
+ this.resetBoundsSize(isVertical, totalSize);
1536
+ this.createDisplayComponentsIfNeed(displayItems);
1537
+ this.tracking();
1538
+ const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize: actualScrollSize, fromItemId: id });
1539
+ const notChanged = actualScrollSize === _scrollSize;
1540
+ if (notChanged) {
1541
+ iteration += 1;
1542
+ }
1543
+ if (iteration < MAX_SCROLL_TO_ITERATIONS) {
1544
+ this.clearScrollToRepeatExecutionTimeout();
1545
+ this._scrollToRepeatExecutionTimeout = setTimeout(() => {
1546
+ this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1, notChanged);
1547
+ });
1548
+ }
1549
+ else {
1550
+ this._$scrollSize.next(actualScrollSize);
1551
+ container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1552
+ }
1628
1553
  }
1629
1554
  container.nativeElement.scrollTo(params);
1555
+ this._$scrollSize.next(scrollSize);
1630
1556
  }
1631
1557
  else {
1632
1558
  const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize;
@@ -1646,43 +1572,28 @@ class NgVirtualListComponent extends DisposableComponent {
1646
1572
  containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
1647
1573
  containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
1648
1574
  containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1649
- containerEl.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1650
1575
  this._resizeObserver = new ResizeObserver(this._onResizeHandler);
1651
1576
  this._resizeObserver.observe(containerEl.nativeElement);
1652
1577
  this._onResizeHandler();
1653
1578
  }
1654
1579
  }
1655
- clearScrollImmediately() {
1656
- const container = this._container;
1657
- if (container) {
1658
- this._scrolls.forEach((_, handler) => {
1659
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1660
- });
1661
- }
1662
- this._scrolls.clear();
1663
- }
1664
1580
  ngOnDestroy() {
1665
1581
  super.ngOnDestroy();
1666
1582
  this.clearScrollToRepeatExecutionTimeout();
1667
- this.clearScrollImmediately();
1668
1583
  if (this._trackBox) {
1669
1584
  this._trackBox.dispose();
1670
1585
  }
1671
- if (this._isScrollingDebounces) {
1672
- this._isScrollingDebounces.dispose();
1673
- }
1674
- if (this._componentsResizeObserver) {
1675
- this._componentsResizeObserver.disconnect();
1676
- }
1677
- if (this._resizeObserver) {
1678
- this._resizeObserver.disconnect();
1679
- }
1680
1586
  const containerEl = this._container;
1681
1587
  if (containerEl) {
1682
1588
  containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1683
- containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1684
1589
  containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
1685
1590
  containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
1591
+ if (this._componentsResizeObserver) {
1592
+ this._componentsResizeObserver.disconnect();
1593
+ }
1594
+ if (this._resizeObserver) {
1595
+ this._resizeObserver.disconnect();
1596
+ }
1686
1597
  }
1687
1598
  if (this._displayComponents) {
1688
1599
  while (this._displayComponents.length > 0) {
@@ -1694,7 +1605,7 @@ class NgVirtualListComponent extends DisposableComponent {
1694
1605
  }
1695
1606
  NgVirtualListComponent.__nextId = 0;
1696
1607
  NgVirtualListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
1697
- NgVirtualListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", snap: "snap", snapToItem: "snapToItem", enabledBufferOptimization: "enabledBufferOptimization", itemRenderer: "itemRenderer", stickyMap: "stickyMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", itemsOffset: "itemsOffset", trackBy: "trackBy" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }], usesInheritance: true, ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
1608
+ NgVirtualListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", snap: "snap", enabledBufferOptimization: "enabledBufferOptimization", itemRenderer: "itemRenderer", stickyMap: "stickyMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", itemsOffset: "itemsOffset", trackBy: "trackBy" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }], usesInheritance: true, ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
1698
1609
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListComponent, decorators: [{
1699
1610
  type: Component,
1700
1611
  args: [{ selector: 'ng-virtual-list', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, 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"] }]
@@ -1715,8 +1626,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImpor
1715
1626
  type: Input
1716
1627
  }], snap: [{
1717
1628
  type: Input
1718
- }], snapToItem: [{
1719
- type: Input
1720
1629
  }], enabledBufferOptimization: [{
1721
1630
  type: Input
1722
1631
  }], itemRenderer: [{