ng-virtual-list 20.7.20 → 20.8.0

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.
@@ -803,7 +803,7 @@ const debounce = (cb, debounceTime = 0) => {
803
803
 
804
804
  /**
805
805
  * Switch css classes
806
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/toggleClassName.ts
806
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/toggle-class-name.ts
807
807
  * @author Evgenii Grebennikov
808
808
  * @email djonnyx@gmail.com
809
809
  */
@@ -818,7 +818,7 @@ const toggleClassName = (el, className, removeClassName) => {
818
818
 
819
819
  /**
820
820
  * Scroll event.
821
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/scrollEvent.ts
821
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/scroll-event.ts
822
822
  * @author Evgenii Grebennikov
823
823
  * @email djonnyx@gmail.com
824
824
  */
@@ -860,7 +860,7 @@ class ScrollEvent {
860
860
 
861
861
  /**
862
862
  * Simple event emitter
863
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/eventEmitter.ts
863
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/event-emitter.ts
864
864
  * @author Evgenii Grebennikov
865
865
  * @email djonnyx@gmail.com
866
866
  */
@@ -989,13 +989,16 @@ class CMap {
989
989
  clear() {
990
990
  this._dict = {};
991
991
  }
992
+ toObject() {
993
+ return this._dict;
994
+ }
992
995
  }
993
996
  const CACHE_BOX_CHANGE_EVENT_NAME = 'change';
994
997
  const MAX_SCROLL_DIRECTION_POOL = 50, CLEAR_SCROLL_DIRECTION_TO = 10, DIR_BACK = '-1', DIR_NONE = '0', DIR_FORWARD = '1';
995
998
  /**
996
999
  * Cache map.
997
1000
  * Emits a change event on each mutation.
998
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/cacheMap.ts
1001
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/cache-map.ts
999
1002
  * @author Evgenii Grebennikov
1000
1003
  * @email djonnyx@gmail.com
1001
1004
  */
@@ -1118,6 +1121,12 @@ class CacheMap extends EventEmitter {
1118
1121
  get(id) {
1119
1122
  return this._map.get(id);
1120
1123
  }
1124
+ delete(id) {
1125
+ this._map.delete(id);
1126
+ }
1127
+ clear() {
1128
+ this._map.clear();
1129
+ }
1121
1130
  snapshot() {
1122
1131
  this._snapshot = new CMap(this._map);
1123
1132
  }
@@ -1170,10 +1179,10 @@ class Tracker {
1170
1179
  if (!items) {
1171
1180
  return;
1172
1181
  }
1173
- const idPropName = this._trackingPropertyName, untrackedItems = [...components], newTrackItems = [], isDown = direction === 0 || direction === 1;
1182
+ const untrackedItems = [...components], newTrackItems = [], isDown = direction === 0 || direction === 1;
1174
1183
  let isRegularSnapped = false;
1175
1184
  for (let i = isDown ? 0 : items.length - 1, l = isDown ? items.length : 0; isDown ? i < l : i >= l; isDown ? i++ : i--) {
1176
- const item = items[i], itemTrackingProperty = item[idPropName];
1185
+ const item = items[i], itemTrackingProperty = item.id;
1177
1186
  if (this._trackMap) {
1178
1187
  if (this._trackMap.has(itemTrackingProperty)) {
1179
1188
  const diId = this._trackMap.get(itemTrackingProperty), compIndex = this._displayObjectIndexMapById[diId], comp = components[compIndex];
@@ -1219,7 +1228,7 @@ class Tracker {
1219
1228
  }
1220
1229
  for (let i = 0, l = newTrackItems.length; i < l; i++) {
1221
1230
  if (untrackedItems.length > 0) {
1222
- const comp = untrackedItems.shift(), item = newTrackItems[i], itemTrackingProperty = item[idPropName];
1231
+ const comp = untrackedItems.shift(), item = newTrackItems[i], itemTrackingProperty = item.id;
1223
1232
  if (comp) {
1224
1233
  if (snapedComponent) {
1225
1234
  if (item['config']['snapped'] || item['config']['snappedOut']) {
@@ -1294,7 +1303,7 @@ const bufferInterpolation = (currentBufferValue, array, value, extra) => {
1294
1303
  else {
1295
1304
  array.push(value);
1296
1305
  }
1297
- while (array.length >= bufferSize) {
1306
+ while (array.length > bufferSize) {
1298
1307
  array.shift();
1299
1308
  }
1300
1309
  const l = array.length;
@@ -1320,7 +1329,7 @@ var ItemDisplayMethods;
1320
1329
  const DEFAULT_BUFFER_EXTREMUM_THRESHOLD = 15, DEFAULT_MAX_BUFFER_SEQUENCE_LENGTH = 30, DEFAULT_RESET_BUFFER_SIZE_TIMEOUT = 10000, IS_NEW = 'isNew';
1321
1330
  /**
1322
1331
  * An object that performs tracking, calculations and caching.
1323
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/trackBox.ts
1332
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/track-box.ts
1324
1333
  * @author Evgenii Grebennikov
1325
1334
  * @email djonnyx@gmail.com
1326
1335
  */
@@ -1376,14 +1385,14 @@ class TrackBox extends CacheMap {
1376
1385
  initialize() {
1377
1386
  this._tracker = new Tracker(this._trackingPropertyName);
1378
1387
  }
1379
- set(id, bounds) {
1388
+ set(id, cache) {
1380
1389
  if (this._map.has(id)) {
1381
1390
  const b = this._map.get(id);
1382
- if (b?.width === bounds.width && b.height === bounds.height) {
1391
+ if (b?.width === cache.width && b.height === cache.height) {
1383
1392
  return this._map;
1384
1393
  }
1385
1394
  }
1386
- const v = this._map.set(id, bounds);
1395
+ const v = this._map.set(id, cache);
1387
1396
  this.bumpVersion();
1388
1397
  return v;
1389
1398
  }
@@ -1409,7 +1418,7 @@ class TrackBox extends CacheMap {
1409
1418
  _maxBufferSize = this._defaultBufferSize;
1410
1419
  _resetBufferSizeTimeout = DEFAULT_RESET_BUFFER_SIZE_TIMEOUT;
1411
1420
  _resetBufferSizeTimer;
1412
- isReseted = true;
1421
+ _isReseted = true;
1413
1422
  lifeCircle() {
1414
1423
  this.fireChangeIfNeed();
1415
1424
  this.lifeCircleDo();
@@ -1418,20 +1427,14 @@ class TrackBox extends CacheMap {
1418
1427
  * Scans the collection for deleted items and flushes the deleted item cache.
1419
1428
  */
1420
1429
  resetCollection(currentCollection, itemSize) {
1421
- if (currentCollection !== undefined && currentCollection !== null && currentCollection === this._previousCollection) {
1430
+ if (currentCollection !== undefined && currentCollection !== null &&
1431
+ currentCollection === this._previousCollection) {
1422
1432
  console.warn('Attention! The collection must be immutable.');
1423
1433
  return;
1424
1434
  }
1425
- let reseted = this.isReseted;
1426
- if (reseted) {
1427
- if (!(!this._previousCollection || this._previousCollection.length === 0)) {
1428
- reseted = false;
1429
- }
1430
- }
1431
- if (!reseted && (!currentCollection || currentCollection.length === 0)) {
1432
- reseted = true;
1433
- }
1434
- this.isReseted = reseted;
1435
+ const reseted = !((!!this._previousCollection && this._previousCollection.length > 0) &&
1436
+ (!!currentCollection && currentCollection.length > 0));
1437
+ this._isReseted = reseted;
1435
1438
  this.dispatch(TrackBoxEvents.RESET, reseted);
1436
1439
  this.updateCache(this._previousCollection, currentCollection, itemSize);
1437
1440
  this._previousCollection = [...(currentCollection || [])];
@@ -1440,12 +1443,13 @@ class TrackBox extends CacheMap {
1440
1443
  * Update the cache of items from the list
1441
1444
  */
1442
1445
  updateCache(previousCollection, currentCollection, itemSize) {
1446
+ const trackBy = this._trackingPropertyName;
1443
1447
  let crudDetected = false;
1444
1448
  if (!currentCollection || currentCollection.length === 0) {
1445
1449
  if (previousCollection) {
1446
1450
  // deleted
1447
1451
  for (let i = 0, l = previousCollection.length; i < l; i++) {
1448
- const item = previousCollection[i], id = item.id;
1452
+ const item = previousCollection[i], id = item[trackBy];
1449
1453
  crudDetected = true;
1450
1454
  if (this._map.has(id)) {
1451
1455
  this._map.delete(id);
@@ -1459,7 +1463,7 @@ class TrackBox extends CacheMap {
1459
1463
  // added
1460
1464
  for (let i = 0, l = currentCollection.length; i < l; i++) {
1461
1465
  crudDetected = true;
1462
- const item = currentCollection[i], id = item.id;
1466
+ const item = currentCollection[i], id = item[trackBy];
1463
1467
  this._map.set(id, { width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
1464
1468
  }
1465
1469
  }
@@ -1469,38 +1473,45 @@ class TrackBox extends CacheMap {
1469
1473
  for (let i = 0, l = currentCollection.length; i < l; i++) {
1470
1474
  const item = currentCollection[i];
1471
1475
  if (item) {
1472
- collectionDict[item.id] = item;
1476
+ collectionDict[item[trackBy]] = item;
1473
1477
  }
1474
1478
  }
1475
1479
  const notChangedMap = {}, deletedMap = {}, deletedItemsMap = {}, updatedMap = {};
1476
1480
  for (let i = 0, l = previousCollection.length; i < l; i++) {
1477
- const item = previousCollection[i], id = item.id;
1481
+ const item = previousCollection[i], id = item[trackBy];
1478
1482
  if (item) {
1479
1483
  if (collectionDict.hasOwnProperty(id)) {
1480
1484
  if (item === collectionDict[id]) {
1481
1485
  // not changed
1482
- notChangedMap[item.id] = item;
1483
- this._map.set(id, { ...(this._map.get(id) || { width: itemSize, height: itemSize }), method: ItemDisplayMethods.NOT_CHANGED });
1486
+ notChangedMap[item[trackBy]] = item;
1487
+ this._map.set(id, {
1488
+ ...(this._map.get(id) || { width: itemSize, height: itemSize }),
1489
+ method: ItemDisplayMethods.NOT_CHANGED
1490
+ });
1484
1491
  continue;
1485
1492
  }
1486
1493
  else {
1487
1494
  // updated
1488
1495
  crudDetected = true;
1489
- updatedMap[item.id] = item;
1490
- this._map.set(id, { ...(this._map.get(id) || { width: itemSize, height: itemSize }), method: ItemDisplayMethods.UPDATE });
1496
+ updatedMap[item[trackBy]] = item;
1497
+ this._map.set(id, {
1498
+ ...(this._map.get(id) || { width: itemSize, height: itemSize }),
1499
+ method: ItemDisplayMethods.UPDATE
1500
+ });
1491
1501
  continue;
1492
1502
  }
1493
1503
  }
1494
1504
  // deleted
1495
1505
  crudDetected = true;
1496
- deletedMap[item.id] = item;
1497
- deletedItemsMap[i] = this._map.get(item.id);
1506
+ deletedMap[id] = item;
1507
+ deletedItemsMap[i] = this._map.get(id);
1498
1508
  this._map.delete(id);
1499
1509
  }
1500
1510
  }
1501
1511
  for (let i = 0, l = currentCollection.length; i < l; i++) {
1502
- const item = currentCollection[i], id = item.id;
1503
- if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) && !notChangedMap.hasOwnProperty(id)) {
1512
+ const item = currentCollection[i], id = item[trackBy];
1513
+ if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) &&
1514
+ !notChangedMap.hasOwnProperty(id)) {
1504
1515
  // added
1505
1516
  crudDetected = true;
1506
1517
  this._map.set(id, { width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
@@ -1553,12 +1564,6 @@ class TrackBox extends CacheMap {
1553
1564
  const displayItems = this.generateDisplayCollection(items, itemConfigMap, { ...metrics, });
1554
1565
  return { displayItems, totalSize: metrics.totalSize, delta: metrics.delta, crudDetected };
1555
1566
  }
1556
- /**
1557
- * Finds the closest element in the collection by scrollSize
1558
- */
1559
- getNearestItem(scrollSize, items, itemSize, isVertical) {
1560
- return this.getElementFromStart(scrollSize, items, this._map, itemSize, isVertical);
1561
- }
1562
1567
  _previousScrollSize = 0;
1563
1568
  updateAdaptiveBufferParams(metrics, totalItemsLength) {
1564
1569
  this.disposeClearBufferSizeTimer();
@@ -1580,41 +1585,18 @@ class TrackBox extends CacheMap {
1580
1585
  disposeClearBufferSizeTimer() {
1581
1586
  clearTimeout(this._resetBufferSizeTimer);
1582
1587
  }
1583
- /**
1584
- * Calculates the position of an element based on the given scrollSize
1585
- */
1586
- getElementFromStart(scrollSize, collection, map, typicalItemSize, isVertical) {
1587
- const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME;
1588
- let offset = 0;
1589
- for (let i = 0, l = collection.length; i < l; i++) {
1590
- const item = collection[i], id = item.id;
1591
- let itemSize = 0;
1592
- if (map.has(id)) {
1593
- const bounds = map.get(id);
1594
- itemSize = bounds ? bounds[sizeProperty] : typicalItemSize;
1595
- }
1596
- else {
1597
- itemSize = typicalItemSize;
1598
- }
1599
- if (offset > scrollSize) {
1600
- return item;
1601
- }
1602
- offset += itemSize;
1603
- }
1604
- return undefined;
1605
- }
1606
1588
  /**
1607
1589
  * Calculates the entry into the overscroll area and returns the number of overscroll elements
1608
1590
  */
1609
1591
  getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical, indexOffset = 0) {
1610
- const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME;
1592
+ const trackBy = this._trackingPropertyName, sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME;
1611
1593
  let offset = 0, num = 0;
1612
1594
  for (let j = collection.length - indexOffset - 1; j >= i; j--) {
1613
- const item = collection[j], id = item.id;
1595
+ const item = collection[j], id = item[trackBy];
1614
1596
  let itemSize = 0;
1615
1597
  if (map.has(id)) {
1616
- const bounds = map.get(id);
1617
- itemSize = bounds ? bounds[sizeProperty] : typicalItemSize;
1598
+ const cache = map.get(id);
1599
+ itemSize = cache ? cache[sizeProperty] : typicalItemSize;
1618
1600
  }
1619
1601
  else {
1620
1602
  itemSize = typicalItemSize;
@@ -1631,8 +1613,8 @@ class TrackBox extends CacheMap {
1631
1613
  * Calculates list metrics
1632
1614
  */
1633
1615
  recalculateMetrics(options) {
1634
- const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, bufferSize: minBufferSize, scrollSize, snap, itemConfigMap, enabledBufferOptimization, previousTotalSize, crudDetected, deletedItemsMap } = options;
1635
- const bufferSize = Math.max(minBufferSize, this._bufferSize), { 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)
1616
+ const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, bufferSize: minBufferSize, scrollSize, snap, itemConfigMap, enabledBufferOptimization, previousTotalSize, crudDetected, deletedItemsMap } = options, roundedScrollSize = Math.round(scrollSize);
1617
+ const trackBy = this._trackingPropertyName, bufferSize = Math.max(minBufferSize, this._bufferSize), { 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)
1636
1618
  || (typeof fromItemId === 'string' && fromItemId > '-1');
1637
1619
  let leftItemsOffset = 0, rightItemsOffset = 0;
1638
1620
  if (enabledBufferOptimization) {
@@ -1657,41 +1639,40 @@ class TrackBox extends CacheMap {
1657
1639
  leftItemsOffset = rightItemsOffset = bufferSize;
1658
1640
  }
1659
1641
  let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, leftSizeOfAddedItems = 0, leftSizeOfUpdatedItems = 0, leftSizeOfDeletedItems = 0, itemById = undefined, itemByIdPos = 0, targetDisplayItemIndex = -1, isTargetInOverscroll = false, actualScrollSize = itemByIdPos, totalSize = 0, startIndex, isFromItemIdFound = false, deltaFromStartCreation = 0;
1660
- let isNew = !this.isReseted && (scrollSize === 0);
1642
+ let isNew = !this._isReseted && (roundedScrollSize === 0);
1661
1643
  // If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
1662
1644
  if (dynamicSize) {
1663
1645
  let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
1664
1646
  for (let i = 0, l = collection.length; i < l; i++) {
1665
- const ii = i + 1, collectionItem = collection[i], id = collectionItem.id;
1647
+ const ii = i + 1, collectionItem = collection[i], id = collectionItem[trackBy];
1666
1648
  let componentSize = 0, componentSizeDelta = 0, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
1667
1649
  if (map.has(id)) {
1668
- const bounds = map.get(id) || { width: typicalItemSize, height: typicalItemSize };
1669
- componentSize = bounds[sizeProperty];
1670
- itemDisplayMethod = bounds?.method ?? ItemDisplayMethods.UPDATE;
1671
- const isItemNew = bounds?.[IS_NEW] ?? this._isLazy;
1672
- if (!isItemNew && (!this._isLazy || !itemConfigMap[collection[0].id]?.sticky)) {
1650
+ const cache = map.get(id) || { width: typicalItemSize, height: typicalItemSize };
1651
+ componentSize = cache[sizeProperty];
1652
+ itemDisplayMethod = cache?.method ?? ItemDisplayMethods.UPDATE;
1653
+ const isItemNew = cache?.[IS_NEW] ?? (this._isLazy && this._isReseted);
1654
+ if (!isItemNew && (!this._isLazy || !itemConfigMap[collection[0][trackBy]]?.sticky) || (roundedScrollSize > 0)) {
1673
1655
  isNew = false;
1674
1656
  }
1675
1657
  switch (itemDisplayMethod) {
1676
1658
  case ItemDisplayMethods.UPDATE: {
1677
- const snapshotBounds = snapshot.get(id);
1678
- const componentSnapshotSize = componentSize - (snapshotBounds ? snapshotBounds[sizeProperty] : typicalItemSize);
1679
- componentSizeDelta = isItemNew ? 0 : componentSnapshotSize;
1680
- map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED, isNew: false });
1681
- if (isItemNew) {
1659
+ const snapshotBounds = snapshot.get(id), componentSnapshotSize = componentSize - (snapshotBounds ? snapshotBounds[sizeProperty] : typicalItemSize);
1660
+ componentSizeDelta = componentSnapshotSize;
1661
+ map.set(id, { ...cache, method: ItemDisplayMethods.NOT_CHANGED, isNew: false });
1662
+ if (isNew) {
1682
1663
  deltaFromStartCreation += componentSize;
1683
1664
  }
1684
1665
  break;
1685
1666
  }
1686
1667
  case ItemDisplayMethods.CREATE: {
1687
1668
  componentSizeDelta = isNew ? 0 : typicalItemSize;
1688
- map.set(id, { ...bounds, method: isNew ? ItemDisplayMethods.UPDATE : ItemDisplayMethods.NOT_CHANGED, isNew });
1669
+ map.set(id, { ...cache, method: ItemDisplayMethods.UPDATE, isNew });
1689
1670
  break;
1690
1671
  }
1691
1672
  }
1692
1673
  }
1693
1674
  if (deletedItemsMap.hasOwnProperty(i)) {
1694
- const bounds = deletedItemsMap[i], size = bounds?.[sizeProperty] ?? typicalItemSize;
1675
+ const cache = deletedItemsMap[i], size = cache?.[sizeProperty] ?? typicalItemSize;
1695
1676
  if (y < scrollSize - size) {
1696
1677
  leftSizeOfDeletedItems += size;
1697
1678
  }
@@ -1713,7 +1694,7 @@ class TrackBox extends CacheMap {
1713
1694
  y -= size - componentSize;
1714
1695
  }
1715
1696
  else {
1716
- if (itemConfigMap && !itemConfigMap[collectionItem.id] && y >= scrollSize && y < scrollSize + stickyComponentSize) {
1697
+ if (itemConfigMap && !itemConfigMap[id] && y >= scrollSize && y < scrollSize + stickyComponentSize) {
1717
1698
  const snappedY = scrollSize - stickyComponentSize;
1718
1699
  leftHiddenItemsWeight -= (snappedY - y);
1719
1700
  y = snappedY;
@@ -1801,24 +1782,24 @@ class TrackBox extends CacheMap {
1801
1782
  if (crudDetected) {
1802
1783
  let y = 0;
1803
1784
  for (let i = 0, l = collection.length; i < l; i++) {
1804
- const collectionItem = collection[i], id = collectionItem.id;
1785
+ const collectionItem = collection[i], id = collectionItem[trackBy];
1805
1786
  let componentSize = typicalItemSize, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
1806
1787
  if (map.has(id)) {
1807
- const bounds = map.get(id);
1808
- itemDisplayMethod = bounds?.method ?? ItemDisplayMethods.UPDATE;
1809
- const isItemNew = bounds?.[IS_NEW] ?? this._isLazy;
1810
- if (!isItemNew && (!this._isLazy || !itemConfigMap[collection[0].id]?.sticky)) {
1788
+ const cache = map.get(id);
1789
+ itemDisplayMethod = cache?.method ?? ItemDisplayMethods.UPDATE;
1790
+ const isItemNew = cache?.[IS_NEW] ?? this._isLazy;
1791
+ if (!isItemNew && (!this._isLazy || !itemConfigMap[collection[0][trackBy]]?.sticky)) {
1811
1792
  isNew = false;
1812
1793
  }
1813
1794
  if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
1814
1795
  if (isNew) {
1815
1796
  deltaFromStartCreation += componentSize;
1816
1797
  }
1817
- map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED, isNew });
1798
+ map.set(id, { ...cache, method: ItemDisplayMethods.NOT_CHANGED, isNew });
1818
1799
  }
1819
1800
  }
1820
1801
  if (deletedItemsMap.hasOwnProperty(i)) {
1821
- const bounds = deletedItemsMap[i], size = bounds?.[sizeProperty] ?? typicalItemSize;
1802
+ const cache = deletedItemsMap[i], size = cache?.[sizeProperty] ?? typicalItemSize;
1822
1803
  if (y < scrollSize - size) {
1823
1804
  leftSizeOfDeletedItems += size;
1824
1805
  }
@@ -1908,14 +1889,15 @@ class TrackBox extends CacheMap {
1908
1889
  generateDisplayCollection(items, itemConfigMap, metrics) {
1909
1890
  const { width, height, normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsOnDisplayLength, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
1910
1891
  if (items.length) {
1911
- const actualSnippedPosition = snippedPos, isSnappingMethodAdvanced = this.isSnappingMethodAdvanced, boundsSize = isVertical ? height : width, actualEndSnippedPosition = boundsSize;
1892
+ const trackBy = this._trackingPropertyName, actualSnippedPosition = snippedPos, isSnappingMethodAdvanced = this.isSnappingMethodAdvanced, boundsSize = isVertical ? height : width, actualEndSnippedPosition = boundsSize;
1912
1893
  let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0, endStickyItem, nextEndSticky, endStickyItemIndex = -1, endStickyItemSize = 0, count = 1;
1913
1894
  if (snap) {
1914
1895
  for (let i = Math.min(itemsFromStartToScrollEnd > 0 ? itemsFromStartToScrollEnd : 0, totalLength - 1); i >= 0; i--) {
1915
- if (!items[i]) {
1896
+ const collectionItem = items[i];
1897
+ if (!collectionItem) {
1916
1898
  continue;
1917
1899
  }
1918
- const id = items[i].id, cache = this.get(id), sticky = itemConfigMap[id]?.sticky ?? 0, selectable = itemConfigMap[id]?.selectable ?? true, collapsable = itemConfigMap[id]?.collapsable ?? false, size = dynamicSize ? cache?.[sizeProperty] || typicalItemSize : typicalItemSize;
1900
+ const id = collectionItem[trackBy], cache = this.get(id), sticky = itemConfigMap[id]?.sticky ?? 0, selectable = itemConfigMap[id]?.selectable ?? true, collapsable = itemConfigMap[id]?.collapsable ?? false, size = dynamicSize ? cache?.[sizeProperty] || typicalItemSize : typicalItemSize;
1919
1901
  if (sticky === 1) {
1920
1902
  const isOdd = i % 2 != 0, measures = {
1921
1903
  x: isVertical ? 0 : actualSnippedPosition,
@@ -1939,7 +1921,7 @@ class TrackBox extends CacheMap {
1939
1921
  tabIndex: count,
1940
1922
  zIndex: '1',
1941
1923
  };
1942
- const itemData = items[i];
1924
+ const itemData = collectionItem;
1943
1925
  stickyItem = { index: i, id, measures, data: itemData, config };
1944
1926
  stickyItemIndex = i;
1945
1927
  stickyItemSize = size;
@@ -1950,12 +1932,13 @@ class TrackBox extends CacheMap {
1950
1932
  }
1951
1933
  }
1952
1934
  if (snap) {
1953
- const startIndex = itemsFromStartToScrollEnd + itemsOnDisplayLength - 1;
1935
+ const si = itemsFromStartToScrollEnd + itemsOnDisplayLength - 1, startIndex = si < 0 ? si : si;
1954
1936
  for (let i = Math.min(startIndex, totalLength > 0 ? totalLength - 1 : 0), l = totalLength; i < l; i++) {
1955
- if (!items[i]) {
1937
+ const collectionItem = items[i];
1938
+ if (!collectionItem) {
1956
1939
  continue;
1957
1940
  }
1958
- const id = items[i].id, cache = this.get(id), sticky = itemConfigMap[id]?.sticky ?? 0, selectable = itemConfigMap[id]?.selectable ?? true, collapsable = itemConfigMap[id]?.collapsable ?? false, size = dynamicSize
1941
+ const id = collectionItem[trackBy], cache = this.get(id), sticky = itemConfigMap[id]?.sticky ?? 0, selectable = itemConfigMap[id]?.selectable ?? true, collapsable = itemConfigMap[id]?.collapsable ?? false, size = dynamicSize
1959
1942
  ? cache?.[sizeProperty] || typicalItemSize
1960
1943
  : typicalItemSize;
1961
1944
  if (sticky === 2) {
@@ -1981,7 +1964,7 @@ class TrackBox extends CacheMap {
1981
1964
  tabIndex: items.length,
1982
1965
  zIndex: '1',
1983
1966
  };
1984
- const itemData = items[i];
1967
+ const itemData = collectionItem;
1985
1968
  endStickyItem = { index: i, id, measures, data: itemData, config };
1986
1969
  endStickyItemIndex = i;
1987
1970
  endStickyItemSize = size;
@@ -1995,10 +1978,11 @@ class TrackBox extends CacheMap {
1995
1978
  if (i >= totalLength) {
1996
1979
  break;
1997
1980
  }
1998
- if (!items[i]) {
1981
+ const collectionItem = items[i];
1982
+ if (!collectionItem) {
1999
1983
  continue;
2000
1984
  }
2001
- const id = items[i].id, cache = this.get(id), size = dynamicSize ? cache?.[sizeProperty] || typicalItemSize : typicalItemSize;
1985
+ const id = collectionItem[trackBy], cache = this.get(id), size = dynamicSize ? cache?.[sizeProperty] || typicalItemSize : typicalItemSize;
2002
1986
  if (id !== stickyItem?.id && id !== endStickyItem?.id) {
2003
1987
  const isOdd = i % 2 != 0, sticky = itemConfigMap[id]?.sticky ?? 0, selectable = itemConfigMap[id]?.selectable ?? true, collapsable = itemConfigMap[id]?.collapsable ?? false, snapped = snap && (sticky === 1 && pos <= scrollSize || sticky === 2 && pos >= scrollSize + boundsSize - size), measures = {
2004
1988
  x: isVertical ? sticky === 1 ? 0 : boundsSize - size : pos,
@@ -2026,7 +2010,7 @@ class TrackBox extends CacheMap {
2026
2010
  if (snapped) {
2027
2011
  config.zIndex = '2';
2028
2012
  }
2029
- const itemData = items[i];
2013
+ const itemData = collectionItem;
2030
2014
  const item = { index: i, id, measures, data: itemData, config };
2031
2015
  if (!nextSticky && stickyItemIndex < i && sticky === 1 && (pos <= scrollSize + size + stickyItemSize)) {
2032
2016
  item.measures.x = isVertical ? 0 : snapped ? actualSnippedPosition : pos;
@@ -2036,7 +2020,8 @@ class TrackBox extends CacheMap {
2036
2020
  nextSticky.measures.delta = isVertical ? (item.measures.y - scrollSize) : (item.measures.x - scrollSize);
2037
2021
  nextSticky.config.zIndex = '3';
2038
2022
  }
2039
- else if (!nextEndSticky && endStickyItemIndex > i && sticky === 2 && (pos >= scrollSize + boundsSize - size - endStickyItemSize)) {
2023
+ else if (!nextEndSticky && endStickyItemIndex > i && sticky === 2 &&
2024
+ (pos >= scrollSize + boundsSize - size - endStickyItemSize)) {
2040
2025
  item.measures.x = isVertical ? 0 : snapped ? actualEndSnippedPosition - size : pos;
2041
2026
  item.measures.y = isVertical ? snapped ? actualEndSnippedPosition - size : pos : 0;
2042
2027
  nextEndSticky = item;
@@ -2064,7 +2049,8 @@ class TrackBox extends CacheMap {
2064
2049
  nextSticky.measures.delta = isVertical ? nextSticky.measures.y - scrollSize : nextSticky.measures.x - scrollSize;
2065
2050
  }
2066
2051
  }
2067
- if (nextEndSticky && endStickyItem && nextEndSticky.measures[axis] >= scrollSize + boundsSize - endStickyItemSize - nextEndSticky.measures[sizeProperty]) {
2052
+ if (nextEndSticky && endStickyItem &&
2053
+ (nextEndSticky.measures[axis] >= scrollSize + boundsSize - endStickyItemSize - nextEndSticky.measures[sizeProperty])) {
2068
2054
  if (nextEndSticky.measures[axis] < scrollSize + boundsSize - endStickyItemSize) {
2069
2055
  endStickyItem.measures[axis] = nextEndSticky.measures[axis] + nextEndSticky.measures[sizeProperty];
2070
2056
  endStickyItem.config.snapped = nextEndSticky.config.snapped = false;
@@ -2137,7 +2123,7 @@ const FIREFOX_SCROLLBAR_OVERLAP_SIZE = 12;
2137
2123
  const HORIZONTAL_ALIASES = [Directions.HORIZONTAL, 'horizontal'], VERTICAL_ALIASES = [Directions.VERTICAL, 'vertical'];
2138
2124
  /**
2139
2125
  * Determines the axis membership of a virtual list
2140
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/isDirection.ts
2126
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/is-direction.ts
2141
2127
  * @author Evgenii Grebennikov
2142
2128
  * @email djonnyx@gmail.com
2143
2129
  */
@@ -2151,7 +2137,7 @@ const isDirection = (src, expected) => {
2151
2137
  const NONE_ALIASES = [MethodsForSelecting.NONE, 'none'], SELECT_ALIASES = [MethodsForSelecting.SELECT, 'select'], MULTI_SELECT_ALIASES = [MethodsForSelecting.MULTI_SELECT, 'multi-select'];
2152
2138
  /**
2153
2139
  * Defines the method for selecting list items.
2154
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/isMethodForSelecting.ts
2140
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/is-method-for-selecting.ts
2155
2141
  * @author Evgenii Grebennikov
2156
2142
  * @email djonnyx@gmail.com
2157
2143
  */
@@ -2196,7 +2182,7 @@ const copyValueAsReadonly = (source) => {
2196
2182
  const NORMAL_ALIASES = [CollectionModes.NORMAL, 'normal'], LAZY_ALIASES = [CollectionModes.LAZY, 'lazy'];
2197
2183
  /**
2198
2184
  * Determines the axis membership of a virtual list
2199
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/isCollectionMode.ts
2185
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/is-collection-mode.ts
2200
2186
  * @author Evgenii Grebennikov
2201
2187
  * @email djonnyx@gmail.com
2202
2188
  */
@@ -2313,11 +2299,12 @@ class NgVirtualListComponent {
2313
2299
  let valid = validateArray(v, true);
2314
2300
  if (valid) {
2315
2301
  if (v) {
2302
+ const trackBy = this.trackBy();
2316
2303
  for (let i = 0, l = v.length; i < l; i++) {
2317
2304
  const item = v[i];
2318
2305
  valid = validateObject(item, true);
2319
2306
  if (valid) {
2320
- if (item && !(validateFloat(item.id, true) || validateString(item.id, true))) {
2307
+ if (item && !(validateFloat(item?.[trackBy], true) || validateString(item?.[trackBy], true))) {
2321
2308
  valid = false;
2322
2309
  break;
2323
2310
  }
@@ -2848,11 +2835,11 @@ class NgVirtualListComponent {
2848
2835
  combineLatest([$items, $itemSize]).pipe(takeUntilDestroyed(), map(([items, itemSize]) => ({ items, itemSize })), tap(({ items, itemSize }) => {
2849
2836
  this._trackBox.resetCollection(items, itemSize);
2850
2837
  })).subscribe();
2851
- combineLatest([$items, $collapsedItemIds, $itemConfigMap]).pipe(takeUntilDestroyed(), tap(([items, collapsedIds, itemConfigMap]) => {
2838
+ combineLatest([$items, $collapsedItemIds, $itemConfigMap, $trackBy]).pipe(takeUntilDestroyed(), tap(([items, collapsedIds, itemConfigMap, trackBy]) => {
2852
2839
  const hiddenItems = new CMap();
2853
2840
  let isCollapsed = false;
2854
2841
  for (let i = 0, l = items.length; i < l; i++) {
2855
- const item = items[i], id = item.id, group = (itemConfigMap[id]?.sticky ?? 0) > 0, collapsed = collapsedIds.includes(id);
2842
+ const item = items[i], id = item[trackBy], group = (itemConfigMap[id]?.sticky ?? 0) > 0, collapsed = collapsedIds.includes(id);
2856
2843
  if (group) {
2857
2844
  isCollapsed = collapsed;
2858
2845
  }
@@ -2864,7 +2851,7 @@ class NgVirtualListComponent {
2864
2851
  }
2865
2852
  const actualItems = [];
2866
2853
  for (let i = 0, l = items.length; i < l; i++) {
2867
- const item = items[i], id = item.id;
2854
+ const item = items[i], id = item[trackBy];
2868
2855
  if (hiddenItems.has(id)) {
2869
2856
  continue;
2870
2857
  }
@@ -2996,7 +2983,7 @@ class NgVirtualListComponent {
2996
2983
  this._onResizeHandler();
2997
2984
  })).subscribe();
2998
2985
  const $scrollTo = this.$scrollTo;
2999
- combineLatest([$container, $scrollTo]).pipe(takeUntilDestroyed(), filter(([container]) => container !== undefined), map(([container, event]) => ({ container: container?.nativeElement, event })), switchMap(({ container, event }) => {
2986
+ combineLatest([$container, $trackBy, $scrollTo]).pipe(takeUntilDestroyed(), filter(([container]) => container !== undefined), map(([container, trackBy, event]) => ({ container: container?.nativeElement, trackBy, event })), switchMap(({ container, trackBy, event }) => {
3000
2987
  const cnt = container, { id, behavior = BEHAVIOR_INSTANT, iteration = 0, isLastIteration = false, scrollCalled = false, cb } = event;
3001
2988
  const items = this._actualItems();
3002
2989
  if (items && items.length) {
@@ -3047,7 +3034,7 @@ class NgVirtualListComponent {
3047
3034
  }
3048
3035
  }
3049
3036
  else {
3050
- const index = items.findIndex(item => item.id === id);
3037
+ const index = items.findIndex(item => item[trackBy] === id);
3051
3038
  if (index > -1) {
3052
3039
  const isVertical = this._isVertical, currentScollSize = (isVertical ? cnt.scrollTop : cnt.scrollLeft), scrollSize = index * this.itemSize();
3053
3040
  if (currentScollSize !== scrollSize) {
@@ -3257,7 +3244,7 @@ class NgVirtualListComponent {
3257
3244
  const behavior = options?.behavior ?? BEHAVIOR_INSTANT, iteration = options?.iteration ?? 0;
3258
3245
  validateScrollBehavior(behavior);
3259
3246
  validateIteration(iteration);
3260
- const items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0], id = latItem.id, actualIteration = validateScrollIteration(iteration);
3247
+ const trackBy = this.trackBy(), items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0], id = latItem[trackBy], actualIteration = validateScrollIteration(iteration);
3261
3248
  this._$scrollTo.next({ id, behavior, iteration: actualIteration, isLastIteration: actualIteration === MAX_SCROLL_TO_ITERATIONS, cb });
3262
3249
  }
3263
3250
  ngOnDestroy() {