ng-virtual-list 19.1.24 → 19.1.26
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 +2 -2
- package/fesm2022/ng-virtual-list.mjs +155 -119
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/components/ng-virtual-list-item.component.d.ts +2 -0
- package/lib/ng-virtual-list.component.d.ts +6 -1
- package/lib/utils/trackBox.d.ts +8 -3
- package/lib/utils/tracker.d.ts +1 -1
- package/package.json +1 -1
- package/lib/utils/collection.d.ts +0 -19
package/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# NgVirtualList
|
|
2
2
|
Maximum performance for extremely large lists.
|
|
3
|
-
10x more resource intensive than standard lists
|
|
4
3
|
|
|
5
4
|
Angular version 19.X.X.
|
|
6
5
|
|
|
@@ -443,7 +442,7 @@ Inputs
|
|
|
443
442
|
| Property | Type | Description |
|
|
444
443
|
|---|---|---|
|
|
445
444
|
| id | number | Readonly. Returns the unique identifier of the component. |
|
|
446
|
-
| items | [IVirtualListCollection](https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/models/collection.model.ts) | Collection of list items. |
|
|
445
|
+
| items | [IVirtualListCollection](https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/models/collection.model.ts) | Collection of list items. The collection of elements must be immutable. |
|
|
447
446
|
| 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
447
|
| itemsOffset | number? = 2 | Number of elements outside the scope of visibility. Default value is 2. |
|
|
449
448
|
| itemRenderer | TemplateRef | Rendering element template. |
|
|
@@ -453,6 +452,7 @@ Inputs
|
|
|
453
452
|
| direction | [Direction? = 'vertical'](https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/enums/direction.ts) | Determines the direction in which elements are placed. Default value is "vertical". |
|
|
454
453
|
| dynamicSize | boolean? = false | If true then the items in the list can have different sizes and the itemSize property is ignored. If false then the items in the list have a fixed size specified by the itemSize property. The default value is false. |
|
|
455
454
|
| enabledBufferOptimization | boolean? = true | Enables buffer optimization. Can only be used if items in the collection are not added or updated. |
|
|
455
|
+
| trackBy | string? = 'id' | The name of the property by which tracking is performed. |
|
|
456
456
|
|
|
457
457
|
<br/>
|
|
458
458
|
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
246
|
-
return v.instance[itemTrackingProperty] === elId;
|
|
253
|
+
return v.instance.id === elId;
|
|
247
254
|
});
|
|
248
255
|
if (indexByUntrackedItems > -1) {
|
|
249
|
-
el.instance.item
|
|
250
|
-
|
|
251
|
-
afterComponentSetup
|
|
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
|
|
264
|
-
|
|
265
|
-
this.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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)
|
|
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/19.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)
|
|
591
|
-
|
|
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
|
-
|
|
615
|
-
this.clearCache(deleted, updated, added, itemSize);
|
|
574
|
+
this.updateCache(this._previousCollection, currentCollection, itemSize);
|
|
616
575
|
this._previousCollection = currentCollection;
|
|
617
576
|
}
|
|
618
577
|
/**
|
|
619
|
-
*
|
|
578
|
+
* Update the cache of items from the list
|
|
620
579
|
*/
|
|
621
|
-
|
|
622
|
-
if (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
+
}
|
|
627
589
|
}
|
|
628
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 });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
629
602
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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) {
|
|
@@ -1181,7 +1183,7 @@ class NgVirtualListComponent {
|
|
|
1181
1183
|
const container = this._container()?.nativeElement;
|
|
1182
1184
|
if (container) {
|
|
1183
1185
|
const dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft);
|
|
1184
|
-
let actualScrollSize = scrollSize,
|
|
1186
|
+
let actualScrollSize = scrollSize, isScrollIUmmediate = false;
|
|
1185
1187
|
if (dynamicSize && delta !== 0) {
|
|
1186
1188
|
actualScrollSize = scrollSize + delta;
|
|
1187
1189
|
const params = {
|
|
@@ -1190,12 +1192,16 @@ class NgVirtualListComponent {
|
|
|
1190
1192
|
};
|
|
1191
1193
|
const container = this._container();
|
|
1192
1194
|
if (container) {
|
|
1193
|
-
|
|
1194
|
-
this.scrollImmediately(container, params)
|
|
1195
|
-
|
|
1195
|
+
isScrollIUmmediate = true;
|
|
1196
|
+
this.scrollImmediately(container, params, () => {
|
|
1197
|
+
const scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1198
|
+
this._scrollSize.set(scrollSize);
|
|
1199
|
+
});
|
|
1196
1200
|
}
|
|
1197
1201
|
}
|
|
1198
|
-
|
|
1202
|
+
if (!isScrollIUmmediate) {
|
|
1203
|
+
this._scrollSize.set(actualScrollSize);
|
|
1204
|
+
}
|
|
1199
1205
|
}
|
|
1200
1206
|
};
|
|
1201
1207
|
scrollImmediately(container, params, cb) {
|
|
@@ -1204,7 +1210,6 @@ class NgVirtualListComponent {
|
|
|
1204
1210
|
const handler = () => {
|
|
1205
1211
|
if (container) {
|
|
1206
1212
|
container.nativeElement.removeEventListener(SCROLL_END, handler);
|
|
1207
|
-
container.nativeElement.scroll(params);
|
|
1208
1213
|
if (cb !== undefined) {
|
|
1209
1214
|
cb();
|
|
1210
1215
|
}
|
|
@@ -1213,6 +1218,7 @@ class NgVirtualListComponent {
|
|
|
1213
1218
|
};
|
|
1214
1219
|
container.nativeElement.addEventListener(SCROLL_END, handler);
|
|
1215
1220
|
container.nativeElement.scroll(params);
|
|
1221
|
+
this._trackBox.clearDelta();
|
|
1216
1222
|
this._scrollImmediatelyHandler = handler;
|
|
1217
1223
|
}
|
|
1218
1224
|
_scrollImmediatelyHandler = undefined;
|
|
@@ -1228,10 +1234,10 @@ class NgVirtualListComponent {
|
|
|
1228
1234
|
_onScrollEndHandler = (e) => {
|
|
1229
1235
|
const container = this._container();
|
|
1230
1236
|
if (container) {
|
|
1231
|
-
this._trackBox.clearDelta();
|
|
1232
|
-
this._trackBox.clearDeltaDirection();
|
|
1233
1237
|
const itemSize = this.itemSize(), snapToItem = this.snapToItem(), dynamicSize = this.dynamicSize(), delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1234
1238
|
let actualScrollSize = scrollSize;
|
|
1239
|
+
this._trackBox.clearDelta();
|
|
1240
|
+
this._trackBox.clearDeltaDirection();
|
|
1235
1241
|
if (dynamicSize) {
|
|
1236
1242
|
actualScrollSize = scrollSize + delta;
|
|
1237
1243
|
if (snapToItem) {
|
|
@@ -1245,8 +1251,13 @@ class NgVirtualListComponent {
|
|
|
1245
1251
|
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1246
1252
|
behavior: BEHAVIOR_INSTANT
|
|
1247
1253
|
};
|
|
1248
|
-
this.
|
|
1249
|
-
container
|
|
1254
|
+
const container = this._container();
|
|
1255
|
+
if (container) {
|
|
1256
|
+
this.scrollImmediately(container, params, () => {
|
|
1257
|
+
const scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1258
|
+
this._scrollSize.set(scrollSize);
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1250
1261
|
return;
|
|
1251
1262
|
}
|
|
1252
1263
|
}
|
|
@@ -1258,19 +1269,28 @@ class NgVirtualListComponent {
|
|
|
1258
1269
|
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1259
1270
|
behavior: BEHAVIOR_INSTANT
|
|
1260
1271
|
};
|
|
1261
|
-
container.
|
|
1272
|
+
const container = this._container();
|
|
1273
|
+
if (container) {
|
|
1274
|
+
this.scrollImmediately(container, params, () => {
|
|
1275
|
+
const scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
1276
|
+
this._scrollSize.set(scrollSize);
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1262
1279
|
}
|
|
1263
1280
|
}
|
|
1264
|
-
this._scrollSize.set(actualScrollSize);
|
|
1265
1281
|
}
|
|
1266
1282
|
};
|
|
1267
1283
|
_elementRef = inject((ElementRef));
|
|
1268
1284
|
_initialized;
|
|
1269
1285
|
$initialized;
|
|
1286
|
+
/**
|
|
1287
|
+
* The name of the property by which tracking is performed
|
|
1288
|
+
*/
|
|
1289
|
+
trackBy = input(TRACK_BY_PROPERTY_NAME);
|
|
1270
1290
|
/**
|
|
1271
1291
|
* Dictionary of element sizes by their id
|
|
1272
1292
|
*/
|
|
1273
|
-
_trackBox = new TrackBox(
|
|
1293
|
+
_trackBox = new TrackBox(this.trackBy());
|
|
1274
1294
|
_onTrackBoxChangeHandler = (v) => {
|
|
1275
1295
|
this._$cacheVersion.next(v);
|
|
1276
1296
|
};
|
|
@@ -1283,6 +1303,10 @@ class NgVirtualListComponent {
|
|
|
1283
1303
|
this._initialized = signal(false);
|
|
1284
1304
|
this.$initialized = toObservable(this._initialized);
|
|
1285
1305
|
this._trackBox.displayComponents = this._displayComponents;
|
|
1306
|
+
const $trackBy = toObservable(this.trackBy);
|
|
1307
|
+
$trackBy.pipe(takeUntilDestroyed(), tap(v => {
|
|
1308
|
+
this._trackBox.trackingPropertyName = v;
|
|
1309
|
+
})).subscribe();
|
|
1286
1310
|
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
1311
|
$isVertical.pipe(takeUntilDestroyed(), tap(v => {
|
|
1288
1312
|
this._isVertical = v;
|
|
@@ -1311,7 +1335,10 @@ class NgVirtualListComponent {
|
|
|
1311
1335
|
if (!this.isScrolling && dynamicSize && container) {
|
|
1312
1336
|
actualScrollSize = scrollSize + delta;
|
|
1313
1337
|
if (snapToItem) {
|
|
1314
|
-
|
|
1338
|
+
const items = this.items(), isVertical = this._isVertical, targetItem = this._trackBox.getNearestItem(actualScrollSize, items, itemSize, isVertical);
|
|
1339
|
+
if (targetItem) {
|
|
1340
|
+
this.scrollTo(targetItem.id, BEHAVIOR_INSTANT);
|
|
1341
|
+
}
|
|
1315
1342
|
}
|
|
1316
1343
|
else if (scrollSize !== actualScrollSize) {
|
|
1317
1344
|
const params = {
|
|
@@ -1319,7 +1346,6 @@ class NgVirtualListComponent {
|
|
|
1319
1346
|
behavior: BEHAVIOR_INSTANT
|
|
1320
1347
|
};
|
|
1321
1348
|
this.scrollImmediately(container, params);
|
|
1322
|
-
this._trackBox.clearDelta();
|
|
1323
1349
|
}
|
|
1324
1350
|
}
|
|
1325
1351
|
return of(displayItems);
|
|
@@ -1347,6 +1373,9 @@ class NgVirtualListComponent {
|
|
|
1347
1373
|
const dir = d || this.direction();
|
|
1348
1374
|
return isDirection(dir, Directions.VERTICAL);
|
|
1349
1375
|
}
|
|
1376
|
+
_componentsResizeObserver = new ResizeObserver(() => {
|
|
1377
|
+
this._trackBox.changes();
|
|
1378
|
+
});
|
|
1350
1379
|
createDisplayComponentsIfNeed(displayItems) {
|
|
1351
1380
|
if (!displayItems || !this._listContainerRef) {
|
|
1352
1381
|
this._trackBox.setDisplayObjectIndexMapById({});
|
|
@@ -1358,15 +1387,19 @@ class NgVirtualListComponent {
|
|
|
1358
1387
|
if (_listContainerRef) {
|
|
1359
1388
|
const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
|
|
1360
1389
|
this._displayComponents.push(comp);
|
|
1390
|
+
this._componentsResizeObserver.observe(comp.instance.element);
|
|
1361
1391
|
}
|
|
1362
1392
|
}
|
|
1363
1393
|
const maxLength = displayItems.length;
|
|
1364
1394
|
while (this._displayComponents.length > maxLength) {
|
|
1365
1395
|
const comp = this._displayComponents.pop();
|
|
1366
|
-
comp
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1396
|
+
if (comp) {
|
|
1397
|
+
this._componentsResizeObserver.unobserve(comp.instance.element);
|
|
1398
|
+
comp?.destroy();
|
|
1399
|
+
const id = comp?.instance.item?.id;
|
|
1400
|
+
if (id !== undefined) {
|
|
1401
|
+
this._trackBox.untrackComponentByIdProperty(comp?.instance);
|
|
1402
|
+
}
|
|
1370
1403
|
}
|
|
1371
1404
|
}
|
|
1372
1405
|
this.resetRenderers();
|
|
@@ -1523,8 +1556,11 @@ class NgVirtualListComponent {
|
|
|
1523
1556
|
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1524
1557
|
containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1525
1558
|
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1559
|
+
if (this._componentsResizeObserver) {
|
|
1560
|
+
this._componentsResizeObserver.disconnect();
|
|
1561
|
+
}
|
|
1526
1562
|
if (this._resizeObserver) {
|
|
1527
|
-
this._resizeObserver.
|
|
1563
|
+
this._resizeObserver.disconnect();
|
|
1528
1564
|
}
|
|
1529
1565
|
}
|
|
1530
1566
|
if (this._displayComponents) {
|
|
@@ -1535,7 +1571,7 @@ class NgVirtualListComponent {
|
|
|
1535
1571
|
}
|
|
1536
1572
|
}
|
|
1537
1573
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1538
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", 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 });
|
|
1574
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", 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
1575
|
}
|
|
1540
1576
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
1541
1577
|
type: Component,
|