ng-virtual-list 20.10.3 → 20.10.4

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
@@ -101,7 +101,7 @@ npm i ng-virtual-list
101
101
 
102
102
  ## 🚀 Quick Start
103
103
  ```html
104
- <ng-virtual-list [items]="items" [bufferSize]="5" [itemRenderer]="itemRenderer" [itemSize]="64"></ng-virtual-list>
104
+ <ng-virtual-list [items]="items" [bufferSize]="5" [itemRenderer]="itemRenderer" [dynamicSize]="false" [itemSize]="64"></ng-virtual-list>
105
105
 
106
106
  <ng-template #itemRenderer let-data="data">
107
107
  @if (data) {
@@ -124,7 +124,7 @@ items = Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Item #${i}` })
124
124
  Template:
125
125
  ```html
126
126
  <ng-virtual-list class="list" direction="horizontal" [items]="horizontalItems" [bufferSize]="1" [maxBufferSize]="5"
127
- [itemRenderer]="horizontalItemRenderer" [itemSize]="64" [methodForSelecting]="'select'"
127
+ [itemRenderer]="horizontalItemRenderer" [dynamicSize]="false" [itemSize]="64" [methodForSelecting]="'select'"
128
128
  [selectedIds]="2" (onSelect)="onSelect($event)" (onItemClick)="onItemClick($event)"></ng-virtual-list>
129
129
 
130
130
  <ng-template #horizontalItemRenderer let-data="data" let-config="config">
@@ -174,7 +174,7 @@ export class AppComponent {
174
174
  Template:
175
175
  ```html
176
176
  <ng-virtual-list class="list" direction="horizontal" [items]="horizontalGroupItems" [itemRenderer]="horizontalGroupItemRenderer"
177
- [bufferSize]="1" [maxBufferSize]="5" [itemConfigMap]="horizontalGroupItemConfigMap" [itemSize]="54" [snap]="true"
177
+ [bufferSize]="1" [maxBufferSize]="5" [itemConfigMap]="horizontalGroupItemConfigMap" [dynamicSize]="false" [itemSize]="54" [snap]="true"
178
178
  methodForSelecting="multi-select" [selectedIds]="[3,2]" (onSelect)="onSelect($event)" (onItemClick)="onItemClick($event)"></ng-virtual-list>
179
179
 
180
180
  <ng-template #horizontalGroupItemRenderer let-data="data" let-config="config">
@@ -248,7 +248,7 @@ export class AppComponent {
248
248
  Template:
249
249
  ```html
250
250
  <ng-virtual-list class="list simple" [items]="items" [bufferSize]="1" [maxBufferSize]="5" [itemRenderer]="itemRenderer"
251
- [itemSize]="40"></ng-virtual-list>
251
+ [dynamicSize]="false" [itemSize]="40"></ng-virtual-list>
252
252
 
253
253
  <ng-template #itemRenderer let-data="data">
254
254
  @if (data) {
@@ -289,7 +289,7 @@ export class AppComponent {
289
289
  Template:
290
290
  ```html
291
291
  <ng-virtual-list class="list simple" [items]="groupItems" [bufferSize]="1" [maxBufferSize]="5" [itemRenderer]="groupItemRenderer"
292
- [itemConfigMap]="groupItemConfigMap" [itemSize]="40" [snap]="false"></ng-virtual-list>
292
+ [itemConfigMap]="groupItemConfigMap" [dynamicSize]="false" [itemSize]="40" [snap]="false"></ng-virtual-list>
293
293
 
294
294
  <ng-template #groupItemRenderer let-data="data">
295
295
  @if (data) {
@@ -316,7 +316,7 @@ Template:
316
316
  Template (with snapping):
317
317
  ```html
318
318
  <ng-virtual-list class="list simple" [items]="groupItems" [bufferSize]="1" [maxBufferSize]="5" [itemRenderer]="groupItemRenderer"
319
- [itemConfigMap]="groupItemConfigMap" [itemSize]="40" [snap]="true"></ng-virtual-list>
319
+ [itemConfigMap]="groupItemConfigMap" [dynamicSize]="false" [itemSize]="40" [snap]="true"></ng-virtual-list>
320
320
 
321
321
  <ng-template #groupItemRenderer let-data="data">
322
322
  @if (data) {
@@ -381,7 +381,7 @@ Template
381
381
  </div>
382
382
 
383
383
  <ng-virtual-list #virtualList class="list" [items]="items" [itemRenderer]="itemRenderer" [bufferSize]="1" [maxBufferSize]="5"
384
- [itemSize]="40"></ng-virtual-list>
384
+ [dynamicSize]="false" [itemSize]="40"></ng-virtual-list>
385
385
 
386
386
  <ng-template #itemRenderer let-data="data">
387
387
  @if (data) {
@@ -435,7 +435,7 @@ Virtual list with height-adjustable elements.
435
435
  Template
436
436
  ```html
437
437
  <ng-virtual-list #dynamicList class="list" [items]="groupDynamicItems" [itemRenderer]="groupItemRenderer" [bufferSize]="1" [maxBufferSize]="5"
438
- [itemConfigMap]="groupDynamicItemConfigMap" [dynamicSize]="true" [snap]="true"></ng-virtual-list>
438
+ [itemConfigMap]="groupDynamicItemConfigMap" [snap]="true"></ng-virtual-list>
439
439
 
440
440
  <ng-template #groupItemRenderer let-data="data">
441
441
  @if (data) {
@@ -649,7 +649,7 @@ Inputs
649
649
  |---|---|---|
650
650
  | id | number | Readonly. Returns the unique identifier of the component. |
651
651
  | 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. |
652
- | 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. |
652
+ | itemSize | number? = 24 | If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element. If the dynamicSize property is true, the items in the list can have different sizes, and you must specify the itemSize property to adjust the sizes of the items in the unallocated area. |
653
653
  | bufferSize | number? = 2 | Number of elements outside the scope of visibility. Default value is 2. |
654
654
  | maxBufferSize | number? = 10 | Maximum number of elements outside the scope of visibility. Default value is 10. If maxBufferSize is set to be greater than bufferSize, then adaptive buffer mode is enabled. The greater the scroll size, the more elements are allocated for rendering. |
655
655
  | itemRenderer | TemplateRef | Rendering element template. |
@@ -661,7 +661,7 @@ Inputs
661
661
  | snap | boolean? = false | Determines whether elements will snap. Default value is "false". |
662
662
  | 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. 'chat' - The group is rendered on a background. List items below the group are not rendered. |
663
663
  | 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". |
664
- | 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. |
664
+ | dynamicSize | boolean? = true | If true, items in the list may have different sizes, and the itemSize property must be specified to adjust the sizes of items in the unallocated area. If false then the items in the list have a fixed size specified by the itemSize property. The default value is true. |
665
665
  | enabledBufferOptimization | boolean? = true | Experimental! Enables buffer optimization. Can only be used if items in the collection are not added or updated. |
666
666
  | trackBy | string? = 'id' | The name of the property by which tracking is performed. |
667
667
  | selectedIds | Array<[Id](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/types/id.ts)> \| [Id](https://github.com/DjonnyX/ng-virtual-list/blob/20.x/projects/ng-virtual-list/src/lib/types/id.ts) \| undefined | Sets the selected items. |
@@ -737,12 +737,12 @@ Properties
737
737
 
738
738
  | Angular version | ng-virtual-list version | git | npm |
739
739
  |--|--|--|--|
740
- | 19.x | 19.10.0 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.10.0](https://www.npmjs.com/package/ng-virtual-list/v/19.10.0) |
741
- | 18.x | 18.10.0 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.10.0](https://www.npmjs.com/package/ng-virtual-list/v/18.10.0) |
742
- | 17.x | 17.10.0 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.10.0](https://www.npmjs.com/package/ng-virtual-list/v/17.10.0) |
743
- | 16.x | 16.9.6 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.9.6](https://www.npmjs.com/package/ng-virtual-list/v/16.9.6) |
744
- | 15.x | 15.9.6 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.9.6](https://www.npmjs.com/package/ng-virtual-list/v/15.9.6) |
745
- | 14.x | 14.9.6 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.9.6](https://www.npmjs.com/package/ng-virtual-list/v/14.9.6) |
740
+ | 19.x | 19.10.1 | [19.x](https://github.com/DjonnyX/ng-virtual-list/tree/19.x) | [19.10.1](https://www.npmjs.com/package/ng-virtual-list/v/19.10.1) |
741
+ | 18.x | 18.10.1 | [18.x](https://github.com/DjonnyX/ng-virtual-list/tree/18.x) | [18.10.1](https://www.npmjs.com/package/ng-virtual-list/v/18.10.1) |
742
+ | 17.x | 17.10.1 | [17.x](https://github.com/DjonnyX/ng-virtual-list/tree/17.x) | [17.10.1](https://www.npmjs.com/package/ng-virtual-list/v/17.10.1) |
743
+ | 16.x | 16.10.0 | [16.x](https://github.com/DjonnyX/ng-virtual-list/tree/16.x) | [16.10.0](https://www.npmjs.com/package/ng-virtual-list/v/16.10.0) |
744
+ | 15.x | 15.10.0 | [15.x](https://github.com/DjonnyX/ng-virtual-list/tree/15.x) | [15.10.0](https://www.npmjs.com/package/ng-virtual-list/v/15.10.0) |
745
+ | 14.x | 14.10.1 | [14.x](https://github.com/DjonnyX/ng-virtual-list/tree/14.x) | [14.10.1](https://www.npmjs.com/package/ng-virtual-list/v/14.10.1) |
746
746
 
747
747
  <br/>
748
748
 
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { Injectable, output, inject, ElementRef, DestroyRef, Input, Directive, signal, computed, ChangeDetectionStrategy, Component, viewChild, input, effect, ViewEncapsulation, InjectionToken, ViewChild, NO_ERRORS_SCHEMA, ViewContainerRef } from '@angular/core';
5
5
  import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
6
- import { Subject, tap, fromEvent, race, of, combineLatest, map, filter as filter$1, switchMap as switchMap$1, delay, takeUntil as takeUntil$1, from, debounceTime, BehaviorSubject as BehaviorSubject$1, distinctUntilChanged, take } from 'rxjs';
6
+ import { Subject, tap, fromEvent, race, of, switchMap as switchMap$1, combineLatest, map, filter as filter$1, delay, takeUntil as takeUntil$1, from, debounceTime, BehaviorSubject as BehaviorSubject$1, distinctUntilChanged, take } from 'rxjs';
7
7
  import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
8
8
  import { switchMap, takeUntil, filter, tap as tap$1 } from 'rxjs/operators';
9
9
  import { CdkScrollable } from '@angular/cdk/scrolling';
@@ -134,7 +134,7 @@ const DEFAULT_SNAP = false;
134
134
  const DEFAULT_SELECT_BY_CLICK = true;
135
135
  const DEFAULT_COLLAPSE_BY_CLICK = true;
136
136
  const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = false;
137
- const DEFAULT_DYNAMIC_SIZE = false;
137
+ const DEFAULT_DYNAMIC_SIZE = true;
138
138
  const DEFAULT_SNAP_TO_END_TRANSITION_INSTANT_OFFSET = 1;
139
139
  const DEFAULT_SNAP_SCROLLTO_BOTTOM = false;
140
140
  const TRACK_BY_PROPERTY_NAME = 'id';
@@ -288,6 +288,7 @@ class NgVirtualListService {
288
288
  $scrollToStart = this._$scrollToStart.asObservable();
289
289
  _$scrollToEnd = new Subject();
290
290
  $scrollToEnd = this._$scrollToEnd.asObservable();
291
+ lastFocusedItemId = -1;
291
292
  scrollStartOffset = 0;
292
293
  scrollEndOffset = 0;
293
294
  scrollBarSize = 0;
@@ -634,7 +635,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
634
635
  args: ['maxClickDistance']
635
636
  }], onClick: [{ type: i0.Output, args: ["onClick"] }] } });
636
637
 
637
- const ZEROS_POSITION = -1000, ATTR_AREA_SELECTED = 'area-selected', POSITION = 'position', POSITION_ZERO = '0', ID$1 = 'item-id', KEY_SPACE = ' ', KEY_ARR_LEFT = 'ArrowLeft', KEY_ARR_UP = 'ArrowUp', KEY_ARR_RIGHT = 'ArrowRight', KEY_ARR_DOWN = 'ArrowDown', EVENT_FOCUS_IN = 'focusin', EVENT_FOCUS_OUT = 'focusout', EVENT_KEY_DOWN = 'keydown', CLASS_NAME_SNAPPED = 'snapped', CLASS_NAME_SNAPPED_OUT = 'snapped-out', CLASS_NAME_FOCUS = 'focus';
638
+ const ZEROS_POSITION = -1000, NAVIGATE_TO_ATTEMT = 5, ATTR_AREA_SELECTED = 'area-selected', POSITION = 'position', POSITION_ZERO = '0', ID$1 = 'item-id', KEY_SPACE = ' ', KEY_ARR_LEFT = 'ArrowLeft', KEY_ARR_UP = 'ArrowUp', KEY_ARR_RIGHT = 'ArrowRight', KEY_ARR_DOWN = 'ArrowDown', EVENT_FOCUS_IN = 'focusin', EVENT_FOCUS_OUT = 'focusout', EVENT_KEY_DOWN = 'keydown', CLASS_NAME_SNAPPED = 'snapped', CLASS_NAME_SNAPPED_OUT = 'snapped-out', CLASS_NAME_FOCUS = 'focus';
638
639
  /**
639
640
  * Virtual list component.
640
641
  * Maximum performance for extremely large lists.
@@ -728,6 +729,7 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
728
729
  (align = FocusAlignments.CENTER) => {
729
730
  this.focus(align);
730
731
  };
732
+ _destroyRef = inject(DestroyRef);
731
733
  constructor() {
732
734
  super();
733
735
  this._id = this._service.generateComponentId();
@@ -765,46 +767,43 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
765
767
  this.focused.set(false);
766
768
  this.updateConfig(this._data);
767
769
  this.updatePartStr(this._data, this._isSelected, this._isCollapsed);
768
- })).subscribe(),
769
- fromEvent(this.element, EVENT_KEY_DOWN).pipe(takeUntilDestroyed(), tap(e => {
770
- switch (e.key) {
771
- case KEY_SPACE: {
772
- e.stopImmediatePropagation();
773
- e.preventDefault();
774
- this._service.select(this._data);
775
- this._service.collapse(this._data);
776
- break;
777
- }
778
- case KEY_ARR_LEFT:
779
- if (!this.config().isVertical) {
780
- e.stopImmediatePropagation();
781
- e.preventDefault();
782
- this.focusPrev();
783
- }
784
- break;
785
- case KEY_ARR_UP:
786
- if (this.config().isVertical) {
787
- e.stopImmediatePropagation();
788
- e.preventDefault();
789
- this.focusPrev();
790
- }
791
- break;
792
- case KEY_ARR_RIGHT:
793
- if (!this.config().isVertical) {
794
- e.stopImmediatePropagation();
795
- e.preventDefault();
796
- this.focusNext();
797
- }
798
- break;
799
- case KEY_ARR_DOWN:
800
- if (this.config().isVertical) {
770
+ })).subscribe();
771
+ $focused.pipe(takeUntilDestroyed(), switchMap$1(v => {
772
+ if (v) {
773
+ return fromEvent(this.element, EVENT_KEY_DOWN).pipe(takeUntilDestroyed(this._destroyRef), tap(e => {
774
+ switch (e.key) {
775
+ case KEY_SPACE: {
801
776
  e.stopImmediatePropagation();
802
777
  e.preventDefault();
803
- this.focusNext();
778
+ this._service.select(this._data);
779
+ this._service.collapse(this._data);
780
+ break;
804
781
  }
805
- break;
806
- }
807
- })).subscribe();
782
+ case KEY_ARR_LEFT:
783
+ if (!this.config().isVertical) {
784
+ this.toPrevItem(e);
785
+ }
786
+ break;
787
+ case KEY_ARR_UP:
788
+ if (this.config().isVertical) {
789
+ this.toPrevItem(e);
790
+ }
791
+ break;
792
+ case KEY_ARR_RIGHT:
793
+ if (!this.config().isVertical) {
794
+ this.toNextItem(e);
795
+ }
796
+ break;
797
+ case KEY_ARR_DOWN:
798
+ if (this.config().isVertical) {
799
+ this.toNextItem(e);
800
+ }
801
+ break;
802
+ }
803
+ }));
804
+ }
805
+ return of(false);
806
+ })).subscribe();
808
807
  combineLatest([$data, this._service.$methodOfSelecting, this._service.$selectedIds, this._service.$collapsedIds]).pipe(takeUntilDestroyed(), map(([, m, selectedIds, collapsedIds]) => ({ method: m, selectedIds, collapsedIds })), tap(({ method, selectedIds, collapsedIds }) => {
809
808
  switch (method) {
810
809
  case MethodsForSelectingTypes.SELECT: {
@@ -833,19 +832,54 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
833
832
  this.updateMeasures(this._data);
834
833
  })).subscribe();
835
834
  }
835
+ toNextItem(e, attempt = NAVIGATE_TO_ATTEMT) {
836
+ const index = this.focusNext();
837
+ if (index > -1) {
838
+ this._service.lastFocusedItemId = index;
839
+ if (!!e && e.cancelable) {
840
+ e.stopImmediatePropagation();
841
+ e.preventDefault();
842
+ }
843
+ return;
844
+ }
845
+ if (this._service.lastFocusedItemId > -1) {
846
+ this.focus(FocusAlignments.CENTER, this._service.lastFocusedItemId);
847
+ }
848
+ if (attempt > 0) {
849
+ this.toNextItem(e, attempt - 1);
850
+ }
851
+ }
852
+ toPrevItem(e, attempt = NAVIGATE_TO_ATTEMT) {
853
+ const index = this.focusPrev();
854
+ if (index > -1) {
855
+ this._service.lastFocusedItemId = index;
856
+ if (!!e && e.cancelable) {
857
+ e.stopImmediatePropagation();
858
+ e.preventDefault();
859
+ }
860
+ return;
861
+ }
862
+ if (this._service.lastFocusedItemId > -1) {
863
+ this.focus(FocusAlignments.CENTER, this._service.lastFocusedItemId);
864
+ }
865
+ if (attempt > 0) {
866
+ this.toPrevItem(e, attempt - 1);
867
+ }
868
+ }
836
869
  focusNext() {
837
870
  if (this._service.listElement) {
838
871
  const tabIndex = this._data?.config?.tabIndex ?? 0, length = this._service.collection?.length ?? 0;
839
872
  let index = tabIndex;
840
873
  while (index <= length) {
841
874
  index++;
842
- const el = this._service.listElement.querySelector(getListElementByIndex(index));
843
- if (el) {
844
- this._service.focus(el);
845
- break;
875
+ const element = this._service.listElement.querySelector(getListElementByIndex(index));
876
+ if (!!element && element.style.visibility !== VISIBILITY_HIDDEN) {
877
+ this._service.focus(element);
878
+ return index;
846
879
  }
847
880
  }
848
881
  }
882
+ return -1;
849
883
  }
850
884
  focusPrev() {
851
885
  if (this._service.listElement) {
@@ -853,21 +887,22 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
853
887
  let index = tabIndex;
854
888
  while (index >= 0) {
855
889
  index--;
856
- const el = this._service.listElement.querySelector(getListElementByIndex(index));
857
- if (el) {
858
- this._service.focus(el);
859
- break;
890
+ const element = this._service.listElement.querySelector(getListElementByIndex(index));
891
+ if (!!element) {
892
+ this._service.focus(element);
893
+ return index;
860
894
  }
861
895
  }
862
896
  }
897
+ return -1;
863
898
  }
864
- focus(align = FocusAlignments.CENTER) {
899
+ focus(align = FocusAlignments.CENTER, index = -1) {
865
900
  if (this._service.listElement) {
866
- const tabIndex = this._data?.config?.tabIndex ?? 0;
867
- let index = tabIndex;
868
- const el = this._service.listElement.querySelector(getListElementByIndex(index));
869
- if (el) {
870
- this._service.focus(el, align);
901
+ const tabIndex = index > -1 ? index : this._data?.config?.tabIndex ?? 0;
902
+ let i = tabIndex;
903
+ const element = this._service.listElement.querySelector(getListElementByIndex(i));
904
+ if (!!element) {
905
+ this._service.focus(element, align);
871
906
  }
872
907
  }
873
908
  }
@@ -2195,14 +2230,16 @@ class TrackBox extends CacheMap {
2195
2230
  }
2196
2231
  }
2197
2232
  changes(immediately = false) {
2198
- if (this.changesDetected()) {
2199
- return;
2200
- }
2201
- this.bumpVersion();
2202
2233
  if (immediately) {
2203
2234
  this._previousVersion = this._version;
2204
2235
  this.dispatch(CACHE_BOX_CHANGE_EVENT_NAME, this.version);
2205
2236
  }
2237
+ else {
2238
+ if (this.changesDetected()) {
2239
+ return;
2240
+ }
2241
+ this.bumpVersion();
2242
+ }
2206
2243
  }
2207
2244
  generateDisplayCollection(items, itemConfigMap, metrics) {
2208
2245
  const { offsetY, offsetX, width, height, normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsOnDisplayLength, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
@@ -3821,10 +3858,10 @@ class NgScrollerComponent extends NgScrollView {
3821
3858
  this.prepared = false;
3822
3859
  }
3823
3860
  refresh(fireUpdate = false, updateScrollbar = true) {
3824
- this.scrollLimits();
3825
3861
  if (updateScrollbar) {
3826
3862
  this.stopScrolling();
3827
3863
  }
3864
+ this.scrollLimits();
3828
3865
  if (this.isVertical()) {
3829
3866
  this.refreshY(this._y);
3830
3867
  }
@@ -3832,7 +3869,7 @@ class NgScrollerComponent extends NgScrollView {
3832
3869
  this.refreshX(this._x);
3833
3870
  }
3834
3871
  if (updateScrollbar) {
3835
- this.updateScrollBarHandler(true);
3872
+ this.updateScrollBarHandler(false);
3836
3873
  if (this.cdkScrollable) {
3837
3874
  this.cdkScrollable.getElementRef().nativeElement.dispatchEvent(SCROLLBAR_SCROLL_EVENT);
3838
3875
  }
@@ -3895,7 +3932,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3895
3932
  args: ['scrollBar', { read: NgScrollBarComponent }]
3896
3933
  }], scrollbarEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollbarEnabled", required: false }] }], scrollbarInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollbarInteractive", required: false }] }], focusedElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusedElement", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], classes: [{ type: i0.Input, args: [{ isSignal: true, alias: "classes", required: false }] }], startOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "startOffset", required: false }] }], endOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "endOffset", required: false }] }], scrollbarTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollbarTheme", required: false }] }], scrollbarMinSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollbarMinSize", required: false }] }] } });
3897
3934
 
3898
- const MIN_SCROLL_TO_START_PIXELS = 10, RANGE_DISPLAY_ITEMS_END_OFFSET = 20, DEFAULT_INIT_ITERATIONS = 30, ROLE_LIST = 'list', ROLE_LIST_BOX = 'listbox', ITEM_ID = 'item-id', ITEM_CONTAINER = 'ngvl-item__container', READY_TO_START = 'ready-to-start', WAIT_FOR_PREPARATION = 'wait-for-preparation';
3935
+ const MIN_SCROLL_TO_START_PIXELS = 10, RANGE_DISPLAY_ITEMS_END_OFFSET = 20, MIN_PREPARE_ITERATIONS = 20, EMPTY_SCROLL_STATE_VERSION = '-1', ROLE_LIST = 'list', ROLE_LIST_BOX = 'listbox', ITEM_ID = 'item-id', ITEM_CONTAINER = 'ngvl-item__container', READY_TO_START = 'ready-to-start', WAIT_FOR_PREPARATION = 'wait-for-preparation';
3936
+ const getScrollStateVersion = (scrollSize, cacheVersion) => {
3937
+ return `${scrollSize}_${cacheVersion}`;
3938
+ };
3899
3939
  const validateScrollIteration = (value) => {
3900
3940
  return Number.isNaN(value) || (value < 0) ? 0 : value > MAX_SCROLL_TO_ITERATIONS ? MAX_SCROLL_TO_ITERATIONS : value;
3901
3941
  }, validateId = (id) => {
@@ -4419,7 +4459,8 @@ class NgVirtualListComponent {
4419
4459
  };
4420
4460
  /**
4421
4461
  * If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element.
4422
- * Ignored if the dynamicSize property is true.
4462
+ * If the dynamicSize property is true, the items in the list can have different sizes, and you must specify the itemSize property
4463
+ * to adjust the sizes of the items in the unallocated area.
4423
4464
  */
4424
4465
  itemSize = input(DEFAULT_ITEM_SIZE, ...(ngDevMode ? [{ debugName: "itemSize", ...this._itemSizeOptions }] : [{ ...this._itemSizeOptions }]));
4425
4466
  _dynamicSizeOptions = {
@@ -4433,8 +4474,9 @@ class NgVirtualListComponent {
4433
4474
  },
4434
4475
  };
4435
4476
  /**
4436
- * If true then the items in the list can have different sizes and the itemSize property is ignored.
4437
- * If false then the items in the list have a fixed size specified by the itemSize property. The default value is false.
4477
+ * If true, items in the list may have different sizes, and the itemSize property must be specified to adjust
4478
+ * the sizes of items in the unallocated area.
4479
+ * If false then the items in the list have a fixed size specified by the itemSize property. The default value is true.
4438
4480
  */
4439
4481
  dynamicSize = input(DEFAULT_DYNAMIC_SIZE, ...(ngDevMode ? [{ debugName: "dynamicSize", ...this._dynamicSizeOptions }] : [{ ...this._dynamicSizeOptions }]));
4440
4482
  _directionOptions = {
@@ -4721,6 +4763,7 @@ class NgVirtualListComponent {
4721
4763
  [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: pos, behavior,
4722
4764
  fireUpdate: true, blending: true, userAction: true, duration: this.animationParams().navigateToItem,
4723
4765
  };
4766
+ scroller.refresh(false);
4724
4767
  scroller.scrollTo(params);
4725
4768
  }
4726
4769
  }
@@ -4755,8 +4798,10 @@ class NgVirtualListComponent {
4755
4798
  $scrollTo = this._$scrollTo.asObservable();
4756
4799
  _$scroll = new Subject();
4757
4800
  $scroll = this._$scroll.asObservable();
4758
- _$userScroll = new Subject();
4759
- $userScroll = this._$userScroll.asObservable();
4801
+ _$fireUpdate = new Subject();
4802
+ $fireUpdate = this._$fireUpdate.asObservable();
4803
+ _$fireUpdateNextFrame = new Subject();
4804
+ $fireUpdateNextFrame = this._$fireUpdateNextFrame.asObservable();
4760
4805
  _onTrackBoxResetHandler = (v) => {
4761
4806
  if (v && this._scrollerComponent()?.scrollable) {
4762
4807
  this._$isResetedReachStart.next(true);
@@ -4774,9 +4819,7 @@ class NgVirtualListComponent {
4774
4819
  }
4775
4820
  }
4776
4821
  };
4777
- _$updateIteration = new BehaviorSubject$1(0);
4778
4822
  _onPreparedHandler = (v) => {
4779
- this._$updateIteration.next(0);
4780
4823
  this._$prepared.next(v);
4781
4824
  };
4782
4825
  _destroyRef = inject(DestroyRef);
@@ -4790,6 +4833,9 @@ class NgVirtualListComponent {
4790
4833
  this._service.initialize(this._trackBox);
4791
4834
  this._service.itemToFocus = this.itemToFocus;
4792
4835
  this._trackBox.displayComponents = this._displayComponents;
4836
+ this.$fireUpdateNextFrame.pipe(takeUntilDestroyed(), debounceTime(0), tap(e => {
4837
+ this._$fireUpdate.next(e);
4838
+ })).subscribe();
4793
4839
  const $scrollToItem = this.$scrollTo.pipe(takeUntilDestroyed()), $mouseDown = fromEvent(this._elementRef.nativeElement, MOUSE_DOWN).pipe(takeUntilDestroyed()), $touchStart = fromEvent(this._elementRef.nativeElement, TOUCH_START).pipe(takeUntilDestroyed());
4794
4840
  fromEvent(document, KEY_DOWN).pipe(takeUntilDestroyed(), switchMap$1(e => {
4795
4841
  return fromEvent(this._elementRef.nativeElement, FOCUS).pipe(takeUntilDestroyed(this._destroyRef), delay(0), takeUntil$1($scrollToItem), takeUntil$1($mouseDown), takeUntil$1($touchStart), tap(e => {
@@ -4817,24 +4863,28 @@ class NgVirtualListComponent {
4817
4863
  this._service.clickDistance = dist;
4818
4864
  });
4819
4865
  let prepared = false, readyToStart = false, isUserScrolling = false;
4820
- const $updateComplete = this.$update.pipe(takeUntilDestroyed(), switchMap$1(() => {
4821
- this._$updateIteration.next(this._$updateIteration.getValue() + 1);
4822
- const iterations = Math.min(DEFAULT_INIT_ITERATIONS, this.items().length || 1);
4823
- if (this._$updateIteration.getValue() <= iterations) {
4824
- const scroller = this._scrollerComponent();
4825
- if (!!scroller) {
4826
- const isVerrtical = this._isVertical, actualScrollSize = isVerrtical ? (scroller?.actualScrollHeight ?? 0) : (scroller?.actualScrollWidth ?? 0), params = {
4827
- [isVerrtical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
4828
- blending: false, fireUpdate: true, userAction: false, behavior: BEHAVIOR_INSTANT,
4829
- };
4830
- scroller.stopScrolling();
4831
- scroller.scrollTo(params);
4832
- this._service.update();
4866
+ let prevScrollStateVersion = EMPTY_SCROLL_STATE_VERSION, updateIterations = 0;
4867
+ const $updateComplete = this.$update.pipe(takeUntilDestroyed(), debounceTime(0), switchMap$1((v) => {
4868
+ if (((prevScrollStateVersion === EMPTY_SCROLL_STATE_VERSION) || (prevScrollStateVersion !== v)) &&
4869
+ (updateIterations < MIN_PREPARE_ITERATIONS)) {
4870
+ if (v !== EMPTY_SCROLL_STATE_VERSION) {
4871
+ prevScrollStateVersion = v;
4833
4872
  }
4873
+ this._trackBox.isScrollEnd = true;
4874
+ this._$fireUpdateNextFrame.next();
4834
4875
  return of(false);
4835
4876
  }
4877
+ if (prevScrollStateVersion === v) {
4878
+ this._trackBox.isScrollEnd = true;
4879
+ if (updateIterations < MIN_PREPARE_ITERATIONS) {
4880
+ updateIterations++;
4881
+ this._$fireUpdateNextFrame.next();
4882
+ return of(false);
4883
+ }
4884
+ }
4885
+ prevScrollStateVersion = v;
4836
4886
  return of(true);
4837
- }), debounceTime(200), distinctUntilChanged()), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $dynamicSize = toObservable(this.dynamicSize);
4887
+ }), debounceTime(0), filter$1(v => !!v)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $dynamicSize = toObservable(this.dynamicSize);
4838
4888
  const $snapScrollToBottom = toObservable(this.snapScrollToBottom), $waitForPreparation = toObservable(this.waitForPreparation);
4839
4889
  combineLatest([$dynamicSize, $snapScrollToBottom, $waitForPreparation]).pipe(takeUntilDestroyed(), switchMap$1(([dynamicSize, snapScrollToBottom, waitForPreparation]) => {
4840
4890
  if (!dynamicSize || !snapScrollToBottom || !waitForPreparation) {
@@ -4849,7 +4899,6 @@ class NgVirtualListComponent {
4849
4899
  return of(false);
4850
4900
  }
4851
4901
  if (!!dynamicSize && !!snapScrollToBottom && !!waitForPreparation) {
4852
- this._$updateIteration.next(0);
4853
4902
  return this.$prepared.pipe(takeUntilDestroyed(this._destroyRef), distinctUntilChanged(), switchMap$1(v => {
4854
4903
  if (!v) {
4855
4904
  this._$show.next(false);
@@ -4861,14 +4910,15 @@ class NgVirtualListComponent {
4861
4910
  scrollerComponent.stopScrolling();
4862
4911
  }
4863
4912
  this.classes.set({ prepared: v, [READY_TO_START]: false, [WAIT_FOR_PREPARATION]: false });
4864
- this._$updateIteration.next(0);
4865
4913
  this._service.update(true);
4866
4914
  return of(false);
4867
4915
  }
4868
4916
  return of(true).pipe(takeUntilDestroyed(this._destroyRef), switchMap$1(v => {
4869
4917
  const waitForPreparation = this.waitForPreparation();
4870
4918
  if (waitForPreparation) {
4871
- return $updateComplete.pipe(takeUntilDestroyed(this._destroyRef), filter$1(v => !!v), take(1), tap(() => {
4919
+ prevScrollStateVersion = EMPTY_SCROLL_STATE_VERSION;
4920
+ updateIterations = 0;
4921
+ return $updateComplete.pipe(takeUntilDestroyed(this._destroyRef), take(1), tap(() => {
4872
4922
  prepared = readyToStart = true;
4873
4923
  const waitForPreparation = this.waitForPreparation(), scrollerComponent = this._scrollerComponent();
4874
4924
  if (scrollerComponent) {
@@ -5047,9 +5097,8 @@ class NgVirtualListComponent {
5047
5097
  const { snapScrollToBottom, bounds, listBounds, scrollEndOffset, items, itemConfigMap, scrollSize, itemSize, bufferSize, maxBufferSize, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion, } = params;
5048
5098
  const scroller = this._scrollerComponent();
5049
5099
  if (scroller) {
5050
- let actualScrollSize = snapScrollToBottom && !prepared ?
5051
- (isVertical ? scroller.actualScrollHeight ?? 0 : scroller.actualScrollWidth ?? 0) :
5052
- (isVertical ? scroller.actualScrollTop ?? 0 : scroller.actualScrollLeft ?? 0), totalSize = 0, displayItems;
5100
+ let actualScrollSize = !prepared && snapScrollToBottom ? (isVertical ? scroller.scrollHeight ?? 0 : scroller.scrollWidth ?? 0) :
5101
+ (isVertical ? scroller.scrollTop ?? 0 : scroller.scrollLeft ?? 0), totalSize = 0, displayItems;
5053
5102
  const { width, height, x, y } = bounds, viewportSize = (isVertical ? height : width);
5054
5103
  let scrollLength = Math.round(this._totalSize()) ?? 0, actualScrollLength = Math.round(scrollLength === 0 ? 0 : scrollLength > viewportSize ? scrollLength - viewportSize : scrollLength), roundedMaxPosition = Math.round(actualScrollLength), scrollPosition = Math.round(actualScrollSize);
5055
5104
  const opts = {
@@ -5086,7 +5135,8 @@ class NgVirtualListComponent {
5086
5135
  this._isScrollFinished.set(scrollPosition >= roundedMaxPosition);
5087
5136
  }
5088
5137
  }
5089
- actualScrollSize = (isVertical ? scroller.actualScrollTop ?? 0 : scroller.actualScrollLeft ?? 0);
5138
+ actualScrollSize = !prepared && snapScrollToBottom ? (isVertical ? scroller.scrollHeight ?? 0 : scroller.scrollWidth ?? 0) :
5139
+ (isVertical ? scroller.scrollTop ?? 0 : scroller.scrollLeft ?? 0);
5090
5140
  const delta = this._trackBox.delta, roundedActualScrollSize = Math.round(actualScrollSize), scrollPositionAfterUpdate = actualScrollSize + delta, roundedScrollPositionAfterUpdate = Math.round(scrollPositionAfterUpdate), roundedMaxPositionAfterUpdate = Math.round(totalSize - viewportSize);
5091
5141
  if (this._isSnappingMethodAdvanced) {
5092
5142
  this.updateRegularRenderer();
@@ -5102,12 +5152,14 @@ class NgVirtualListComponent {
5102
5152
  }
5103
5153
  this._trackBox.isScrollEnd = true;
5104
5154
  if (roundedMaxPositionAfterUpdate > 0) {
5105
- const diff = roundedMaxPositionAfterUpdate - roundedScrollPositionAfterUpdate, snapToEndTransitionInstantOffset = this.snapToEndTransitionInstantOffset() || viewportSize, animated = prepared && readyToStart && diff >= 0 && diff <= snapToEndTransitionInstantOffset, params = {
5155
+ const params = {
5106
5156
  [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: roundedMaxPositionAfterUpdate,
5107
- fireUpdate: !(prepared && readyToStart), behavior: animated ? this.scrollBehavior() : BEHAVIOR_INSTANT,
5157
+ fireUpdate: false, behavior: BEHAVIOR_INSTANT,
5108
5158
  blending: false, duration: this.animationParams().scrollToItem,
5109
5159
  };
5110
5160
  scroller?.scrollTo?.(params);
5161
+ this._$update.next(getScrollStateVersion(this._isVertical ? scroller.scrollTop : scroller.scrollLeft, cacheVersion));
5162
+ return;
5111
5163
  }
5112
5164
  }
5113
5165
  else if (roundedActualScrollSize !== roundedScrollPositionAfterUpdate && scrollPositionAfterUpdate > 0) {
@@ -5119,18 +5171,20 @@ class NgVirtualListComponent {
5119
5171
  if (this._scrollSize() !== scrollPositionAfterUpdate) {
5120
5172
  const params = {
5121
5173
  [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollPositionAfterUpdate, blending: prepared && readyToStart,
5122
- fireUpdate: !(prepared && readyToStart), behavior: BEHAVIOR_INSTANT, duration: this.animationParams().scrollToItem,
5174
+ fireUpdate: false, behavior: BEHAVIOR_INSTANT, duration: this.animationParams().scrollToItem,
5123
5175
  };
5124
5176
  scroller.scrollTo(params);
5177
+ this._$update.next(getScrollStateVersion(this._isVertical ? scroller.scrollTop : scroller.scrollLeft, cacheVersion));
5178
+ return;
5125
5179
  }
5126
5180
  }
5127
5181
  if (!prepared && !readyToStart) {
5128
- this._$update.next();
5182
+ this._$update.next(getScrollStateVersion(this._isVertical ? scroller.scrollTop : scroller.scrollLeft, cacheVersion));
5129
5183
  }
5130
5184
  }
5131
5185
  };
5132
5186
  combineLatest([$snapScrollToBottom, $bounds, $listBounds, $scrollEndOffset, $actualItems, $itemConfigMap, $scrollSize, $itemSize,
5133
- $bufferSize, $maxBufferSize, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion, this.$userScroll,
5187
+ $bufferSize, $maxBufferSize, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion, this.$fireUpdate,
5134
5188
  ]).pipe(takeUntilDestroyed(), distinctUntilChanged(), tap(([snapScrollToBottom, bounds, listBounds, scrollEndOffset, items, itemConfigMap, scrollSize, itemSize, bufferSize, maxBufferSize, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
5135
5189
  update({
5136
5190
  snapScrollToBottom, bounds, listBounds, scrollEndOffset, items, itemConfigMap, scrollSize, itemSize,
@@ -5207,7 +5261,7 @@ class NgVirtualListComponent {
5207
5261
  this.onScroll.emit(event);
5208
5262
  this._$scroll.next(event);
5209
5263
  if (userAction && !this.dynamicSize()) {
5210
- this._$userScroll.next(event);
5264
+ this._$fireUpdate.next(event);
5211
5265
  }
5212
5266
  }
5213
5267
  })).subscribe();
@@ -5229,7 +5283,7 @@ class NgVirtualListComponent {
5229
5283
  this.onScrollEnd.emit(event);
5230
5284
  this._$scroll.next(event);
5231
5285
  if (userAction && !this.dynamicSize()) {
5232
- this._$userScroll.next(event);
5286
+ this._$fireUpdate.next(event);
5233
5287
  }
5234
5288
  }
5235
5289
  })).subscribe();
@@ -5397,7 +5451,7 @@ class NgVirtualListComponent {
5397
5451
  $collapsedIds.pipe(takeUntilDestroyed(), distinctUntilChanged(), tap(v => {
5398
5452
  this._service.setCollapsedIds(v);
5399
5453
  })).subscribe();
5400
- this._$userScroll.next({
5454
+ this._$fireUpdate.next({
5401
5455
  scrollSize: 0,
5402
5456
  scrollWeight: 0,
5403
5457
  size: 0,