ng-virtual-list 17.0.21 → 17.0.22

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.
Files changed (87) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +29 -42
  3. package/src/lib/components/ng-virtual-list-item.component.html +9 -0
  4. package/src/lib/components/ng-virtual-list-item.component.scss +17 -0
  5. package/src/lib/components/ng-virtual-list-item.component.spec.ts +23 -0
  6. package/src/lib/components/ng-virtual-list-item.component.ts +111 -0
  7. package/src/lib/const/index.ts +67 -0
  8. package/{lib/enums/direction.d.ts → src/lib/enums/direction.ts} +9 -8
  9. package/{lib/enums/directions.d.ts → src/lib/enums/directions.ts} +16 -16
  10. package/{lib/enums/index.d.ts → src/lib/enums/index.ts} +7 -4
  11. package/{lib/models/collection.model.d.ts → src/lib/models/collection.model.ts} +9 -9
  12. package/{lib/models/index.d.ts → src/lib/models/index.ts} +13 -6
  13. package/{lib/models/item.model.d.ts → src/lib/models/item.model.ts} +15 -14
  14. package/{lib/models/render-collection.model.d.ts → src/lib/models/render-collection.model.ts} +9 -9
  15. package/{lib/models/render-item-config.model.d.ts → src/lib/models/render-item-config.model.ts} +33 -33
  16. package/{lib/models/render-item.model.d.ts → src/lib/models/render-item.model.ts} +29 -28
  17. package/{lib/models/scroll-direction.model.d.ts → src/lib/models/scroll-direction.model.ts} +5 -5
  18. package/{lib/models/scroll-event.model.d.ts → src/lib/models/scroll-event.model.ts} +51 -50
  19. package/{lib/models/sticky-map.model.d.ts → src/lib/models/sticky-map.model.ts} +12 -12
  20. package/src/lib/ng-virtual-list.component.html +5 -0
  21. package/src/lib/ng-virtual-list.component.scss +28 -0
  22. package/src/lib/ng-virtual-list.component.spec.ts +23 -0
  23. package/src/lib/ng-virtual-list.component.ts +543 -0
  24. package/src/lib/ng-virtual-list.module.ts +12 -0
  25. package/{lib/types/id.d.ts → src/lib/types/id.ts} +7 -7
  26. package/{lib/types/index.d.ts → src/lib/types/index.ts} +9 -4
  27. package/{lib/types/rect.d.ts → src/lib/types/rect.ts} +18 -17
  28. package/{lib/types/size.d.ts → src/lib/types/size.ts} +16 -16
  29. package/src/lib/utils/cacheMap.ts +224 -0
  30. package/src/lib/utils/debounce.ts +31 -0
  31. package/src/lib/utils/eventEmitter.ts +119 -0
  32. package/{lib/utils/index.d.ts → src/lib/utils/index.ts} +15 -7
  33. package/src/lib/utils/isDirection.ts +17 -0
  34. package/src/lib/utils/scrollEvent.ts +62 -0
  35. package/src/lib/utils/toggleClassName.ts +14 -0
  36. package/src/lib/utils/trackBox.ts +839 -0
  37. package/src/lib/utils/tracker.ts +126 -0
  38. package/{public-api.d.ts → src/public-api.ts} +8 -4
  39. package/tsconfig.lib.json +16 -0
  40. package/tsconfig.lib.prod.json +11 -0
  41. package/tsconfig.spec.json +15 -0
  42. package/esm2022/lib/components/ng-virtual-list-item.component.mjs +0 -88
  43. package/esm2022/lib/const/index.mjs +0 -35
  44. package/esm2022/lib/enums/direction.mjs +0 -2
  45. package/esm2022/lib/enums/directions.mjs +0 -18
  46. package/esm2022/lib/enums/index.mjs +0 -3
  47. package/esm2022/lib/models/collection.model.mjs +0 -3
  48. package/esm2022/lib/models/index.mjs +0 -2
  49. package/esm2022/lib/models/item.model.mjs +0 -3
  50. package/esm2022/lib/models/render-collection.model.mjs +0 -3
  51. package/esm2022/lib/models/render-item-config.model.mjs +0 -2
  52. package/esm2022/lib/models/render-item.model.mjs +0 -3
  53. package/esm2022/lib/models/scroll-direction.model.mjs +0 -2
  54. package/esm2022/lib/models/scroll-event.model.mjs +0 -2
  55. package/esm2022/lib/models/sticky-map.model.mjs +0 -2
  56. package/esm2022/lib/ng-virtual-list.component.mjs +0 -390
  57. package/esm2022/lib/ng-virtual-list.module.mjs +0 -20
  58. package/esm2022/lib/types/id.mjs +0 -2
  59. package/esm2022/lib/types/index.mjs +0 -2
  60. package/esm2022/lib/types/rect.mjs +0 -2
  61. package/esm2022/lib/types/size.mjs +0 -2
  62. package/esm2022/lib/utils/cacheMap.mjs +0 -168
  63. package/esm2022/lib/utils/debounce.mjs +0 -31
  64. package/esm2022/lib/utils/eventEmitter.mjs +0 -105
  65. package/esm2022/lib/utils/index.mjs +0 -8
  66. package/esm2022/lib/utils/isDirection.mjs +0 -15
  67. package/esm2022/lib/utils/scrollEvent.mjs +0 -42
  68. package/esm2022/lib/utils/toggleClassName.mjs +0 -15
  69. package/esm2022/lib/utils/trackBox.mjs +0 -627
  70. package/esm2022/lib/utils/tracker.mjs +0 -93
  71. package/esm2022/ng-virtual-list.mjs +0 -5
  72. package/esm2022/public-api.mjs +0 -8
  73. package/fesm2022/ng-virtual-list.mjs +0 -1639
  74. package/fesm2022/ng-virtual-list.mjs.map +0 -1
  75. package/index.d.ts +0 -5
  76. package/lib/components/ng-virtual-list-item.component.d.ts +0 -31
  77. package/lib/const/index.d.ts +0 -33
  78. package/lib/ng-virtual-list.component.d.ts +0 -130
  79. package/lib/ng-virtual-list.module.d.ts +0 -9
  80. package/lib/utils/cacheMap.d.ts +0 -60
  81. package/lib/utils/debounce.d.ts +0 -16
  82. package/lib/utils/eventEmitter.d.ts +0 -40
  83. package/lib/utils/isDirection.d.ts +0 -8
  84. package/lib/utils/scrollEvent.d.ts +0 -39
  85. package/lib/utils/toggleClassName.d.ts +0 -7
  86. package/lib/utils/trackBox.d.ts +0 -176
  87. package/lib/utils/tracker.d.ts +0 -44
@@ -0,0 +1,543 @@
1
+ import {
2
+ AfterViewInit, ChangeDetectionStrategy, Component, ComponentRef, ElementRef, inject, input,
3
+ OnDestroy, OnInit, output, signal, TemplateRef, ViewChild, viewChild, ViewContainerRef, ViewEncapsulation,
4
+ WritableSignal,
5
+ } from '@angular/core';
6
+ import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
7
+ import { BehaviorSubject, combineLatest, distinctUntilChanged, filter, map, Observable, of, switchMap, tap } from 'rxjs';
8
+ import { NgVirtualListItemComponent } from './components/ng-virtual-list-item.component';
9
+ import {
10
+ BEHAVIOR_AUTO, BEHAVIOR_INSTANT, CLASS_LIST_HORIZONTAL, CLASS_LIST_VERTICAL, DEFAULT_DIRECTION, DEFAULT_DYNAMIC_SIZE,
11
+ DEFAULT_ENABLED_BUFFER_OPTIMIZATION, DEFAULT_ITEM_SIZE, DEFAULT_ITEMS_OFFSET, DEFAULT_SNAP, HEIGHT_PROP_NAME, LEFT_PROP_NAME,
12
+ MAX_SCROLL_TO_ITERATIONS, PX, SCROLL, SCROLL_END, TOP_PROP_NAME, TRACK_BY_PROPERTY_NAME, WIDTH_PROP_NAME,
13
+ } from './const';
14
+ import { IScrollEvent, IVirtualListCollection, IVirtualListItem, IVirtualListStickyMap } from './models';
15
+ import { Id, ISize } from './types';
16
+ import { IRenderVirtualListCollection } from './models/render-collection.model';
17
+ import { Direction, Directions } from './enums';
18
+ import { ScrollEvent, TrackBox, isDirection, toggleClassName } from './utils';
19
+ import { IGetItemPositionOptions, IUpdateCollectionOptions, TRACK_BOX_CHANGE_EVENT_NAME } from './utils/trackBox';
20
+
21
+ /**
22
+ * Virtual list component.
23
+ * Maximum performance for extremely large lists.
24
+ * It is based on algorithms for virtualization of screen objects.
25
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/ng-virtual-list.component.ts
26
+ * @author Evgenii Grebennikov
27
+ * @email djonnyx@gmail.com
28
+ */
29
+ @Component({
30
+ selector: 'ng-virtual-list',
31
+ standalone: false,
32
+ templateUrl: './ng-virtual-list.component.html',
33
+ styleUrl: './ng-virtual-list.component.scss',
34
+ changeDetection: ChangeDetectionStrategy.OnPush,
35
+ encapsulation: ViewEncapsulation.ShadowDom,
36
+ })
37
+ export class NgVirtualListComponent implements AfterViewInit, OnInit, OnDestroy {
38
+ private static __nextId: number = 0;
39
+
40
+ private _id: number = NgVirtualListComponent.__nextId;
41
+ /**
42
+ * Readonly. Returns the unique identifier of the component.
43
+ */
44
+ get id() { return this._id; }
45
+
46
+ @ViewChild('renderersContainer', { read: ViewContainerRef })
47
+ protected _listContainerRef: ViewContainerRef | undefined;
48
+
49
+ protected _container = viewChild<ElementRef<HTMLDivElement>>('container');
50
+
51
+ protected _list = viewChild<ElementRef<HTMLUListElement>>('list');
52
+
53
+ /**
54
+ * Fires when the list has been scrolled.
55
+ */
56
+ onScroll = output<IScrollEvent>();
57
+
58
+ /**
59
+ * Fires when the list has completed scrolling.
60
+ */
61
+ onScrollEnd = output<IScrollEvent>();
62
+
63
+ private _itemsOptions = {
64
+ transform: (v: IVirtualListCollection | undefined) => {
65
+ this._trackBox.resetCollection(v, this.itemSize());
66
+ return v;
67
+ },
68
+ } as any;
69
+
70
+ /**
71
+ * Collection of list items.
72
+ */
73
+ items = input.required<IVirtualListCollection>({
74
+ ...this._itemsOptions,
75
+ });
76
+
77
+ /**
78
+ * Determines whether elements will snap. Default value is "true".
79
+ */
80
+ snap = input<boolean>(DEFAULT_SNAP);
81
+
82
+ /**
83
+ * Experimental!
84
+ * Enables buffer optimization.
85
+ * Can only be used if items in the collection are not added or updated. Otherwise, artifacts in the form of twitching of the scroll area are possible.
86
+ * Works only if the property dynamic = true
87
+ */
88
+ enabledBufferOptimization = input<boolean>(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
89
+
90
+ /**
91
+ * Rendering element template.
92
+ */
93
+ itemRenderer = input.required<TemplateRef<any>>();
94
+
95
+ /**
96
+ * Dictionary zIndex by id of the list element. If the value is not set or equal to 0,
97
+ * then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element.
98
+ */
99
+ stickyMap = input<IVirtualListStickyMap>({});
100
+
101
+ private _itemSizeOptions = {
102
+ transform: (v: number | undefined) => {
103
+ if (v === undefined) {
104
+ return DEFAULT_ITEM_SIZE;
105
+ }
106
+ const val = Number(v);
107
+ return Number.isNaN(val) || val <= 0 ? DEFAULT_ITEM_SIZE : val;
108
+ },
109
+ } as any;
110
+
111
+ /**
112
+ * If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element.
113
+ * Ignored if the dynamicSize property is true.
114
+ */
115
+ itemSize = input<number>(DEFAULT_ITEM_SIZE, { ...this._itemSizeOptions });
116
+
117
+ /**
118
+ * If true then the items in the list can have different sizes and the itemSize property is ignored.
119
+ * If false then the items in the list have a fixed size specified by the itemSize property. The default value is false.
120
+ */
121
+ dynamicSize = input(DEFAULT_DYNAMIC_SIZE);
122
+
123
+ /**
124
+ * Determines the direction in which elements are placed. Default value is "vertical".
125
+ */
126
+ direction = input<Direction>(DEFAULT_DIRECTION);
127
+
128
+ /**
129
+ * Number of elements outside the scope of visibility. Default value is 2.
130
+ */
131
+ itemsOffset = input<number>(DEFAULT_ITEMS_OFFSET);
132
+
133
+ private _isVertical = this.getIsVertical();
134
+
135
+ protected _displayComponents: Array<ComponentRef<NgVirtualListItemComponent>> = [];
136
+
137
+ protected _bounds = signal<DOMRect | null>(null);
138
+
139
+ protected _scrollSize = signal<number>(0);
140
+
141
+ private _resizeObserver: ResizeObserver | null = null;
142
+
143
+ private _onResizeHandler = () => {
144
+ this._bounds.set(this._container()?.nativeElement?.getBoundingClientRect() ?? null);
145
+ }
146
+
147
+ private _onScrollHandler = (e?: Event) => {
148
+ this.clearScrollToRepeatExecutionTimeout();
149
+
150
+ const container = this._container()?.nativeElement;
151
+ if (container) {
152
+ const scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft),
153
+ actualScrollSize = scrollSize;
154
+ this._scrollSize.set(actualScrollSize);
155
+ }
156
+ }
157
+
158
+ private _elementRef = inject(ElementRef<HTMLDivElement>);
159
+
160
+ private _initialized!: WritableSignal<boolean>;
161
+
162
+ readonly $initialized!: Observable<boolean>;
163
+
164
+ /**
165
+ * The name of the property by which tracking is performed
166
+ */
167
+ trackBy = input<string>(TRACK_BY_PROPERTY_NAME);
168
+
169
+ /**
170
+ * Dictionary of element sizes by their id
171
+ */
172
+ private _trackBox = new TrackBox(this.trackBy());
173
+
174
+ private _onTrackBoxChangeHandler = (v: number) => {
175
+ this._$cacheVersion.next(v);
176
+ }
177
+
178
+ private _$cacheVersion = new BehaviorSubject<number>(-1);
179
+ get $cacheVersion() { return this._$cacheVersion.asObservable(); }
180
+
181
+ constructor() {
182
+ NgVirtualListComponent.__nextId = NgVirtualListComponent.__nextId + 1 === Number.MAX_SAFE_INTEGER
183
+ ? 0 : NgVirtualListComponent.__nextId + 1;
184
+ this._id = NgVirtualListComponent.__nextId;
185
+
186
+ this._initialized = signal<boolean>(false);
187
+ this.$initialized = toObservable(this._initialized);
188
+
189
+ this._trackBox.displayComponents = this._displayComponents;
190
+
191
+ const $trackBy = toObservable(this.trackBy);
192
+
193
+ $trackBy.pipe(
194
+ takeUntilDestroyed(),
195
+ tap(v => {
196
+ this._trackBox.trackingPropertyName = v;
197
+ }),
198
+ ).subscribe();
199
+
200
+ const $bounds = toObservable(this._bounds).pipe(
201
+ filter(b => !!b),
202
+ ), $items = toObservable(this.items).pipe(
203
+ map(i => !i ? [] : i),
204
+ ), $scrollSize = toObservable(this._scrollSize),
205
+ $itemSize = toObservable(this.itemSize).pipe(
206
+ map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v),
207
+ ),
208
+ $itemsOffset = toObservable(this.itemsOffset).pipe(
209
+ map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v),
210
+ ),
211
+ $stickyMap = toObservable(this.stickyMap).pipe(
212
+ map(v => !v ? {} : v),
213
+ ),
214
+ $snap = toObservable(this.snap),
215
+ $isVertical = toObservable(this.direction).pipe(
216
+ map(v => this.getIsVertical(v || DEFAULT_DIRECTION)),
217
+ ),
218
+ $dynamicSize = toObservable(this.dynamicSize),
219
+ $enabledBufferOptimization = toObservable(this.enabledBufferOptimization),
220
+ $cacheVersion = this.$cacheVersion;
221
+
222
+ $isVertical.pipe(
223
+ takeUntilDestroyed(),
224
+ tap(v => {
225
+ this._isVertical = v;
226
+ const el: HTMLElement = this._elementRef.nativeElement;
227
+ toggleClassName(el, v ? CLASS_LIST_VERTICAL : CLASS_LIST_HORIZONTAL, true);
228
+ }),
229
+ ).subscribe();
230
+
231
+ $dynamicSize.pipe(
232
+ takeUntilDestroyed(),
233
+ tap(dynamicSize => {
234
+ this.listenCacheChangesIfNeed(dynamicSize);
235
+ })
236
+ ).subscribe();
237
+
238
+ combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
239
+ $itemsOffset, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
240
+ ]).pipe(
241
+ takeUntilDestroyed(),
242
+ distinctUntilChanged(),
243
+ filter(([initialized]) => !!initialized),
244
+ switchMap(([,
245
+ bounds, items, stickyMap, _scrollSize, itemSize,
246
+ itemsOffset, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,
247
+ ]) => {
248
+ const { width, height } = bounds as DOMRect, delta = this._trackBox.delta,
249
+ scrollSize = (this._isVertical ? this._container()?.nativeElement.scrollTop ?? 0 : this._container()?.nativeElement.scrollLeft) ?? 0,
250
+ actualScrollSize = scrollSize + delta;
251
+ const opts: IUpdateCollectionOptions<IVirtualListItem, IVirtualListCollection> = {
252
+ bounds: { width, height }, dynamicSize, isVertical, itemSize,
253
+ itemsOffset, scrollSize: actualScrollSize, snap, enabledBufferOptimization,
254
+ };
255
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, opts);
256
+
257
+ this.resetBoundsSize(isVertical, totalSize);
258
+
259
+ this.createDisplayComponentsIfNeed(displayItems);
260
+
261
+ this.tracking();
262
+
263
+ const container = this._container();
264
+
265
+ if (container) {
266
+ this._trackBox.clearDelta();
267
+
268
+ if (scrollSize !== actualScrollSize) {
269
+ const params: ScrollToOptions = {
270
+ [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
271
+ behavior: BEHAVIOR_INSTANT
272
+ };
273
+
274
+ container.nativeElement.scrollTo(params);
275
+ }
276
+ }
277
+
278
+ return of(displayItems);
279
+ }),
280
+ ).subscribe();
281
+
282
+ combineLatest([this.$initialized, toObservable(this.itemRenderer)]).pipe(
283
+ takeUntilDestroyed(),
284
+ distinctUntilChanged(),
285
+ filter(([initialized]) => !!initialized),
286
+ tap(([, itemRenderer]) => {
287
+ this.resetRenderers(itemRenderer);
288
+ })
289
+ )
290
+ }
291
+
292
+ /** @internal */
293
+ ngOnInit() {
294
+ this._initialized.set(true);
295
+ }
296
+
297
+ private listenCacheChangesIfNeed(value: boolean) {
298
+ if (value) {
299
+ if (!this._trackBox.hasEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler)) {
300
+ this._trackBox.addEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler);
301
+ }
302
+ } else {
303
+ if (this._trackBox.hasEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler)) {
304
+ this._trackBox.removeEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler);
305
+ }
306
+ }
307
+ }
308
+
309
+ private getIsVertical(d?: Direction) {
310
+ const dir = d || this.direction();
311
+ return isDirection(dir, Directions.VERTICAL);
312
+ }
313
+
314
+ private _componentsResizeObserver = new ResizeObserver(() => {
315
+ this._trackBox.changes();
316
+ });
317
+
318
+ private createDisplayComponentsIfNeed(displayItems: IRenderVirtualListCollection | null) {
319
+ if (!displayItems || !this._listContainerRef) {
320
+ this._trackBox.setDisplayObjectIndexMapById({});
321
+ return;
322
+ }
323
+
324
+ this._trackBox.items = displayItems;
325
+
326
+ const _listContainerRef = this._listContainerRef;
327
+
328
+ const maxLength = displayItems.length, components = this._displayComponents;
329
+
330
+ while (components.length < maxLength) {
331
+ if (_listContainerRef) {
332
+ const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
333
+ components.push(comp);
334
+
335
+ this._componentsResizeObserver.observe(comp.instance.element);
336
+ }
337
+ }
338
+
339
+ this.resetRenderers();
340
+ }
341
+
342
+ private resetRenderers(itemRenderer?: TemplateRef<HTMLElement>) {
343
+ const doMap: { [id: number]: number } = {};
344
+ for (let i = 0, l = this._displayComponents.length; i < l; i++) {
345
+ const item = this._displayComponents[i];
346
+ item.instance.renderer = itemRenderer || this.itemRenderer();
347
+ doMap[item.instance.id] = i;
348
+ }
349
+
350
+ this._trackBox.setDisplayObjectIndexMapById(doMap);
351
+ }
352
+
353
+ /**
354
+ * Tracking by id
355
+ */
356
+ protected tracking() {
357
+ this._trackBox.track();
358
+ }
359
+
360
+ private resetBoundsSize(isVertical: boolean, totalSize: number) {
361
+ const l = this._list();
362
+ if (l) {
363
+ l.nativeElement.style[isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME] = `${totalSize}${PX}`;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Returns the bounds of an element with a given id
369
+ */
370
+ getItemBounds(id: Id): ISize | undefined {
371
+ return this._trackBox.getItemBounds(id);
372
+ }
373
+
374
+ /**
375
+ * The method scrolls the list to the element with the given id and returns the value of the scrolled area.
376
+ * Behavior accepts the values ​​"auto", "instant" and "smooth".
377
+ */
378
+ scrollTo(id: Id, behavior: ScrollBehavior = BEHAVIOR_AUTO) {
379
+ this.scrollToExecutor(id, behavior);
380
+ }
381
+
382
+ private _scrollToRepeatExecutionTimeout: any;
383
+
384
+ private clearScrollToRepeatExecutionTimeout() {
385
+ clearTimeout(this._scrollToRepeatExecutionTimeout);
386
+ }
387
+
388
+ protected scrollToExecutor(id: Id, behavior: ScrollBehavior, iteration: number = 0, isLastIteration = false) {
389
+ const items = this.items();
390
+ if (!items || !items.length) {
391
+ return;
392
+ }
393
+
394
+ const dynamicSize = this.dynamicSize(), container = this._container(), itemSize = this.itemSize();
395
+ if (container) {
396
+ this.clearScrollToRepeatExecutionTimeout();
397
+
398
+ if (dynamicSize) {
399
+ if (container) {
400
+ container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
401
+ }
402
+
403
+ const { width, height } = this._bounds() || { width: 0, height: 0 },
404
+ stickyMap = this.stickyMap(), items = this.items(), isVertical = this._isVertical, delta = this._trackBox.delta,
405
+ opts: IGetItemPositionOptions<IVirtualListItem, IVirtualListCollection> = {
406
+ bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
407
+ itemsOffset: this.itemsOffset(), scrollSize: (isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft) + delta,
408
+ snap: this.snap(), fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization(),
409
+ },
410
+ scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts),
411
+ params: ScrollToOptions = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
412
+ this._trackBox.clearDelta();
413
+
414
+ if (container) {
415
+ const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
416
+ ...opts, scrollSize, fromItemId: isLastIteration ? undefined : id,
417
+ }), delta = this._trackBox.delta;
418
+
419
+ this._trackBox.clearDelta();
420
+
421
+ let actualScrollSize = scrollSize + delta;
422
+
423
+ this.resetBoundsSize(isVertical, totalSize);
424
+
425
+ this.createDisplayComponentsIfNeed(displayItems);
426
+
427
+ this.tracking();
428
+
429
+ const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize: actualScrollSize, fromItemId: id });
430
+
431
+ const notChanged = actualScrollSize === _scrollSize;
432
+
433
+ if (!notChanged || iteration < MAX_SCROLL_TO_ITERATIONS) {
434
+ this.clearScrollToRepeatExecutionTimeout();
435
+ this._scrollToRepeatExecutionTimeout = setTimeout(() => {
436
+ this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1, notChanged);
437
+ });
438
+ } else {
439
+ this._scrollSize.set(actualScrollSize);
440
+
441
+ container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
442
+ }
443
+ }
444
+
445
+ container.nativeElement.scrollTo(params);
446
+
447
+ this._scrollSize.set(scrollSize);
448
+ } else {
449
+ const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize();
450
+ const params: ScrollToOptions = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
451
+ container.nativeElement.scrollTo(params);
452
+ }
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Scrolls the scroll area to the desired element with the specified ID.
458
+ */
459
+ scrollToEnd(behavior: ScrollBehavior = BEHAVIOR_INSTANT) {
460
+ const items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0];
461
+ this.scrollTo(latItem.id, behavior);
462
+ }
463
+
464
+ private _onContainerScrollHandler = (e: Event) => {
465
+ const containerEl = this._container();
466
+ if (containerEl) {
467
+ const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
468
+ this._trackBox.deltaDirection = this._scrollSize() > scrollSize ? -1 : this._scrollSize() < scrollSize ? 1 : 0;
469
+
470
+ const event = new ScrollEvent({
471
+ direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
472
+ list: this._list()!.nativeElement, delta: this._trackBox.delta,
473
+ scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
474
+ });
475
+
476
+ this.onScroll.emit(event);
477
+ }
478
+ }
479
+
480
+ private _onContainerScrollEndHandler = (e: Event) => {
481
+ const containerEl = this._container();
482
+ if (containerEl) {
483
+ const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
484
+ this._trackBox.deltaDirection = this._scrollSize() > scrollSize ? -1 : 0;
485
+
486
+ const event = new ScrollEvent({
487
+ direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
488
+ list: this._list()!.nativeElement, delta: this._trackBox.delta,
489
+ scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
490
+ });
491
+
492
+ this.onScrollEnd.emit(event);
493
+ }
494
+ }
495
+
496
+ /** @internal */
497
+ ngAfterViewInit(): void {
498
+ const containerEl = this._container();
499
+ if (containerEl) {
500
+ // for direction calculation
501
+ containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
502
+ containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
503
+
504
+ containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
505
+
506
+ this._resizeObserver = new ResizeObserver(this._onResizeHandler);
507
+ this._resizeObserver.observe(containerEl.nativeElement);
508
+
509
+ this._onResizeHandler();
510
+ }
511
+ }
512
+
513
+ /** @internal */
514
+ ngOnDestroy(): void {
515
+ this.clearScrollToRepeatExecutionTimeout();
516
+
517
+ if (this._trackBox) {
518
+ this._trackBox.dispose();
519
+ }
520
+
521
+ const containerEl = this._container();
522
+ if (containerEl) {
523
+ containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
524
+ containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
525
+ containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
526
+
527
+ if (this._componentsResizeObserver) {
528
+ this._componentsResizeObserver.disconnect();
529
+ }
530
+
531
+ if (this._resizeObserver) {
532
+ this._resizeObserver.disconnect();
533
+ }
534
+ }
535
+
536
+ if (this._displayComponents) {
537
+ while (this._displayComponents.length > 0) {
538
+ const comp = this._displayComponents.pop();
539
+ comp?.destroy();
540
+ }
541
+ }
542
+ }
543
+ }
@@ -0,0 +1,12 @@
1
+ import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { NgVirtualListComponent } from './ng-virtual-list.component';
4
+ import { NgVirtualListItemComponent } from './components/ng-virtual-list-item.component';
5
+
6
+ @NgModule({
7
+ declarations: [NgVirtualListComponent, NgVirtualListItemComponent],
8
+ exports: [NgVirtualListComponent],
9
+ imports: [CommonModule],
10
+ schemas: [NO_ERRORS_SCHEMA],
11
+ })
12
+ export class NgVirtualListModule { }
@@ -1,7 +1,7 @@
1
- /**
2
- * Identifier type
3
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/types/id.ts
4
- * @author Evgenii Grebennikov
5
- * @email djonnyx@gmail.com
6
- */
7
- export type Id = string | number;
1
+ /**
2
+ * Identifier type
3
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/types/id.ts
4
+ * @author Evgenii Grebennikov
5
+ * @email djonnyx@gmail.com
6
+ */
7
+ export type Id = string | number;
@@ -1,4 +1,9 @@
1
- import { Id } from './id';
2
- import { ISize } from './size';
3
- import { IRect } from './rect';
4
- export type { Id, ISize, IRect, };
1
+ import { Id } from './id';
2
+ import { ISize } from './size';
3
+ import { IRect } from './rect';
4
+
5
+ export type {
6
+ Id,
7
+ ISize,
8
+ IRect,
9
+ }
@@ -1,17 +1,18 @@
1
- import { ISize } from "./size";
2
- /**
3
- * Rectangular area interface
4
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/types/rect.ts
5
- * @author Evgenii Grebennikov
6
- * @email djonnyx@gmail.com
7
- */
8
- export interface IRect extends ISize {
9
- /**
10
- * X coordinate.
11
- */
12
- x: number;
13
- /**
14
- * Y coordinate.
15
- */
16
- y: number;
17
- }
1
+ import { ISize } from "./size";
2
+
3
+ /**
4
+ * Rectangular area interface
5
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/types/rect.ts
6
+ * @author Evgenii Grebennikov
7
+ * @email djonnyx@gmail.com
8
+ */
9
+ export interface IRect extends ISize {
10
+ /**
11
+ * X coordinate.
12
+ */
13
+ x: number;
14
+ /**
15
+ * Y coordinate.
16
+ */
17
+ y: number;
18
+ }
@@ -1,16 +1,16 @@
1
- /**
2
- * Area area Interface
3
- * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/types/size.ts
4
- * @author Evgenii Grebennikov
5
- * @email djonnyx@gmail.com
6
- */
7
- export interface ISize {
8
- /**
9
- * Width value.
10
- */
11
- width: number;
12
- /**
13
- * Height value.
14
- */
15
- height: number;
16
- }
1
+ /**
2
+ * Area area Interface
3
+ * @link https://github.com/DjonnyX/ng-virtual-list/blob/17.x/projects/ng-virtual-list/src/lib/types/size.ts
4
+ * @author Evgenii Grebennikov
5
+ * @email djonnyx@gmail.com
6
+ */
7
+ export interface ISize {
8
+ /**
9
+ * Width value.
10
+ */
11
+ width: number;
12
+ /**
13
+ * Height value.
14
+ */
15
+ height: number;
16
+ }