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 +8 -7
- package/fesm2022/ng-virtual-list.mjs +171 -136
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/index.d.ts +10 -3
- package/package.json +1 -1
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.
|
|
481
|
-
| 18.x | 18.0.
|
|
482
|
-
| 17.x | 17.0.
|
|
483
|
-
| 16.x | 16.0.
|
|
484
|
-
| 15.x | 15.0.
|
|
485
|
-
| 14.x | 14.0.
|
|
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,
|
|
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/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)
|
|
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
|
+
}
|
|
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
|
-
|
|
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) {
|
|
@@ -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,
|
|
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
|
-
|
|
1196
|
+
isScrollIUmmediate = true;
|
|
1194
1197
|
this.scrollImmediately(container, params);
|
|
1195
|
-
this._trackBox.clearDelta();
|
|
1196
1198
|
}
|
|
1197
1199
|
}
|
|
1198
|
-
|
|
1200
|
+
if (!isScrollIUmmediate) {
|
|
1201
|
+
this._scrollSize.set(actualScrollSize);
|
|
1202
|
+
}
|
|
1199
1203
|
}
|
|
1200
1204
|
};
|
|
1201
1205
|
scrollImmediately(container, params, cb) {
|
|
1202
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1249
|
-
container
|
|
1250
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|