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;
@@ -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,30 +235,25 @@ class Tracker {
236
235
  /**
237
236
  * tracking by propName
238
237
  */
239
- track(items, components, afterComponentSetup) {
238
+ track(items, components, direction) {
240
239
  var _a;
241
240
  if (!items) {
242
241
  return;
243
242
  }
244
- const idPropName = this._trackingPropertyName, untrackedItems = [...components];
245
- for (let i = 0, l = items.length; i < l; i++) {
243
+ const idPropName = this._trackingPropertyName, untrackedItems = [...components], isDown = direction === 0 || direction === 1;
244
+ for (let i = isDown ? 0 : items.length - 1, l = isDown ? items.length : 0; isDown ? i < l : i >= l; isDown ? i++ : i--) {
246
245
  const item = items[i], itemTrackingProperty = item[idPropName];
247
246
  if (this._trackMap) {
248
- const diId = this._trackMap[itemTrackingProperty];
249
247
  if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
250
- const lastIndex = this._displayObjectIndexMapById[diId], el = components[lastIndex];
251
- const elId = (_a = el === null || el === void 0 ? void 0 : el.instance) === null || _a === void 0 ? void 0 : _a.id;
252
- if (el && elId === diId) {
248
+ const diId = this._trackMap[itemTrackingProperty], compIndex = this._displayObjectIndexMapById[diId], comp = components[compIndex];
249
+ const compId = (_a = comp === null || comp === void 0 ? void 0 : comp.instance) === null || _a === void 0 ? void 0 : _a.id;
250
+ if (comp !== undefined && compId == diId) {
253
251
  const indexByUntrackedItems = untrackedItems.findIndex(v => {
254
- return v.instance.id === elId;
252
+ return v.instance.id == compId;
255
253
  });
256
254
  if (indexByUntrackedItems > -1) {
257
- if (el.instance.item !== item) {
258
- el.instance.item = item;
259
- if (afterComponentSetup !== undefined) {
260
- afterComponentSetup(el.instance, item);
261
- }
262
- }
255
+ comp.instance.item = item;
256
+ comp.instance.show();
263
257
  untrackedItems.splice(indexByUntrackedItems, 1);
264
258
  continue;
265
259
  }
@@ -270,20 +264,18 @@ class Tracker {
270
264
  if (untrackedItems.length > 0) {
271
265
  const el = untrackedItems.shift(), item = items[i];
272
266
  if (el) {
273
- if (el.instance.item !== item) {
274
- el.instance.item = item;
275
- if (this._trackMap) {
276
- this._trackMap[itemTrackingProperty] = el.instance.id;
277
- }
278
- if (afterComponentSetup !== undefined) {
279
- afterComponentSetup(el.instance, item);
280
- }
267
+ el.instance.item = item;
268
+ if (this._trackMap) {
269
+ this._trackMap[itemTrackingProperty] = el.instance.id;
281
270
  }
282
271
  }
283
272
  }
284
273
  }
285
274
  if (untrackedItems.length) {
286
- throw Error('Tracking by id caused an error.');
275
+ for (let i = 0, l = untrackedItems.length; i < l; i++) {
276
+ const comp = untrackedItems[i];
277
+ comp.instance.hide();
278
+ }
287
279
  }
288
280
  }
289
281
  untrackComponentByIdProperty(component) {
@@ -406,7 +398,7 @@ class EventEmitter {
406
398
  }
407
399
  }
408
400
 
409
- const MAX_SCROLL_DIRECTION_POOL = 8, CLEAR_SCROLL_DIRECTION_TO = 0;
401
+ const MAX_SCROLL_DIRECTION_POOL = 50, CLEAR_SCROLL_DIRECTION_TO = 10;
410
402
  /**
411
403
  * Cache map.
412
404
  * Emits a change event on each mutation.
@@ -454,15 +446,23 @@ class CacheMap extends EventEmitter {
454
446
  this._scrollDirectionCache.shift();
455
447
  }
456
448
  this._scrollDirectionCache.push(v);
457
- const dict = { [-1]: 0, [0]: 0, [1]: 0 };
458
- for (let i = 0, l = this._scrollDirectionCache.length; i < l; i++) {
459
- const dir = this._scrollDirectionCache[i];
449
+ const dict = { ['-1']: 0, ['0']: 0, ['1']: 0 };
450
+ for (let i = 0, l = this._scrollDirectionCache.length, li = l - 1; i < l; i++) {
451
+ const dir = String(this._scrollDirectionCache[i]);
460
452
  dict[dir] += 1;
453
+ if (i === li) {
454
+ for (let d in dict) {
455
+ if (d === String(v)) {
456
+ continue;
457
+ }
458
+ dict[d] -= 1;
459
+ }
460
+ }
461
461
  }
462
- if (dict[-1] > dict[0] && dict[-1] > dict[1]) {
462
+ if (dict['-1'] > dict['0'] && dict['-1'] > dict['1']) {
463
463
  return -1;
464
464
  }
465
- else if (dict[1] > dict[-1] && dict[1] > dict[0]) {
465
+ else if (dict['1'] > dict['-1'] && dict['1'] > dict['0']) {
466
466
  return 1;
467
467
  }
468
468
  return 0;
@@ -716,12 +716,34 @@ class TrackBox extends CacheMap {
716
716
  recalculateMetrics(options) {
717
717
  var _a;
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;
@@ -993,7 +1023,7 @@ class TrackBox extends CacheMap {
993
1023
  if (!this._items || !this._displayComponents) {
994
1024
  return;
995
1025
  }
996
- this._tracker.track(this._items, this._displayComponents);
1026
+ this._tracker.track(this._items, this._displayComponents, this.scrollDirection);
997
1027
  }
998
1028
  setDisplayObjectIndexMapById(v) {
999
1029
  this._tracker.displayObjectIndexMapById = v;
@@ -1129,8 +1159,6 @@ class NgVirtualListComponent extends DisposableComponent {
1129
1159
  };
1130
1160
  this._$snap = new BehaviorSubject(DEFAULT_SNAP);
1131
1161
  this.$snap = this._$snap.asObservable();
1132
- this._$snapToItem = new BehaviorSubject(DEFAULT_SNAP_TO_ITEM);
1133
- this.$snapToItem = this._$snapToItem.asObservable();
1134
1162
  this._$enabledBufferOptimization = new BehaviorSubject(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
1135
1163
  this.$enabledBufferOptimization = this._$enabledBufferOptimization.asObservable();
1136
1164
  this._$itemRenderer = new BehaviorSubject(undefined);
@@ -1158,81 +1186,22 @@ class NgVirtualListComponent extends DisposableComponent {
1158
1186
  this._displayComponents = [];
1159
1187
  this._$bounds = new BehaviorSubject(null);
1160
1188
  this._$scrollSize = new BehaviorSubject(0);
1161
- this._isScrollingDebounces = debounce((v) => {
1162
- this._isScrolling = v;
1163
- }, 250);
1164
- this._isScrolling = false;
1165
1189
  this._resizeObserver = null;
1166
1190
  this._onResizeHandler = () => {
1167
1191
  var _a, _b, _c;
1168
1192
  this._$bounds.next((_c = (_b = (_a = this._container) === null || _a === void 0 ? void 0 : _a.nativeElement) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect()) !== null && _c !== void 0 ? _c : null);
1169
1193
  };
1170
- this._scrolls = new Map();
1171
1194
  this._onScrollHandler = (e) => {
1172
1195
  var _a;
1173
- this._isScrollingDebounces.dispose();
1174
1196
  this.clearScrollToRepeatExecutionTimeout();
1175
1197
  const container = (_a = this._container) === null || _a === void 0 ? void 0 : _a.nativeElement;
1176
1198
  if (container) {
1177
1199
  const dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
1178
- let actualScrollSize = scrollSize, isScrollIUmmediate = false;
1179
- if (dynamicSize && delta !== 0) {
1180
- actualScrollSize = scrollSize + delta;
1181
- const params = {
1182
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1183
- behavior: BEHAVIOR_INSTANT
1184
- };
1185
- const container = this._container;
1186
- if (container) {
1187
- isScrollIUmmediate = true;
1188
- this.scrollImmediately(container, params);
1189
- }
1190
- }
1191
- if (!isScrollIUmmediate) {
1192
- this._$scrollSize.next(actualScrollSize);
1193
- }
1194
- }
1195
- };
1196
- this._onScrollEndHandler = (e) => {
1197
- const container = this._container;
1198
- if (container) {
1199
- const itemSize = this.itemSize, snapToItem = this.snapToItem, dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
1200
1200
  let actualScrollSize = scrollSize;
1201
- this._trackBox.clearDeltaDirection();
1202
- if (dynamicSize) {
1201
+ if (dynamicSize && delta !== 0) {
1203
1202
  actualScrollSize = scrollSize + delta;
1204
- if (snapToItem) {
1205
- const items = this.items, isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1206
- if (targetItem) {
1207
- this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1208
- }
1209
- this._trackBox.clearDelta();
1210
- }
1211
- else if (scrollSize !== actualScrollSize) {
1212
- const params = {
1213
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1214
- behavior: BEHAVIOR_INSTANT
1215
- };
1216
- const container = this._container;
1217
- if (container) {
1218
- this.scrollImmediately(container, params);
1219
- }
1220
- }
1221
- }
1222
- else {
1223
- const scrollItems = Math.round(scrollSize / itemSize);
1224
- actualScrollSize = snapToItem ? scrollItems * itemSize : scrollSize;
1225
- if (scrollSize !== actualScrollSize) {
1226
- const params = {
1227
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1228
- behavior: BEHAVIOR_INSTANT
1229
- };
1230
- const container = this._container;
1231
- if (container) {
1232
- this.scrollImmediately(container, params);
1233
- }
1234
- }
1235
1203
  }
1204
+ this._$scrollSize.next(actualScrollSize);
1236
1205
  }
1237
1206
  };
1238
1207
  this._$initialized = new BehaviorSubject(false);
@@ -1248,7 +1217,6 @@ class NgVirtualListComponent extends DisposableComponent {
1248
1217
  this._trackBox.changes();
1249
1218
  });
1250
1219
  this._onContainerScrollHandler = (e) => {
1251
- this._isScrolling = true;
1252
1220
  const containerEl = this._container;
1253
1221
  if (containerEl) {
1254
1222
  const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
@@ -1262,7 +1230,6 @@ class NgVirtualListComponent extends DisposableComponent {
1262
1230
  }
1263
1231
  };
1264
1232
  this._onContainerScrollEndHandler = (e) => {
1265
- this._isScrollingDebounces.execute(false);
1266
1233
  const containerEl = this._container;
1267
1234
  if (containerEl) {
1268
1235
  const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
@@ -1285,7 +1252,7 @@ class NgVirtualListComponent extends DisposableComponent {
1285
1252
  $trackBy.pipe(takeUntil(this._$unsubscribe), tap(v => {
1286
1253
  this._trackBox.trackingPropertyName = v;
1287
1254
  })).subscribe();
1288
- 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;
1255
+ 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;
1289
1256
  $isVertical.pipe(takeUntil(this._$unsubscribe), tap(v => {
1290
1257
  this._isVertical = v;
1291
1258
  const el = this._elementRef.nativeElement;
@@ -1295,10 +1262,11 @@ class NgVirtualListComponent extends DisposableComponent {
1295
1262
  this.listenCacheChangesIfNeed(dynamicSize);
1296
1263
  })).subscribe();
1297
1264
  combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
1298
- $itemsOffset, $snap, $snapToItem, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1299
- ]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), debounceTime(0), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, snapToItem, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
1265
+ $itemsOffset, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
1266
+ ]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
1267
+ var _a, _b, _c, _d;
1300
1268
  const { width, height } = bounds;
1301
- let actualScrollSize = scrollSize;
1269
+ let actualScrollSize = (_d = (this._isVertical ? (_b = (_a = this._container) === null || _a === void 0 ? void 0 : _a.nativeElement.scrollTop) !== null && _b !== void 0 ? _b : 0 : (_c = this._container) === null || _c === void 0 ? void 0 : _c.nativeElement.scrollLeft)) !== null && _d !== void 0 ? _d : 0;
1302
1270
  const opts = {
1303
1271
  bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
1304
1272
  itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
@@ -1308,21 +1276,29 @@ class NgVirtualListComponent extends DisposableComponent {
1308
1276
  this.createDisplayComponentsIfNeed(displayItems);
1309
1277
  this.tracking();
1310
1278
  const container = this._container;
1311
- if (!this.isScrolling && dynamicSize && container) {
1312
- actualScrollSize = scrollSize + this._trackBox.delta;
1313
- if (snapToItem) {
1314
- const items = this.items, isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1315
- if (targetItem) {
1316
- this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1279
+ if (container) {
1280
+ actualScrollSize = actualScrollSize + this._trackBox.delta;
1281
+ this._trackBox.clearDelta();
1282
+ if (dynamicSize) {
1283
+ if (scrollSize !== actualScrollSize) {
1284
+ const params = {
1285
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1286
+ behavior: BEHAVIOR_INSTANT
1287
+ };
1288
+ container.nativeElement.scrollTo(params);
1317
1289
  }
1318
1290
  }
1319
- else if (scrollSize !== actualScrollSize) {
1320
- const params = {
1321
- [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1322
- behavior: BEHAVIOR_INSTANT
1323
- };
1324
- this._trackBox.clearDelta();
1325
- container.nativeElement.scrollTo(params);
1291
+ else {
1292
+ if (scrollSize !== actualScrollSize) {
1293
+ const params = {
1294
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1295
+ behavior: BEHAVIOR_INSTANT
1296
+ };
1297
+ const container = this._container;
1298
+ if (container) {
1299
+ container.nativeElement.scrollTo(params);
1300
+ }
1301
+ }
1326
1302
  }
1327
1303
  }
1328
1304
  return of(displayItems);
@@ -1361,18 +1337,7 @@ class NgVirtualListComponent extends DisposableComponent {
1361
1337
  ;
1362
1338
  get snap() { return this._$snap.getValue(); }
1363
1339
  /**
1364
- * Determines whether scroll positions will be snapped to the element. Default value is "false".
1365
- */
1366
- set snapToItem(v) {
1367
- if (this._$snapToItem.getValue() === v) {
1368
- return;
1369
- }
1370
- this._$snapToItem.next(v);
1371
- this._cdr.markForCheck();
1372
- }
1373
- ;
1374
- get snapToItem() { return this._$snapToItem.getValue(); }
1375
- /**
1340
+ * Experimental!
1376
1341
  * Enables buffer optimization.
1377
1342
  * 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.
1378
1343
  * Works only if the property dynamic = true
@@ -1471,31 +1436,6 @@ class NgVirtualListComponent extends DisposableComponent {
1471
1436
  }
1472
1437
  ;
1473
1438
  get trackBy() { return this._$trackBy.getValue(); }
1474
- get isScrolling() { return this._isScrolling; }
1475
- scrollImmediately(container, params, cb) {
1476
- if (this._scrolls.size > 0) {
1477
- container.nativeElement.scrollTo(params);
1478
- return;
1479
- }
1480
- this._trackBox.clearDelta();
1481
- container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1482
- container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1483
- const handler = () => {
1484
- const container = this._container;
1485
- if (container) {
1486
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1487
- this._scrolls.delete(handler);
1488
- container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1489
- container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1490
- if (cb !== undefined) {
1491
- cb();
1492
- }
1493
- }
1494
- };
1495
- this._scrolls.set(handler, true);
1496
- container.nativeElement.addEventListener(SCROLL_END, handler);
1497
- container.nativeElement.scrollTo(params);
1498
- }
1499
1439
  get $cacheVersion() { return this._$cacheVersion.asObservable(); }
1500
1440
  ngOnInit() {
1501
1441
  this._$initialized.next(true);
@@ -1517,32 +1457,20 @@ class NgVirtualListComponent extends DisposableComponent {
1517
1457
  return isDirection(dir, Directions.VERTICAL);
1518
1458
  }
1519
1459
  createDisplayComponentsIfNeed(displayItems) {
1520
- var _a;
1521
1460
  if (!displayItems || !this._listContainerRef) {
1522
1461
  this._trackBox.setDisplayObjectIndexMapById({});
1523
1462
  return;
1524
1463
  }
1525
1464
  this._trackBox.items = displayItems;
1526
1465
  const _listContainerRef = this._listContainerRef;
1527
- while (this._displayComponents.length < displayItems.length) {
1466
+ const maxLength = displayItems.length, components = this._displayComponents;
1467
+ while (components.length < maxLength) {
1528
1468
  if (_listContainerRef) {
1529
1469
  const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
1530
- this._displayComponents.push(comp);
1470
+ components.push(comp);
1531
1471
  this._componentsResizeObserver.observe(comp.instance.element);
1532
1472
  }
1533
1473
  }
1534
- const maxLength = displayItems.length;
1535
- while (this._displayComponents.length > maxLength) {
1536
- const comp = this._displayComponents.pop();
1537
- if (comp) {
1538
- this._componentsResizeObserver.unobserve(comp.instance.element);
1539
- comp.destroy();
1540
- const id = (_a = comp === null || comp === void 0 ? void 0 : comp.instance.item) === null || _a === void 0 ? void 0 : _a.id;
1541
- if (id !== undefined) {
1542
- this._trackBox.untrackComponentByIdProperty(comp === null || comp === void 0 ? void 0 : comp.instance);
1543
- }
1544
- }
1545
- }
1546
1474
  this.resetRenderers();
1547
1475
  }
1548
1476
  resetRenderers(itemRenderer) {
@@ -1582,51 +1510,49 @@ class NgVirtualListComponent extends DisposableComponent {
1582
1510
  clearScrollToRepeatExecutionTimeout() {
1583
1511
  clearTimeout(this._scrollToRepeatExecutionTimeout);
1584
1512
  }
1585
- scrollToExecutor(id, behavior, iteration = 0) {
1586
- this._isScrolling = true;
1587
- this.clearScrollToRepeatExecutionTimeout();
1513
+ scrollToExecutor(id, behavior, iteration = 0, isLastIteration = false) {
1588
1514
  const items = this.items;
1589
1515
  if (!items || !items.length) {
1590
1516
  return;
1591
1517
  }
1592
1518
  const dynamicSize = this.dynamicSize, container = this._container, itemSize = this.itemSize;
1593
1519
  if (container) {
1520
+ this.clearScrollToRepeatExecutionTimeout();
1594
1521
  if (dynamicSize) {
1595
1522
  if (container) {
1596
1523
  container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1597
- container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1598
1524
  }
1599
- const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, isVertical = this._isVertical, opts = {
1525
+ const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, isVertical = this._isVertical, delta = this._trackBox.delta, opts = {
1600
1526
  bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
1601
- itemsOffset: this.itemsOffset, scrollSize: isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
1527
+ itemsOffset: this.itemsOffset, scrollSize: (isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft) + delta,
1602
1528
  snap: this.snap, fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization,
1603
1529
  }, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1604
- this._$scrollSize.next(scrollSize);
1530
+ this._trackBox.clearDelta();
1605
1531
  if (container) {
1606
- const handler = () => {
1607
- if (container) {
1608
- container.nativeElement.removeEventListener(SCROLL_END, handler);
1609
- const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, Object.assign(Object.assign({}, opts), { scrollSize, fromItemId: id }));
1610
- this.resetBoundsSize(isVertical, totalSize);
1611
- this.createDisplayComponentsIfNeed(displayItems);
1612
- this.tracking();
1613
- const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, Object.assign(Object.assign({}, 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);
1532
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, Object.assign(Object.assign({}, opts), { scrollSize, fromItemId: isLastIteration ? undefined : id })), 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, Object.assign(Object.assign({}, 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: [{