ng-virtual-list 20.0.21 → 20.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -443,7 +443,7 @@ Inputs
443
443
  | Property | Type | Description |
444
444
  |---|---|---|
445
445
  | id | number | Readonly. Returns the unique identifier of the component. |
446
- | items | [IVirtualListCollection](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/models/collection.model.ts) | Collection of list items. |
446
+ | items | [IVirtualListCollection](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/models/collection.model.ts) | Collection of list items. The collection of elements must be immutable. |
447
447
  | itemSize | number? = 24 | If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element. Ignored if the dynamicSize property is true. |
448
448
  | itemsOffset | number? = 2 | Number of elements outside the scope of visibility. Default value is 2. |
449
449
  | itemRenderer | TemplateRef | Rendering element template. |
@@ -453,6 +453,7 @@ Inputs
453
453
  | direction | [Direction? = 'vertical'](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/enums/direction.ts) | Determines the direction in which elements are placed. Default value is "vertical". |
454
454
  | dynamicSize | boolean? = false | If true then the items in the list can have different sizes and the itemSize property is ignored. If false then the items in the list have a fixed size specified by the itemSize property. The default value is false. |
455
455
  | enabledBufferOptimization | boolean? = true | Enables buffer optimization. Can only be used if items in the collection are not added or updated. |
456
+ | trackBy | string? = 'id' | The name of the property by which tracking is performed. |
456
457
 
457
458
  <br/>
458
459
 
@@ -477,12 +478,12 @@ Methods
477
478
 
478
479
  | Angular version | ng-virtual-list version | git | npm |
479
480
  |--|--|--|--|
480
- | 19.x | 19.1.24 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.1.24](https://www.npmjs.com/package/ng-virtual-list/v/19.1.24) |
481
- | 18.x | 18.0.13 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.0.13](https://www.npmjs.com/package/ng-virtual-list/v/18.0.13) |
482
- | 17.x | 17.0.10 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.0.10](https://www.npmjs.com/package/ng-virtual-list/v/17.0.10) |
483
- | 16.x | 16.0.13 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.0.13](https://www.npmjs.com/package/ng-virtual-list/v/16.0.13) |
484
- | 15.x | 15.0.11 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.0.11](https://www.npmjs.com/package/ng-virtual-list/v/15.0.11) |
485
- | 14.x | 14.0.11 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.0.11](https://www.npmjs.com/package/ng-virtual-list/v/14.0.11) |
481
+ | 19.x | 19.1.27 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.1.27](https://www.npmjs.com/package/ng-virtual-list/v/19.1.27) |
482
+ | 18.x | 18.0.15 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.0.15](https://www.npmjs.com/package/ng-virtual-list/v/18.0.15) |
483
+ | 17.x | 17.0.12 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.0.12](https://www.npmjs.com/package/ng-virtual-list/v/17.0.12) |
484
+ | 16.x | 16.0.15 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.0.15](https://www.npmjs.com/package/ng-virtual-list/v/16.0.15) |
485
+ | 15.x | 15.0.13 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.0.13](https://www.npmjs.com/package/ng-virtual-list/v/15.0.13) |
486
+ | 14.x | 14.0.13 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.0.13](https://www.npmjs.com/package/ng-virtual-list/v/14.0.13) |
486
487
 
487
488
  <br/>
488
489
 
@@ -3,7 +3,7 @@ import { signal, inject, ElementRef, ChangeDetectionStrategy, Component, viewChi
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule } from '@angular/common';
5
5
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
- import { BehaviorSubject, filter, map, tap, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
6
+ import { BehaviorSubject, tap, filter, map, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
7
7
 
8
8
  /**
9
9
  * Axis of the arrangement of virtual list elements.
@@ -79,7 +79,7 @@ class NgVirtualListItemComponent {
79
79
  const data = this._data = v;
80
80
  if (data) {
81
81
  const styles = this._elementRef.nativeElement.style;
82
- styles.zIndex = data.config.sticky;
82
+ styles.zIndex = String(data.config.sticky);
83
83
  if (data.config.snapped) {
84
84
  styles.transform = ZEROS_TRANSLATE_3D;
85
85
  styles.position = POSITION_STICKY;
@@ -93,6 +93,9 @@ class NgVirtualListItemComponent {
93
93
  }
94
94
  this.data.set(v);
95
95
  }
96
+ get item() {
97
+ return this._data;
98
+ }
96
99
  get itemId() {
97
100
  return this._data?.id;
98
101
  }
@@ -101,6 +104,9 @@ class NgVirtualListItemComponent {
101
104
  this.itemRenderer.set(v);
102
105
  }
103
106
  _elementRef = inject((ElementRef));
107
+ get element() {
108
+ return this._elementRef.nativeElement;
109
+ }
104
110
  constructor() {
105
111
  this._id = NgVirtualListItemComponent.__nextId = NgVirtualListItemComponent.__nextId === Number.MAX_SAFE_INTEGER
106
112
  ? 0 : NgVirtualListItemComponent.__nextId + 1;
@@ -221,6 +227,9 @@ class Tracker {
221
227
  return this._trackMap;
222
228
  }
223
229
  _trackingPropertyName;
230
+ set trackingPropertyName(v) {
231
+ this._trackingPropertyName = v;
232
+ }
224
233
  constructor(trackingPropertyName) {
225
234
  this._trackingPropertyName = trackingPropertyName;
226
235
  }
@@ -238,17 +247,17 @@ class Tracker {
238
247
  const diId = this._trackMap[itemTrackingProperty];
239
248
  if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
240
249
  const lastIndex = this._displayObjectIndexMapById[diId], el = components[lastIndex];
241
- this._checkComponentProperty(el?.instance);
242
- const elId = el?.instance?.[itemTrackingProperty];
250
+ const elId = el?.instance?.id;
243
251
  if (el && elId === diId) {
244
252
  const indexByUntrackedItems = untrackedItems.findIndex(v => {
245
- this._checkComponentProperty(v.instance);
246
- return v.instance[itemTrackingProperty] === elId;
253
+ return v.instance.id === elId;
247
254
  });
248
255
  if (indexByUntrackedItems > -1) {
249
- el.instance.item = item;
250
- if (afterComponentSetup !== undefined) {
251
- afterComponentSetup(el.instance, item);
256
+ if (el.instance.item !== item) {
257
+ el.instance.item = item;
258
+ if (afterComponentSetup !== undefined) {
259
+ afterComponentSetup(el.instance, item);
260
+ }
252
261
  }
253
262
  untrackedItems.splice(indexByUntrackedItems, 1);
254
263
  continue;
@@ -260,13 +269,14 @@ class Tracker {
260
269
  if (untrackedItems.length > 0) {
261
270
  const el = untrackedItems.shift(), item = items[i];
262
271
  if (el) {
263
- el.instance.item = item;
264
- if (this._trackMap) {
265
- this._checkComponentProperty(el.instance);
266
- this._trackMap[itemTrackingProperty] = el.instance[itemTrackingProperty];
267
- }
268
- if (afterComponentSetup !== undefined) {
269
- afterComponentSetup(el.instance, item);
272
+ if (el.instance.item !== item) {
273
+ el.instance.item = item;
274
+ if (this._trackMap) {
275
+ this._trackMap[itemTrackingProperty] = el.instance.id;
276
+ }
277
+ if (afterComponentSetup !== undefined) {
278
+ afterComponentSetup(el.instance, item);
279
+ }
270
280
  }
271
281
  }
272
282
  }
@@ -280,23 +290,10 @@ class Tracker {
280
290
  return;
281
291
  }
282
292
  const propertyIdName = this._trackingPropertyName;
283
- this._checkComponentProperty(component);
284
293
  if (this._trackMap && component[propertyIdName] !== undefined) {
285
294
  delete this._trackMap[propertyIdName];
286
295
  }
287
296
  }
288
- _checkComponentProperty(component) {
289
- if (!component) {
290
- return;
291
- }
292
- const propertyIdName = this._trackingPropertyName;
293
- try {
294
- component[propertyIdName];
295
- }
296
- catch (err) {
297
- throw Error(`Property ${propertyIdName} does not exist.`);
298
- }
299
- }
300
297
  dispose() {
301
298
  this._trackMap = null;
302
299
  }
@@ -475,7 +472,11 @@ class CacheMap extends EventEmitter {
475
472
  this.dispatch('change', this.version);
476
473
  }
477
474
  set(id, bounds) {
478
- if (this._map.has(id) && JSON.stringify(this._map.get(id)) === JSON.stringify(bounds)) {
475
+ if (this._map.has(id)) {
476
+ const b = this._map.get(id), bb = bounds;
477
+ if (b.width === bb.width && b.height === bb.height) {
478
+ return this._map;
479
+ }
479
480
  return this._map;
480
481
  }
481
482
  const v = this._map.set(id, bounds);
@@ -502,56 +503,6 @@ class CacheMap extends EventEmitter {
502
503
  }
503
504
  }
504
505
 
505
- /**
506
- * Returns the removed or updated elements of a collection.
507
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/utils/collection.ts
508
- * @author Evgenii Grebennikov
509
- * @email djonnyx@gmail.com
510
- */
511
- const getCollectionRemovedOrUpdatedItems = (previousCollection, currentCollection) => {
512
- const result = { deleted: new Array(), updated: new Array(), added: new Array(), notChanged: new Array() };
513
- if (!currentCollection || currentCollection.length === 0) {
514
- return { deleted: (previousCollection ? [...previousCollection] : []), updated: [], added: [], notChanged: [] };
515
- }
516
- if (!previousCollection || previousCollection.length === 0) {
517
- return { deleted: [], updated: [], added: (currentCollection ? [...currentCollection] : []), notChanged: [] };
518
- }
519
- const collectionDict = {};
520
- for (let i = 0, l = currentCollection.length; i < l; i++) {
521
- const item = currentCollection[i];
522
- if (item) {
523
- collectionDict[item.id] = item;
524
- }
525
- }
526
- const notChangedMap = {}, deletedMap = {}, updatedMap = {};
527
- for (let i = 0, l = previousCollection.length; i < l; i++) {
528
- const item = previousCollection[i], id = item.id;
529
- if (item) {
530
- if (collectionDict.hasOwnProperty(id)) {
531
- if (item === collectionDict[id]) {
532
- result.notChanged.push(item);
533
- notChangedMap[item.id] = item;
534
- continue;
535
- }
536
- else {
537
- result.updated.push(item);
538
- updatedMap[item.id] = item;
539
- continue;
540
- }
541
- }
542
- result.deleted.push(item);
543
- deletedMap[item.id] = item;
544
- }
545
- }
546
- for (let i = 0, l = currentCollection.length; i < l; i++) {
547
- const item = currentCollection[i], id = item.id;
548
- if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) && !notChangedMap.hasOwnProperty(id)) {
549
- result.added.push(item);
550
- }
551
- }
552
- return result;
553
- };
554
-
555
506
  const TRACK_BOX_CHANGE_EVENT_NAME = 'change';
556
507
  var ItemDisplayMethods;
557
508
  (function (ItemDisplayMethods) {
@@ -582,13 +533,22 @@ class TrackBox extends CacheMap {
582
533
  }
583
534
  this._displayComponents = v;
584
535
  }
536
+ /**
537
+ * Set the trackBy property
538
+ */
539
+ set trackingPropertyName(v) {
540
+ this._tracker.trackingPropertyName = v;
541
+ }
585
542
  constructor(trackingPropertyName) {
586
543
  super();
587
544
  this._tracker = new Tracker(trackingPropertyName);
588
545
  }
589
546
  set(id, bounds) {
590
- if (this._map.has(id) && JSON.stringify(this._map.get(id)) === JSON.stringify(bounds)) {
591
- return this._map;
547
+ if (this._map.has(id)) {
548
+ const b = this._map.get(id);
549
+ if (b?.width === bounds.width && b.height === bounds.height) {
550
+ return this._map;
551
+ }
592
552
  }
593
553
  const v = this._map.set(id, bounds);
594
554
  this.bumpVersion();
@@ -611,31 +571,69 @@ class TrackBox extends CacheMap {
611
571
  console.warn('Attention! The collection must be immutable.');
612
572
  return;
613
573
  }
614
- const { deleted, updated, added } = getCollectionRemovedOrUpdatedItems(this._previousCollection, currentCollection);
615
- this.clearCache(deleted, updated, added, itemSize);
574
+ this.updateCache(this._previousCollection, currentCollection, itemSize);
616
575
  this._previousCollection = currentCollection;
617
576
  }
618
577
  /**
619
- * Clears the cache of items from the list
578
+ * Update the cache of items from the list
620
579
  */
621
- clearCache(deleted, updated, added, itemSize) {
622
- if (deleted) {
623
- for (let i = 0, l = deleted.length; i < l; i++) {
624
- const item = deleted[i], id = item.id;
625
- if (this._map.has(id)) {
626
- this._map.delete(id);
580
+ updateCache(previousCollection, currentCollection, itemSize) {
581
+ if (!currentCollection || currentCollection.length === 0) {
582
+ if (previousCollection) {
583
+ // deleted
584
+ for (let i = 0, l = previousCollection.length; i < l; i++) {
585
+ const item = previousCollection[i], id = item.id;
586
+ if (this._map.has(id)) {
587
+ this._map.delete(id);
588
+ }
589
+ }
590
+ }
591
+ return;
592
+ }
593
+ if (!previousCollection || previousCollection.length === 0) {
594
+ if (currentCollection) {
595
+ // added
596
+ for (let i = 0, l = currentCollection.length; i < l; i++) {
597
+ const item = currentCollection[i], id = item.id;
598
+ this._map.set(id, { x: 0, y: 0, width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
627
599
  }
628
600
  }
601
+ return;
629
602
  }
630
- if (updated) {
631
- for (let i = 0, l = updated.length; i < l; i++) {
632
- const item = updated[i], id = item.id;
633
- this._map.set(id, { ...(this._map.get(id) || { x: 0, y: 0, width: itemSize, height: itemSize }), method: ItemDisplayMethods.UPDATE });
603
+ const collectionDict = {};
604
+ for (let i = 0, l = currentCollection.length; i < l; i++) {
605
+ const item = currentCollection[i];
606
+ if (item) {
607
+ collectionDict[item.id] = item;
608
+ }
609
+ }
610
+ const notChangedMap = {}, deletedMap = {}, updatedMap = {};
611
+ for (let i = 0, l = previousCollection.length; i < l; i++) {
612
+ const item = previousCollection[i], id = item.id;
613
+ if (item) {
614
+ if (collectionDict.hasOwnProperty(id)) {
615
+ if (item === collectionDict[id]) {
616
+ // not changed
617
+ notChangedMap[item.id] = item;
618
+ this._map.set(id, { ...(this._map.get(id) || { x: 0, y: 0, width: itemSize, height: itemSize }), method: ItemDisplayMethods.NOT_CHANGED });
619
+ continue;
620
+ }
621
+ else {
622
+ // updated
623
+ updatedMap[item.id] = item;
624
+ this._map.set(id, { ...(this._map.get(id) || { x: 0, y: 0, width: itemSize, height: itemSize }), method: ItemDisplayMethods.UPDATE });
625
+ continue;
626
+ }
627
+ }
628
+ // deleted
629
+ deletedMap[item.id] = item;
630
+ this._map.delete(id);
634
631
  }
635
632
  }
636
- if (added) {
637
- for (let i = 0, l = added.length; i < l; i++) {
638
- const item = added[i], id = item.id;
633
+ for (let i = 0, l = currentCollection.length; i < l; i++) {
634
+ const item = currentCollection[i], id = item.id;
635
+ if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) && !notChangedMap.hasOwnProperty(id)) {
636
+ // added
639
637
  this._map.set(id, { x: 0, y: 0, width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
640
638
  }
641
639
  }
@@ -907,6 +905,10 @@ class TrackBox extends CacheMap {
907
905
  this.clearScrollDirectionCache();
908
906
  }
909
907
  }
908
+ changes() {
909
+ this.bumpVersion();
910
+ this.fireChange();
911
+ }
910
912
  generateDisplayCollection(items, stickyMap, metrics) {
911
913
  const { normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
912
914
  if (items.length) {
@@ -1175,13 +1177,14 @@ class NgVirtualListComponent {
1175
1177
  _onResizeHandler = () => {
1176
1178
  this._bounds.set(this._container()?.nativeElement?.getBoundingClientRect() ?? null);
1177
1179
  };
1180
+ _scrolls = new Map();
1178
1181
  _onScrollHandler = (e) => {
1179
1182
  this._isScrollingDebounces.dispose();
1180
1183
  this.clearScrollToRepeatExecutionTimeout();
1181
1184
  const container = this._container()?.nativeElement;
1182
1185
  if (container) {
1183
1186
  const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
1184
- let actualScrollSize = scrollSize, isImmediateScroll = false;
1187
+ let actualScrollSize = scrollSize, isScrollIUmmediate = false;
1185
1188
  if (dynamicSize && delta !== 0) {
1186
1189
  actualScrollSize = scrollSize + delta;
1187
1190
  const params = {
@@ -1190,48 +1193,45 @@ class NgVirtualListComponent {
1190
1193
  };
1191
1194
  const container = this._container();
1192
1195
  if (container) {
1193
- isImmediateScroll = true;
1196
+ isScrollIUmmediate = true;
1194
1197
  this.scrollImmediately(container, params);
1195
- this._trackBox.clearDelta();
1196
1198
  }
1197
1199
  }
1198
- this._scrollSize.set(actualScrollSize);
1200
+ if (!isScrollIUmmediate) {
1201
+ this._scrollSize.set(actualScrollSize);
1202
+ }
1199
1203
  }
1200
1204
  };
1201
1205
  scrollImmediately(container, params, cb) {
1202
- this.clearScrollImmediately();
1206
+ if (this._scrolls.size > 0) {
1207
+ container.nativeElement.scrollTo(params);
1208
+ return;
1209
+ }
1210
+ this._trackBox.clearDelta();
1203
1211
  container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1212
+ container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1204
1213
  const handler = () => {
1214
+ const container = this._container();
1205
1215
  if (container) {
1206
1216
  container.nativeElement.removeEventListener(SCROLL_END, handler);
1207
- container.nativeElement.scroll(params);
1217
+ this._scrolls.delete(handler);
1218
+ container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1219
+ container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
1208
1220
  if (cb !== undefined) {
1209
1221
  cb();
1210
1222
  }
1211
- container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
1212
1223
  }
1213
1224
  };
1225
+ this._scrolls.set(handler, true);
1214
1226
  container.nativeElement.addEventListener(SCROLL_END, handler);
1215
- container.nativeElement.scroll(params);
1216
- this._scrollImmediatelyHandler = handler;
1217
- }
1218
- _scrollImmediatelyHandler = undefined;
1219
- clearScrollImmediately() {
1220
- if (this._scrollImmediatelyHandler === undefined) {
1221
- return;
1222
- }
1223
- const container = this._container();
1224
- if (container) {
1225
- container.nativeElement.removeEventListener(SCROLL_END, this._scrollImmediatelyHandler);
1226
- }
1227
+ container.nativeElement.scrollTo(params);
1227
1228
  }
1228
1229
  _onScrollEndHandler = (e) => {
1229
1230
  const container = this._container();
1230
1231
  if (container) {
1231
- this._trackBox.clearDelta();
1232
- this._trackBox.clearDeltaDirection();
1233
1232
  const itemSize = this.itemSize(), snapToItem = this.snapToItem(), dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
1234
1233
  let actualScrollSize = scrollSize;
1234
+ this._trackBox.clearDeltaDirection();
1235
1235
  if (dynamicSize) {
1236
1236
  actualScrollSize = scrollSize + delta;
1237
1237
  if (snapToItem) {
@@ -1239,15 +1239,17 @@ class NgVirtualListComponent {
1239
1239
  if (targetItem) {
1240
1240
  this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1241
1241
  }
1242
+ this._trackBox.clearDelta();
1242
1243
  }
1243
1244
  else if (scrollSize !== actualScrollSize) {
1244
1245
  const params = {
1245
1246
  [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1246
1247
  behavior: BEHAVIOR_INSTANT
1247
1248
  };
1248
- this._scrollSize.set(actualScrollSize);
1249
- container.nativeElement.scroll(params);
1250
- return;
1249
+ const container = this._container();
1250
+ if (container) {
1251
+ this.scrollImmediately(container, params);
1252
+ }
1251
1253
  }
1252
1254
  }
1253
1255
  else {
@@ -1258,19 +1260,25 @@ class NgVirtualListComponent {
1258
1260
  [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1259
1261
  behavior: BEHAVIOR_INSTANT
1260
1262
  };
1261
- container.nativeElement.scroll(params);
1263
+ const container = this._container();
1264
+ if (container) {
1265
+ this.scrollImmediately(container, params);
1266
+ }
1262
1267
  }
1263
1268
  }
1264
- this._scrollSize.set(actualScrollSize);
1265
1269
  }
1266
1270
  };
1267
1271
  _elementRef = inject((ElementRef));
1268
1272
  _initialized;
1269
1273
  $initialized;
1274
+ /**
1275
+ * The name of the property by which tracking is performed
1276
+ */
1277
+ trackBy = input(TRACK_BY_PROPERTY_NAME);
1270
1278
  /**
1271
1279
  * Dictionary of element sizes by their id
1272
1280
  */
1273
- _trackBox = new TrackBox(TRACK_BY_PROPERTY_NAME);
1281
+ _trackBox = new TrackBox(this.trackBy());
1274
1282
  _onTrackBoxChangeHandler = (v) => {
1275
1283
  this._$cacheVersion.next(v);
1276
1284
  };
@@ -1283,6 +1291,10 @@ class NgVirtualListComponent {
1283
1291
  this._initialized = signal(false);
1284
1292
  this.$initialized = toObservable(this._initialized);
1285
1293
  this._trackBox.displayComponents = this._displayComponents;
1294
+ const $trackBy = toObservable(this.trackBy);
1295
+ $trackBy.pipe(takeUntilDestroyed(), tap(v => {
1296
+ this._trackBox.trackingPropertyName = v;
1297
+ })).subscribe();
1286
1298
  const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = toObservable(this.itemsOffset).pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = toObservable(this.stickyMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $snapToItem = toObservable(this.snapToItem), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $cacheVersion = this.$cacheVersion;
1287
1299
  $isVertical.pipe(takeUntilDestroyed(), tap(v => {
1288
1300
  this._isVertical = v;
@@ -1309,17 +1321,20 @@ class NgVirtualListComponent {
1309
1321
  this.tracking();
1310
1322
  const container = this._container();
1311
1323
  if (!this.isScrolling && dynamicSize && container) {
1312
- actualScrollSize = scrollSize + delta;
1324
+ actualScrollSize = scrollSize + this._trackBox.delta;
1313
1325
  if (snapToItem) {
1314
- // etc
1326
+ const items = this.items(), isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
1327
+ if (targetItem) {
1328
+ this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
1329
+ }
1315
1330
  }
1316
1331
  else if (scrollSize !== actualScrollSize) {
1317
1332
  const params = {
1318
1333
  [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
1319
1334
  behavior: BEHAVIOR_INSTANT
1320
1335
  };
1321
- this.scrollImmediately(container, params);
1322
1336
  this._trackBox.clearDelta();
1337
+ container.nativeElement.scrollTo(params);
1323
1338
  }
1324
1339
  }
1325
1340
  return of(displayItems);
@@ -1347,6 +1362,9 @@ class NgVirtualListComponent {
1347
1362
  const dir = d || this.direction();
1348
1363
  return isDirection(dir, Directions.VERTICAL);
1349
1364
  }
1365
+ _componentsResizeObserver = new ResizeObserver(() => {
1366
+ this._trackBox.changes();
1367
+ });
1350
1368
  createDisplayComponentsIfNeed(displayItems) {
1351
1369
  if (!displayItems || !this._listContainerRef) {
1352
1370
  this._trackBox.setDisplayObjectIndexMapById({});
@@ -1358,15 +1376,19 @@ class NgVirtualListComponent {
1358
1376
  if (_listContainerRef) {
1359
1377
  const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
1360
1378
  this._displayComponents.push(comp);
1379
+ this._componentsResizeObserver.observe(comp.instance.element);
1361
1380
  }
1362
1381
  }
1363
1382
  const maxLength = displayItems.length;
1364
1383
  while (this._displayComponents.length > maxLength) {
1365
1384
  const comp = this._displayComponents.pop();
1366
- comp?.destroy();
1367
- const id = comp?.instance.item?.id;
1368
- if (id !== undefined) {
1369
- this._trackBox.untrackComponentByIdProperty(comp?.instance);
1385
+ if (comp) {
1386
+ this._componentsResizeObserver.unobserve(comp.instance.element);
1387
+ comp?.destroy();
1388
+ const id = comp?.instance.item?.id;
1389
+ if (id !== undefined) {
1390
+ this._trackBox.untrackComponentByIdProperty(comp?.instance);
1391
+ }
1370
1392
  }
1371
1393
  }
1372
1394
  this.resetRenderers();
@@ -1455,12 +1477,12 @@ class NgVirtualListComponent {
1455
1477
  };
1456
1478
  container.nativeElement.addEventListener(SCROLL_END, handler);
1457
1479
  }
1458
- container.nativeElement.scroll(params);
1480
+ container.nativeElement.scrollTo(params);
1459
1481
  }
1460
1482
  else {
1461
1483
  const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize();
1462
1484
  const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
1463
- container.nativeElement.scroll(params);
1485
+ container.nativeElement.scrollTo(params);
1464
1486
  }
1465
1487
  }
1466
1488
  }
@@ -1509,6 +1531,15 @@ class NgVirtualListComponent {
1509
1531
  this._onResizeHandler();
1510
1532
  }
1511
1533
  }
1534
+ clearScrollImmediately() {
1535
+ const container = this._container();
1536
+ if (container) {
1537
+ this._scrolls.forEach((_, handler) => {
1538
+ container.nativeElement.removeEventListener(SCROLL_END, handler);
1539
+ });
1540
+ }
1541
+ this._scrolls.clear();
1542
+ }
1512
1543
  ngOnDestroy() {
1513
1544
  this.clearScrollToRepeatExecutionTimeout();
1514
1545
  if (this._trackBox) {
@@ -1517,14 +1548,18 @@ class NgVirtualListComponent {
1517
1548
  if (this._isScrollingDebounces) {
1518
1549
  this._isScrollingDebounces.dispose();
1519
1550
  }
1551
+ this.clearScrollImmediately();
1520
1552
  const containerEl = this._container();
1521
1553
  if (containerEl) {
1522
1554
  containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
1523
1555
  containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
1524
1556
  containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
1525
1557
  containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
1558
+ if (this._componentsResizeObserver) {
1559
+ this._componentsResizeObserver.disconnect();
1560
+ }
1526
1561
  if (this._resizeObserver) {
1527
- this._resizeObserver.unobserve(containerEl.nativeElement);
1562
+ this._resizeObserver.disconnect();
1528
1563
  }
1529
1564
  }
1530
1565
  if (this._displayComponents) {
@@ -1535,7 +1570,7 @@ class NgVirtualListComponent {
1535
1570
  }
1536
1571
  }
1537
1572
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1538
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, snapToItem: { classPropertyName: "snapToItem", publicName: "snapToItem", isSignal: true, isRequired: false, transformFunction: null }, enabledBufferOptimization: { classPropertyName: "enabledBufferOptimization", publicName: "enabledBufferOptimization", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, stickyMap: { classPropertyName: "stickyMap", publicName: "stickyMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
1573
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, snapToItem: { classPropertyName: "snapToItem", publicName: "snapToItem", isSignal: true, isRequired: false, transformFunction: null }, enabledBufferOptimization: { classPropertyName: "enabledBufferOptimization", publicName: "enabledBufferOptimization", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, stickyMap: { classPropertyName: "stickyMap", publicName: "stickyMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
1539
1574
  }
1540
1575
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, decorators: [{
1541
1576
  type: Component,