ng-virtual-list 20.1.4 → 20.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,10 +25,10 @@ npm i ng-virtual-list
25
25
 
26
26
  Template:
27
27
  ```html
28
- <ng-virtual-list class="list" direction="hotizontal" [items]="horizontalItems" [itemsOffset]="50"
29
- [itemRenderer]="hotizontalItemRenderer" [itemSize]="64"></ng-virtual-list>
28
+ <ng-virtual-list class="list" direction="horizontal" [items]="horizontalItems" [itemsOffset]="50"
29
+ [itemRenderer]="horizontalItemRenderer" [itemSize]="64"></ng-virtual-list>
30
30
 
31
- <ng-template #hotizontalItemRenderer let-data="data">
31
+ <ng-template #horizontalItemRenderer let-data="data">
32
32
  @if (data) {
33
33
  <div class="list__h-container" (click)="onItemClick(data)">
34
34
  <span>{{data.name}}</span>
@@ -63,7 +63,7 @@ export class AppComponent {
63
63
 
64
64
  Template:
65
65
  ```html
66
- <ng-virtual-list class="list" direction="hotizontal" [items]="horizontalGroupItems" [itemRenderer]="horizontalGroupItemRenderer"
66
+ <ng-virtual-list class="list" direction="horizontal" [items]="horizontalGroupItems" [itemRenderer]="horizontalGroupItemRenderer"
67
67
  [itemsOffset]="50" [stickyMap]="horizontalGroupItemsStickyMap" [itemSize]="54" [snap]="true"></ng-virtual-list>
68
68
 
69
69
  <ng-template #horizontalGroupItemRenderer let-data="data">
@@ -455,7 +455,7 @@ Inputs
455
455
  | 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. |
456
456
  | itemsOffset | number? = 2 | Number of elements outside the scope of visibility. Default value is 2. |
457
457
  | itemRenderer | TemplateRef | Rendering element template. |
458
- | stickyMap | [IVirtualListStickyMap?](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/models/sticky-map.model.ts) | Dictionary zIndex by id of the list element. If the value is not set or equal to 0, then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element. |
458
+ | stickyMap | [IVirtualListStickyMap?](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/models/sticky-map.model.ts) | Dictionary zIndex by id of the list element. If the value is not set or equal to 0, then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element. 1 - position start, 2 - position end. |
459
459
  | snap | boolean? = false | Determines whether elements will snap. Default value is "false". |
460
460
  | snappingMethod | [SnappingMethod? = 'normal'](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/enums/snapping-method.ts) | Snapping method. 'normal' - Normal group rendering. 'advanced' - The group is rendered on a transparent background. List items below the group are not rendered. |
461
461
  | 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". |
@@ -488,12 +488,12 @@ Methods
488
488
 
489
489
  | Angular version | ng-virtual-list version | git | npm |
490
490
  |--|--|--|--|
491
- | 19.x | 19.2.12 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.2.12](https://www.npmjs.com/package/ng-virtual-list/v/19.2.12) |
492
- | 18.x | 18.1.3 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.1.3](https://www.npmjs.com/package/ng-virtual-list/v/18.1.3) |
493
- | 17.x | 17.1.3 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.1.3](https://www.npmjs.com/package/ng-virtual-list/v/17.1.3) |
494
- | 16.x | 16.1.4 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.1.4](https://www.npmjs.com/package/ng-virtual-list/v/16.1.4) |
495
- | 15.x | 15.1.3 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.1.3](https://www.npmjs.com/package/ng-virtual-list/v/15.1.3) |
496
- | 14.x | 14.1.5 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.1.5](https://www.npmjs.com/package/ng-virtual-list/v/14.1.5) |
491
+ | 19.x | 19.3.3 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.3.3](https://www.npmjs.com/package/ng-virtual-list/v/19.3.3) |
492
+ | 18.x | 18.3.0 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.3.0](https://www.npmjs.com/package/ng-virtual-list/v/18.3.0) |
493
+ | 17.x | 17.3.0 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.3.0](https://www.npmjs.com/package/ng-virtual-list/v/17.3.0) |
494
+ | 16.x | 16.3.0 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.3.0](https://www.npmjs.com/package/ng-virtual-list/v/16.3.0) |
495
+ | 15.x | 15.3.0 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.3.0](https://www.npmjs.com/package/ng-virtual-list/v/15.3.0) |
496
+ | 14.x | 14.3.0 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.3.0](https://www.npmjs.com/package/ng-virtual-list/v/14.3.0) |
497
497
 
498
498
  <br/>
499
499
 
@@ -1,7 +1,7 @@
1
- import * as i0 from '@angular/core';
2
- import { signal, inject, ElementRef, ChangeDetectionStrategy, Component, viewChild, output, input, ViewContainerRef, ViewChild, ViewEncapsulation } from '@angular/core';
3
1
  import * as i1 from '@angular/common';
4
2
  import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { signal, inject, ElementRef, ChangeDetectionStrategy, Component, viewChild, output, input, ViewContainerRef, ViewChild, ViewEncapsulation } from '@angular/core';
5
5
  import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
6
  import { tap, filter, map, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
7
7
 
@@ -82,13 +82,22 @@ const SCROLL_END = 'scrollend';
82
82
  const CLASS_LIST_VERTICAL = 'vertical';
83
83
  const CLASS_LIST_HORIZONTAL = 'horizontal';
84
84
 
85
+ /**
86
+ * Virtual List Item Interface
87
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/models/-basevirtual-list-item-component.ts
88
+ * @author Evgenii Grebennikov
89
+ * @email djonnyx@gmail.com
90
+ */
91
+ class BaseVirtualListItemComponent {
92
+ }
93
+
85
94
  /**
86
95
  * Virtual list item component
87
96
  * @link https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/components/ng-virtual-list-item.component.ts
88
97
  * @author Evgenii Grebennikov
89
98
  * @email djonnyx@gmail.com
90
99
  */
91
- class NgVirtualListItemComponent {
100
+ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
92
101
  static __nextId = 0;
93
102
  _id;
94
103
  get id() {
@@ -128,6 +137,7 @@ class NgVirtualListItemComponent {
128
137
  return this._elementRef.nativeElement;
129
138
  }
130
139
  constructor() {
140
+ super();
131
141
  this._id = NgVirtualListItemComponent.__nextId = NgVirtualListItemComponent.__nextId === Number.MAX_SAFE_INTEGER
132
142
  ? 0 : NgVirtualListItemComponent.__nextId + 1;
133
143
  }
@@ -135,9 +145,10 @@ class NgVirtualListItemComponent {
135
145
  const data = this._data, regular = this.regular, length = this._regularLength;
136
146
  if (data) {
137
147
  const styles = this._elementRef.nativeElement.style;
138
- styles.zIndex = String(data.config.sticky);
148
+ styles.zIndex = data.config.zIndex;
139
149
  if (data.config.snapped) {
140
- styles.transform = ZEROS_TRANSLATE_3D;
150
+ styles.transform = data.config.sticky === 1 ? ZEROS_TRANSLATE_3D : `${TRANSLATE_3D}(${data.config.isVertical ? 0 : data.measures.x}${PX}, ${data.config.isVertical ? data.measures.y : 0}${PX} , 0)`;
151
+ ;
141
152
  if (!data.config.isSnappingMethodAdvanced) {
142
153
  styles.position = POSITION_STICKY;
143
154
  }
@@ -173,7 +184,7 @@ class NgVirtualListItemComponent {
173
184
  }
174
185
  styles.visibility = VISIBILITY_VISIBLE;
175
186
  }
176
- styles.zIndex = String(this._data?.config?.sticky ?? DEFAULT_ZINDEX);
187
+ styles.zIndex = this._data?.config?.zIndex ?? DEFAULT_ZINDEX;
177
188
  }
178
189
  hide() {
179
190
  const styles = this._elementRef.nativeElement.style;
@@ -194,7 +205,7 @@ class NgVirtualListItemComponent {
194
205
  styles.zIndex = HIDDEN_ZINDEX;
195
206
  }
196
207
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
197
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: NgVirtualListItemComponent, isStandalone: true, selector: "ng-virtual-list-item", host: { classAttribute: "ngvl__item" }, ngImport: i0, template: "@let item = data();\r\n@let renderer = itemRenderer();\r\n\r\n@if (item) {\r\n <li #listItem part=\"item\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut}\">\r\n @if (renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, config: item.config}\" />\r\n }\r\n </li>\r\n}", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden;will-change:scroll-position}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
208
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.4", type: NgVirtualListItemComponent, isStandalone: true, selector: "ng-virtual-list-item", host: { classAttribute: "ngvl__item" }, usesInheritance: true, ngImport: i0, template: "@let item = data();\r\n@let renderer = itemRenderer();\r\n\r\n@if (item) {\r\n <li #listItem part=\"item\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut}\">\r\n @if (renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, config: item.config}\" />\r\n }\r\n </li>\r\n}", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden;will-change:scroll-position}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
198
209
  }
199
210
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListItemComponent, decorators: [{
200
211
  type: Component,
@@ -254,12 +265,12 @@ const debounce = (cb, debounceTime = 0) => {
254
265
  * @author Evgenii Grebennikov
255
266
  * @email djonnyx@gmail.com
256
267
  */
257
- const toggleClassName = (el, className, remove = false) => {
268
+ const toggleClassName = (el, className, removeClassName) => {
258
269
  if (!el.classList.contains(className)) {
259
270
  el.classList.add(className);
260
271
  }
261
- else if (remove) {
262
- el.classList.remove(className);
272
+ if (removeClassName) {
273
+ el.classList.remove(removeClassName);
263
274
  }
264
275
  };
265
276
 
@@ -325,7 +336,17 @@ class Tracker {
325
336
  }
326
337
  }
327
338
  comp.instance.item = item;
328
- comp.instance.show();
339
+ if (snapedComponent) {
340
+ if (item['config']['snapped'] || item['config']['snappedOut']) {
341
+ comp.instance.hide();
342
+ }
343
+ else {
344
+ comp.instance.show();
345
+ }
346
+ }
347
+ else {
348
+ comp.instance.show();
349
+ }
329
350
  untrackedItems.splice(indexByUntrackedItems, 1);
330
351
  continue;
331
352
  }
@@ -344,7 +365,17 @@ class Tracker {
344
365
  }
345
366
  }
346
367
  comp.instance.item = item;
347
- comp.instance.show();
368
+ if (snapedComponent) {
369
+ if (item['config']['snapped'] || item['config']['snappedOut']) {
370
+ comp.instance.hide();
371
+ }
372
+ else {
373
+ comp.instance.show();
374
+ }
375
+ }
376
+ else {
377
+ comp.instance.show();
378
+ }
348
379
  if (this._trackMap) {
349
380
  this._trackMap[itemTrackingProperty] = comp.instance.id;
350
381
  }
@@ -962,7 +993,7 @@ class TrackBox extends CacheMap {
962
993
  }
963
994
  }
964
995
  if (deletedItemsMap.hasOwnProperty(i)) {
965
- const bounds = deletedItemsMap[i], size = bounds[sizeProperty] ?? typicalItemSize;
996
+ const bounds = deletedItemsMap[i], size = bounds?.[sizeProperty] ?? typicalItemSize;
966
997
  if (y < scrollSize - size) {
967
998
  leftSizeOfDeletedItems += size;
968
999
  }
@@ -970,7 +1001,7 @@ class TrackBox extends CacheMap {
970
1001
  totalSize += componentSize;
971
1002
  if (isFromId) {
972
1003
  if (itemById === undefined) {
973
- if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
1004
+ if (id !== fromItemId && stickyMap && stickyMap[id] === 1) {
974
1005
  stickyComponentSize = componentSize;
975
1006
  stickyCollectionItem = collectionItem;
976
1007
  }
@@ -1166,19 +1197,19 @@ class TrackBox extends CacheMap {
1166
1197
  this.bumpVersion();
1167
1198
  }
1168
1199
  generateDisplayCollection(items, stickyMap, metrics) {
1169
- const { normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
1200
+ const { width, height, normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsOnDisplayLength, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
1170
1201
  if (items.length) {
1171
- const actualSnippedPosition = snippedPos, isSnappingMethodAdvanced = this.isSnappingMethodAdvanced;
1172
- let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0;
1202
+ const actualSnippedPosition = snippedPos, isSnappingMethodAdvanced = this.isSnappingMethodAdvanced, boundsSize = isVertical ? height : width, actualEndSnippedPosition = boundsSize;
1203
+ let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0, endStickyItem, nextEndSticky, endStickyItemIndex = -1, endStickyItemSize = 0;
1173
1204
  if (snap) {
1174
1205
  for (let i = Math.min(itemsFromStartToScrollEnd > 0 ? itemsFromStartToScrollEnd : 0, totalLength - 1); i >= 0; i--) {
1175
1206
  const id = items[i].id, sticky = stickyMap[id], size = dynamicSize ? this.get(id)?.[sizeProperty] || typicalItemSize : typicalItemSize;
1176
- if (sticky > 0) {
1207
+ if (sticky === 1) {
1177
1208
  const measures = {
1178
1209
  x: isVertical ? 0 : actualSnippedPosition,
1179
1210
  y: isVertical ? actualSnippedPosition : 0,
1180
- width: normalizedItemWidth,
1181
- height: normalizedItemHeight,
1211
+ width: isVertical ? normalizedItemWidth : size,
1212
+ height: isVertical ? size : normalizedItemHeight,
1182
1213
  delta: 0,
1183
1214
  }, config = {
1184
1215
  isVertical,
@@ -1188,6 +1219,7 @@ class TrackBox extends CacheMap {
1188
1219
  snappedOut: false,
1189
1220
  dynamic: dynamicSize,
1190
1221
  isSnappingMethodAdvanced,
1222
+ zIndex: '1',
1191
1223
  };
1192
1224
  const itemData = items[i];
1193
1225
  stickyItem = { id, measures, data: itemData, config };
@@ -1198,18 +1230,50 @@ class TrackBox extends CacheMap {
1198
1230
  }
1199
1231
  }
1200
1232
  }
1233
+ if (snap) {
1234
+ const startIndex = itemsFromStartToScrollEnd + itemsOnDisplayLength - 1;
1235
+ for (let i = Math.min(startIndex, totalLength > 0 ? totalLength - 1 : 0), l = totalLength; i < l; i++) {
1236
+ const id = items[i].id, sticky = stickyMap[id], size = dynamicSize
1237
+ ? this.get(id)?.[sizeProperty] || typicalItemSize
1238
+ : typicalItemSize;
1239
+ if (sticky === 2) {
1240
+ const w = isVertical ? normalizedItemWidth : size, h = isVertical ? size : normalizedItemHeight, measures = {
1241
+ x: isVertical ? 0 : actualEndSnippedPosition - w,
1242
+ y: isVertical ? actualEndSnippedPosition - h : 0,
1243
+ width: w,
1244
+ height: h,
1245
+ delta: 0,
1246
+ }, config = {
1247
+ isVertical,
1248
+ sticky,
1249
+ snap,
1250
+ snapped: true,
1251
+ snappedOut: false,
1252
+ dynamic: dynamicSize,
1253
+ isSnappingMethodAdvanced,
1254
+ zIndex: '1',
1255
+ };
1256
+ const itemData = items[i];
1257
+ endStickyItem = { id, measures, data: itemData, config };
1258
+ endStickyItemIndex = i;
1259
+ endStickyItemSize = size;
1260
+ displayItems.push(endStickyItem);
1261
+ break;
1262
+ }
1263
+ }
1264
+ }
1201
1265
  let i = startIndex;
1202
1266
  while (renderItems > 0) {
1203
1267
  if (i >= totalLength) {
1204
1268
  break;
1205
1269
  }
1206
1270
  const id = items[i].id, size = dynamicSize ? this.get(id)?.[sizeProperty] || typicalItemSize : typicalItemSize;
1207
- if (id !== stickyItem?.id) {
1208
- const snapped = snap && stickyMap[id] > 0 && pos <= scrollSize, measures = {
1209
- x: isVertical ? 0 : pos,
1210
- y: isVertical ? pos : 0,
1211
- width: normalizedItemWidth,
1212
- height: normalizedItemHeight,
1271
+ if (id !== stickyItem?.id && id !== endStickyItem?.id) {
1272
+ const snapped = snap && (stickyMap[id] === 1 && pos <= scrollSize || stickyMap[id] === 2 && pos >= scrollSize + boundsSize - size), measures = {
1273
+ x: isVertical ? stickyMap[id] === 1 ? 0 : boundsSize - size : pos,
1274
+ y: isVertical ? pos : stickyMap[id] === 2 ? boundsSize - size : 0,
1275
+ width: isVertical ? normalizedItemWidth : size,
1276
+ height: isVertical ? size : normalizedItemHeight,
1213
1277
  delta: 0,
1214
1278
  }, config = {
1215
1279
  isVertical,
@@ -1219,15 +1283,28 @@ class TrackBox extends CacheMap {
1219
1283
  snappedOut: false,
1220
1284
  dynamic: dynamicSize,
1221
1285
  isSnappingMethodAdvanced,
1286
+ zIndex: '0',
1222
1287
  };
1288
+ if (snapped) {
1289
+ config.zIndex = '2';
1290
+ }
1223
1291
  const itemData = items[i];
1224
1292
  const item = { id, measures, data: itemData, config };
1225
- if (!nextSticky && stickyItemIndex < i && stickyMap[id] > 0 && pos <= scrollSize + size + stickyItemSize) {
1293
+ if (!nextSticky && stickyItemIndex < i && stickyMap[id] === 1 && (pos <= scrollSize + size + stickyItemSize)) {
1226
1294
  item.measures.x = isVertical ? 0 : snapped ? actualSnippedPosition : pos;
1227
1295
  item.measures.y = isVertical ? snapped ? actualSnippedPosition : pos : 0;
1228
1296
  nextSticky = item;
1229
1297
  nextSticky.config.snapped = snapped;
1230
- nextSticky.measures.delta = isVertical ? item.measures.y - scrollSize : item.measures.x - scrollSize;
1298
+ nextSticky.measures.delta = isVertical ? (item.measures.y - scrollSize) : (item.measures.x - scrollSize);
1299
+ nextSticky.config.zIndex = '3';
1300
+ }
1301
+ else if (!nextEndSticky && endStickyItemIndex > i && stickyMap[id] === 2 && (pos >= scrollSize + boundsSize - size - endStickyItemSize)) {
1302
+ item.measures.x = isVertical ? 0 : snapped ? actualEndSnippedPosition - size : pos;
1303
+ item.measures.y = isVertical ? snapped ? actualEndSnippedPosition - size : pos : 0;
1304
+ nextEndSticky = item;
1305
+ nextEndSticky.config.zIndex = '3';
1306
+ nextEndSticky.config.snapped = snapped;
1307
+ nextEndSticky.measures.delta = isVertical ? (item.measures.y - scrollSize) : (item.measures.x - scrollSize);
1231
1308
  }
1232
1309
  displayItems.push(item);
1233
1310
  }
@@ -1249,6 +1326,19 @@ class TrackBox extends CacheMap {
1249
1326
  nextSticky.measures.delta = isVertical ? nextSticky.measures.y - scrollSize : nextSticky.measures.x - scrollSize;
1250
1327
  }
1251
1328
  }
1329
+ if (nextEndSticky && endStickyItem && nextEndSticky.measures[axis] >= scrollSize + boundsSize - endStickyItemSize - nextEndSticky.measures[sizeProperty]) {
1330
+ if (nextEndSticky.measures[axis] < scrollSize + boundsSize - endStickyItemSize) {
1331
+ endStickyItem.measures[axis] = nextEndSticky.measures[axis] + nextEndSticky.measures[sizeProperty];
1332
+ endStickyItem.config.snapped = nextEndSticky.config.snapped = false;
1333
+ endStickyItem.config.snappedOut = true;
1334
+ endStickyItem.config.sticky = 2;
1335
+ endStickyItem.measures.delta = isVertical ? endStickyItem.measures.y - scrollSize : endStickyItem.measures.x - scrollSize;
1336
+ }
1337
+ else {
1338
+ nextEndSticky.config.snapped = true;
1339
+ nextEndSticky.measures.delta = isVertical ? nextEndSticky.measures.y - scrollSize : nextEndSticky.measures.x - scrollSize;
1340
+ }
1341
+ }
1252
1342
  }
1253
1343
  return displayItems;
1254
1344
  }
@@ -1402,6 +1492,7 @@ class NgVirtualListComponent {
1402
1492
  * Rendering element template.
1403
1493
  */
1404
1494
  itemRenderer = input.required();
1495
+ _itemRenderer = signal(undefined);
1405
1496
  /**
1406
1497
  * Dictionary zIndex by id of the list element. If the value is not set or equal to 0,
1407
1498
  * then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element.
@@ -1441,6 +1532,7 @@ class NgVirtualListComponent {
1441
1532
  */
1442
1533
  snappingMethod = input(DEFAULT_SNAPPING_METHOD);
1443
1534
  _isSnappingMethodAdvanced = this.getIsSnappingMethodAdvanced();
1535
+ get isSnappingMethodAdvanced() { return this._isSnappingMethodAdvanced; }
1444
1536
  _isVertical = this.getIsVertical();
1445
1537
  _displayComponents = [];
1446
1538
  _snapedDisplayComponent;
@@ -1509,10 +1601,18 @@ class NgVirtualListComponent {
1509
1601
  * The name of the property by which tracking is performed
1510
1602
  */
1511
1603
  trackBy = input(TRACK_BY_PROPERTY_NAME);
1604
+ /**
1605
+ * Base class of the element component
1606
+ */
1607
+ _itemComponentClass = NgVirtualListItemComponent;
1608
+ /**
1609
+ * Base class trackBox
1610
+ */
1611
+ _trackBoxClass = TrackBox;
1512
1612
  /**
1513
1613
  * Dictionary of element sizes by their id
1514
1614
  */
1515
- _trackBox = new TrackBox(this.trackBy());
1615
+ _trackBox = new this._trackBoxClass(this.trackBy());
1516
1616
  _onTrackBoxChangeHandler = (v) => {
1517
1617
  this._cacheVersion.set(v);
1518
1618
  };
@@ -1532,7 +1632,7 @@ class NgVirtualListComponent {
1532
1632
  $isVertical.pipe(takeUntilDestroyed(), tap(v => {
1533
1633
  this._isVertical = v;
1534
1634
  const el = this._elementRef.nativeElement;
1535
- toggleClassName(el, v ? CLASS_LIST_VERTICAL : CLASS_LIST_HORIZONTAL, true);
1635
+ toggleClassName(el, v ? CLASS_LIST_VERTICAL : CLASS_LIST_HORIZONTAL, v ? CLASS_LIST_HORIZONTAL : CLASS_LIST_VERTICAL);
1536
1636
  })).subscribe();
1537
1637
  $snappingMethod.pipe(takeUntilDestroyed(), tap(v => {
1538
1638
  this._isSnappingMethodAdvanced = this._trackBox.isSnappingMethodAdvanced = v;
@@ -1569,12 +1669,19 @@ class NgVirtualListComponent {
1569
1669
  }
1570
1670
  return of(displayItems);
1571
1671
  })).subscribe();
1572
- combineLatest([this.$initialized, toObservable(this.itemRenderer)]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
1573
- this.resetRenderers(itemRenderer);
1574
- }));
1672
+ this.setupRenderer();
1673
+ }
1674
+ setupRenderer() {
1675
+ const $itemRenderer = toObservable(this.itemRenderer);
1676
+ $itemRenderer.pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(v => !!v), tap(v => {
1677
+ this._itemRenderer.set(v);
1678
+ })).subscribe();
1575
1679
  }
1576
1680
  /** @internal */
1577
1681
  ngOnInit() {
1682
+ this.onInit();
1683
+ }
1684
+ onInit() {
1578
1685
  this._initialized.set(true);
1579
1686
  }
1580
1687
  listenCacheChangesIfNeed(value) {
@@ -1604,7 +1711,7 @@ class NgVirtualListComponent {
1604
1711
  }
1605
1712
  if (this._isSnappingMethodAdvanced && this.snap()) {
1606
1713
  if (!this._snapedDisplayComponent && this._snapContainerRef) {
1607
- const comp = this._snapContainerRef.createComponent(NgVirtualListItemComponent);
1714
+ const comp = this._snapContainerRef.createComponent(this._itemComponentClass);
1608
1715
  comp.instance.regular = true;
1609
1716
  this._snapedDisplayComponent = comp;
1610
1717
  this._trackBox.snapedDisplayComponent = this._snapedDisplayComponent;
@@ -1617,7 +1724,7 @@ class NgVirtualListComponent {
1617
1724
  const maxLength = displayItems.length, components = this._displayComponents;
1618
1725
  while (components.length < maxLength) {
1619
1726
  if (_listContainerRef) {
1620
- const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
1727
+ const comp = _listContainerRef.createComponent(this._itemComponentClass);
1621
1728
  components.push(comp);
1622
1729
  this._componentsResizeObserver.observe(comp.instance.element);
1623
1730
  }
@@ -1633,13 +1740,13 @@ class NgVirtualListComponent {
1633
1740
  const item = components[i];
1634
1741
  if (item) {
1635
1742
  const id = item.instance.id;
1636
- item.instance.renderer = itemRenderer || this.itemRenderer();
1743
+ item.instance.renderer = itemRenderer || this._itemRenderer();
1637
1744
  doMap[id] = i;
1638
1745
  }
1639
1746
  }
1640
1747
  if (this._isSnappingMethodAdvanced && this.snap() && this._snapedDisplayComponent && this._snapContainerRef) {
1641
1748
  const comp = this._snapedDisplayComponent;
1642
- comp.instance.renderer = itemRenderer || this.itemRenderer();
1749
+ comp.instance.renderer = itemRenderer || this._itemRenderer();
1643
1750
  }
1644
1751
  this._trackBox.setDisplayObjectIndexMapById(doMap);
1645
1752
  }
@@ -1757,6 +1864,9 @@ class NgVirtualListComponent {
1757
1864
  };
1758
1865
  /** @internal */
1759
1866
  ngAfterViewInit() {
1867
+ this.afterViewInit();
1868
+ }
1869
+ afterViewInit() {
1760
1870
  const containerEl = this._container();
1761
1871
  if (containerEl) {
1762
1872
  // for direction calculation
@@ -1770,6 +1880,9 @@ class NgVirtualListComponent {
1770
1880
  }
1771
1881
  /** @internal */
1772
1882
  ngOnDestroy() {
1883
+ this.dispose();
1884
+ }
1885
+ dispose() {
1773
1886
  this.clearScrollToRepeatExecutionTimeout();
1774
1887
  if (this._trackBox) {
1775
1888
  this._trackBox.dispose();
@@ -1800,11 +1913,11 @@ class NgVirtualListComponent {
1800
1913
  }
1801
1914
  }
1802
1915
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1803
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 }, 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 }, snappingMethod: { classPropertyName: "snappingMethod", publicName: "snappingMethod", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_snappedContainer", first: true, predicate: ["snapped"], descendants: true, isSignal: true }, { 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 }, { propertyName: "_snapContainerRef", first: true, predicate: ["snapRendererContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "@if (snap()) {\r\n<div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.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 });
1916
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 }, 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 }, snappingMethod: { classPropertyName: "snappingMethod", publicName: "snappingMethod", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_snappedContainer", first: true, predicate: ["snapped"], descendants: true, isSignal: true }, { 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 }, { propertyName: "_snapContainerRef", first: true, predicate: ["snapRendererContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "@if (snap()) {\r\n<div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.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 });
1804
1917
  }
1805
1918
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: NgVirtualListComponent, decorators: [{
1806
1919
  type: Component,
1807
- args: [{ selector: 'ng-virtual-list', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, template: "@if (snap()) {\r\n<div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"] }]
1920
+ args: [{ selector: 'ng-virtual-list', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, template: "@if (snap()) {\r\n<div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n</div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"] }]
1808
1921
  }], ctorParameters: () => [], propDecorators: { _listContainerRef: [{
1809
1922
  type: ViewChild,
1810
1923
  args: ['renderersContainer', { read: ViewContainerRef }]
@@ -1821,5 +1934,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImpor
1821
1934
  * Generated bundle index. Do not edit.
1822
1935
  */
1823
1936
 
1824
- export { Directions, NgVirtualListComponent, SnappingMethods };
1937
+ export { BaseVirtualListItemComponent, Directions, NgVirtualListComponent, NgVirtualListItemComponent, SnappingMethods };
1825
1938
  //# sourceMappingURL=ng-virtual-list.mjs.map