ng-virtual-list 0.5.2 → 0.6.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 +3 -2
- package/fesm2022/ng-virtual-list.mjs +540 -83
- package/fesm2022/ng-virtual-list.mjs.map +1 -1
- package/lib/components/ng-virtual-list-item.component.d.ts +2 -0
- package/lib/const/index.d.ts +2 -0
- package/lib/models/render-item-config.model.d.ts +1 -0
- package/lib/ng-virtual-list.component.d.ts +17 -9
- package/lib/utils/cacheMap.d.ts +31 -0
- package/lib/utils/eventEmitter.d.ts +37 -0
- package/lib/utils/index.d.ts +3 -1
- package/lib/utils/trackBox.d.ts +78 -0
- package/lib/utils/tracker.d.ts +38 -0
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { signal, inject, ElementRef, ChangeDetectionStrategy, Component, viewChi
|
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
-
import {
|
|
6
|
+
import { BehaviorSubject, filter, map, tap, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Virtual list item component
|
|
@@ -23,18 +23,8 @@ class NgVirtualListItemComponent {
|
|
|
23
23
|
if (this._data === v) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
this._data = v;
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
itemRenderer = signal(undefined);
|
|
30
|
-
set renderer(v) {
|
|
31
|
-
this.itemRenderer.set(v);
|
|
32
|
-
}
|
|
33
|
-
_elementRef = inject((ElementRef));
|
|
34
|
-
constructor() {
|
|
35
|
-
this._id = NgVirtualListItemComponent.__nextId = NgVirtualListItemComponent.__nextId === Number.MAX_SAFE_INTEGER
|
|
36
|
-
? 0 : NgVirtualListItemComponent.__nextId + 1;
|
|
37
|
-
toObservable(this.data).pipe(takeUntilDestroyed(), filter(data => !!data), tap(data => {
|
|
26
|
+
const data = this._data = v;
|
|
27
|
+
if (data) {
|
|
38
28
|
const styles = this._elementRef.nativeElement.style;
|
|
39
29
|
styles.zIndex = data.config.sticky;
|
|
40
30
|
if (data.config.snapped) {
|
|
@@ -45,9 +35,23 @@ class NgVirtualListItemComponent {
|
|
|
45
35
|
styles.position = 'absolute';
|
|
46
36
|
styles.transform = `translate3d(${data.config.isVertical ? 0 : data.measures.x}px, ${data.config.isVertical ? data.measures.y : 0}px , 0)`;
|
|
47
37
|
}
|
|
48
|
-
styles.height = data.config.isVertical ? `${data.measures.height}px` : '100%';
|
|
49
|
-
styles.width = data.config.isVertical ? '100%' : `${data.measures.width}px`;
|
|
50
|
-
}
|
|
38
|
+
styles.height = data.config.isVertical ? data.config.dynamic ? 'auto' : `${data.measures.height}px` : '100%';
|
|
39
|
+
styles.width = data.config.isVertical ? '100%' : data.config.dynamic ? 'auto' : `${data.measures.width}px`;
|
|
40
|
+
}
|
|
41
|
+
this.data.set(v);
|
|
42
|
+
}
|
|
43
|
+
itemRenderer = signal(undefined);
|
|
44
|
+
set renderer(v) {
|
|
45
|
+
this.itemRenderer.set(v);
|
|
46
|
+
}
|
|
47
|
+
_elementRef = inject((ElementRef));
|
|
48
|
+
constructor() {
|
|
49
|
+
this._id = NgVirtualListItemComponent.__nextId = NgVirtualListItemComponent.__nextId === Number.MAX_SAFE_INTEGER
|
|
50
|
+
? 0 : NgVirtualListItemComponent.__nextId + 1;
|
|
51
|
+
}
|
|
52
|
+
getBounds() {
|
|
53
|
+
const el = this._elementRef.nativeElement, { width, height, left, top } = el.getBoundingClientRect();
|
|
54
|
+
return { width, height, x: left, y: top };
|
|
51
55
|
}
|
|
52
56
|
showIfNeed() {
|
|
53
57
|
const styles = this._elementRef.nativeElement.style;
|
|
@@ -84,6 +88,8 @@ const DEFAULT_ITEMS_OFFSET = 2;
|
|
|
84
88
|
const DEFAULT_LIST_SIZE = 400;
|
|
85
89
|
const DEFAULT_SNAP = false;
|
|
86
90
|
const DEFAULT_SNAP_TO_ITEM = false;
|
|
91
|
+
const DEFAULT_DYNAMIC_SIZE = false;
|
|
92
|
+
const TRACK_BY_PROPERTY_NAME = 'id';
|
|
87
93
|
const DEFAULT_DIRECTION = Directions.VERTICAL;
|
|
88
94
|
const DISPLAY_OBJECTS_LENGTH_MESUREMENT_ERROR = 1;
|
|
89
95
|
|
|
@@ -105,7 +111,450 @@ const toggleClassName = (el, className, remove = false) => {
|
|
|
105
111
|
};
|
|
106
112
|
|
|
107
113
|
/**
|
|
108
|
-
*
|
|
114
|
+
* Tracks display items by property
|
|
115
|
+
* @homepage https://github.com/DjonnyX/ng-virtual-list/tree/main/projects/ng-virtual-list
|
|
116
|
+
* @author Evgenii Grebennikov
|
|
117
|
+
* @email djonnyx@gmail.com
|
|
118
|
+
*/
|
|
119
|
+
class Tracker {
|
|
120
|
+
/**
|
|
121
|
+
* display objects dictionary of indexes by id
|
|
122
|
+
*/
|
|
123
|
+
_displayObjectIndexMapById = {};
|
|
124
|
+
set displayObjectIndexMapById(v) {
|
|
125
|
+
if (this._displayObjectIndexMapById === v) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this._displayObjectIndexMapById = v;
|
|
129
|
+
}
|
|
130
|
+
get displayObjectIndexMapById() {
|
|
131
|
+
return this._displayObjectIndexMapById;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Dictionary displayItems propertyNameId by items propertyNameId
|
|
135
|
+
*/
|
|
136
|
+
_trackMap = {};
|
|
137
|
+
get trackMap() {
|
|
138
|
+
return this._trackMap;
|
|
139
|
+
}
|
|
140
|
+
_trackingPropertyName;
|
|
141
|
+
constructor(trackingPropertyName) {
|
|
142
|
+
this._trackingPropertyName = trackingPropertyName;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* tracking by propName
|
|
146
|
+
*/
|
|
147
|
+
track(items, components, afterComponentSetup) {
|
|
148
|
+
if (!items) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const idPropName = this._trackingPropertyName, untrackedItems = [...components];
|
|
152
|
+
for (let i = 0, l = items.length; i < l; i++) {
|
|
153
|
+
const item = items[i], itemTrackingProperty = item[idPropName];
|
|
154
|
+
if (this._trackMap) {
|
|
155
|
+
const diId = this._trackMap[itemTrackingProperty];
|
|
156
|
+
if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
|
|
157
|
+
const lastIndex = this._displayObjectIndexMapById[diId], el = components[lastIndex];
|
|
158
|
+
this._checkComponentProperty(el?.instance);
|
|
159
|
+
const elId = el?.instance?.[itemTrackingProperty];
|
|
160
|
+
if (el && elId === diId) {
|
|
161
|
+
const indexByUntrackedItems = untrackedItems.findIndex(v => {
|
|
162
|
+
this._checkComponentProperty(v.instance);
|
|
163
|
+
return v.instance[itemTrackingProperty] === elId;
|
|
164
|
+
});
|
|
165
|
+
if (indexByUntrackedItems > -1) {
|
|
166
|
+
el.instance.item = item;
|
|
167
|
+
if (afterComponentSetup !== undefined) {
|
|
168
|
+
afterComponentSetup(el.instance, item);
|
|
169
|
+
}
|
|
170
|
+
untrackedItems.splice(indexByUntrackedItems, 1);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
delete this._trackMap[itemTrackingProperty];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (untrackedItems.length > 0) {
|
|
178
|
+
const el = untrackedItems.shift(), item = items[i];
|
|
179
|
+
if (el) {
|
|
180
|
+
el.instance.item = item;
|
|
181
|
+
if (this._trackMap) {
|
|
182
|
+
this._checkComponentProperty(el.instance);
|
|
183
|
+
this._trackMap[itemTrackingProperty] = el.instance[itemTrackingProperty];
|
|
184
|
+
}
|
|
185
|
+
if (afterComponentSetup !== undefined) {
|
|
186
|
+
afterComponentSetup(el.instance, item);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (untrackedItems.length) {
|
|
192
|
+
throw Error('Tracking by id caused an error.');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
untrackComponentByIdProperty(component) {
|
|
196
|
+
if (!component) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const propertyIdName = this._trackingPropertyName;
|
|
200
|
+
this._checkComponentProperty(component);
|
|
201
|
+
if (this._trackMap && component[propertyIdName] !== undefined) {
|
|
202
|
+
delete this._trackMap[propertyIdName];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
_checkComponentProperty(component) {
|
|
206
|
+
if (!component) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const propertyIdName = this._trackingPropertyName;
|
|
210
|
+
try {
|
|
211
|
+
component[propertyIdName];
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
throw Error(`Property ${propertyIdName} does not exist.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
dispose() {
|
|
218
|
+
this._trackMap = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Simple event emitter
|
|
224
|
+
* @homepage https://github.com/DjonnyX/ng-virtual-list/tree/main/projects/ng-virtual-list
|
|
225
|
+
* @author Evgenii Grebennikov
|
|
226
|
+
* @email djonnyx@gmail.com
|
|
227
|
+
*/
|
|
228
|
+
class EventEmitter {
|
|
229
|
+
_listeners = {};
|
|
230
|
+
_disposed = false;
|
|
231
|
+
constructor() { }
|
|
232
|
+
/**
|
|
233
|
+
* Emits the event
|
|
234
|
+
*/
|
|
235
|
+
dispatch(event, ...args) {
|
|
236
|
+
const ctx = this;
|
|
237
|
+
const listeners = this._listeners[event];
|
|
238
|
+
if (Array.isArray(listeners)) {
|
|
239
|
+
for (let i = 0, l = listeners.length; i < l; i++) {
|
|
240
|
+
const listener = listeners[i];
|
|
241
|
+
if (listener) {
|
|
242
|
+
listener.apply(ctx, args);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Emits the event async
|
|
249
|
+
*/
|
|
250
|
+
dispatchAsync(event, ...args) {
|
|
251
|
+
queueMicrotask(() => {
|
|
252
|
+
if (this._disposed) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.dispatch(event, ...args);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Returns true if the event listener is already subscribed.
|
|
260
|
+
*/
|
|
261
|
+
hasEventListener(eventName, handler) {
|
|
262
|
+
const event = eventName;
|
|
263
|
+
if (this._listeners.hasOwnProperty(event)) {
|
|
264
|
+
const listeners = this._listeners[event];
|
|
265
|
+
const index = listeners.findIndex(v => v === handler);
|
|
266
|
+
if (index > -1) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Add event listener
|
|
274
|
+
*/
|
|
275
|
+
addEventListener(eventName, handler) {
|
|
276
|
+
const event = eventName;
|
|
277
|
+
if (!this._listeners.hasOwnProperty(event)) {
|
|
278
|
+
this._listeners[event] = [];
|
|
279
|
+
}
|
|
280
|
+
this._listeners[event].push(handler);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Remove event listener
|
|
284
|
+
*/
|
|
285
|
+
removeEventListener(eventName, handler) {
|
|
286
|
+
const event = eventName;
|
|
287
|
+
if (!this._listeners.hasOwnProperty(event)) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const listeners = this._listeners[event], index = listeners.findIndex(v => v === handler);
|
|
291
|
+
if (index > -1) {
|
|
292
|
+
listeners.splice(index, 1);
|
|
293
|
+
if (listeners.length === 0) {
|
|
294
|
+
delete this._listeners[event];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Remove all listeners
|
|
300
|
+
*/
|
|
301
|
+
removeAllListeners() {
|
|
302
|
+
const events = Object.keys(this._listeners);
|
|
303
|
+
while (events.length > 0) {
|
|
304
|
+
const event = events.pop();
|
|
305
|
+
if (event) {
|
|
306
|
+
const listeners = this._listeners[event];
|
|
307
|
+
if (Array.isArray(listeners)) {
|
|
308
|
+
while (listeners.length > 0) {
|
|
309
|
+
const listener = listeners.pop();
|
|
310
|
+
if (listener) {
|
|
311
|
+
this.removeEventListener(event, listener);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
dispose() {
|
|
319
|
+
this._disposed = true;
|
|
320
|
+
this.removeAllListeners();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Cache map.
|
|
326
|
+
* Emits a change event on each mutation.
|
|
327
|
+
* @homepage https://github.com/DjonnyX/ng-virtual-list/tree/main/projects/ng-virtual-list
|
|
328
|
+
* @author Evgenii Grebennikov
|
|
329
|
+
* @email djonnyx@gmail.com
|
|
330
|
+
*/
|
|
331
|
+
class CacheMap extends EventEmitter {
|
|
332
|
+
_map = new Map();
|
|
333
|
+
_version = 0;
|
|
334
|
+
get version() {
|
|
335
|
+
return this._version;
|
|
336
|
+
}
|
|
337
|
+
constructor() {
|
|
338
|
+
super();
|
|
339
|
+
}
|
|
340
|
+
bumpVersion() {
|
|
341
|
+
this._version = this._version === Number.MAX_SAFE_INTEGER ? 0 : this._version + 1;
|
|
342
|
+
}
|
|
343
|
+
fireChange() {
|
|
344
|
+
this.dispatch('change', this.version);
|
|
345
|
+
}
|
|
346
|
+
set(id, bounds) {
|
|
347
|
+
if (this._map.has(id) && JSON.stringify(this._map.get(id)) === JSON.stringify(bounds)) {
|
|
348
|
+
return this._map;
|
|
349
|
+
}
|
|
350
|
+
const v = this._map.set(id, bounds);
|
|
351
|
+
this.bumpVersion();
|
|
352
|
+
this.fireChange();
|
|
353
|
+
return v;
|
|
354
|
+
}
|
|
355
|
+
has(id) {
|
|
356
|
+
return this._map.has(id);
|
|
357
|
+
}
|
|
358
|
+
get(id) {
|
|
359
|
+
return this._map.get(id);
|
|
360
|
+
}
|
|
361
|
+
forEach(callbackfn, thisArg) {
|
|
362
|
+
return this._map.forEach(callbackfn, thisArg);
|
|
363
|
+
}
|
|
364
|
+
dispose() {
|
|
365
|
+
super.dispose();
|
|
366
|
+
this._map.clear();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* An object that performs tracking, calculations and caching.
|
|
372
|
+
* @homepage https://github.com/DjonnyX/ng-virtual-list/tree/main/projects/ng-virtual-list
|
|
373
|
+
* @author Evgenii Grebennikov
|
|
374
|
+
* @email djonnyx@gmail.com
|
|
375
|
+
*/
|
|
376
|
+
class TrackBox extends CacheMap {
|
|
377
|
+
_tracker;
|
|
378
|
+
_items;
|
|
379
|
+
set items(v) {
|
|
380
|
+
if (this._items === v) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this._items = v;
|
|
384
|
+
}
|
|
385
|
+
_displayComponents;
|
|
386
|
+
set displayComponents(v) {
|
|
387
|
+
if (this._displayComponents === v) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
this._displayComponents = v;
|
|
391
|
+
}
|
|
392
|
+
constructor(trackingPropertyName) {
|
|
393
|
+
super();
|
|
394
|
+
this._tracker = new Tracker(trackingPropertyName);
|
|
395
|
+
}
|
|
396
|
+
set(id, bounds) {
|
|
397
|
+
if (this._map.has(id) && JSON.stringify(this._map.get(id)) === JSON.stringify(bounds)) {
|
|
398
|
+
return this._map;
|
|
399
|
+
}
|
|
400
|
+
const v = this._map.set(id, bounds);
|
|
401
|
+
this.bumpVersion();
|
|
402
|
+
this.fireChange();
|
|
403
|
+
return v;
|
|
404
|
+
}
|
|
405
|
+
_fireChangeTimeouts = [];
|
|
406
|
+
fireChange() {
|
|
407
|
+
this.clearchangesTimeouts();
|
|
408
|
+
this._fireChangeTimeouts.push(setTimeout(() => { this.dispatchAsync('change', this._version); }));
|
|
409
|
+
}
|
|
410
|
+
clearchangesTimeouts() {
|
|
411
|
+
while (this._fireChangeTimeouts.length > 0) {
|
|
412
|
+
const timeout = this._fireChangeTimeouts.pop();
|
|
413
|
+
clearTimeout(timeout);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
recalculateMetrics(options) {
|
|
417
|
+
const { bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, } = options;
|
|
418
|
+
const { width, height } = bounds, sizeProperty = isVertical ? 'height' : 'width', size = isVertical ? height : width, weightToDisplayEnd = scrollSize + height, totalLength = collection.length, typicalItemSize = dynamicSize ? this.getTypicalItemSize(isVertical, itemSize) || itemSize : itemSize, totalSize = dynamicSize ? this.getBoundsFromCache(collection, typicalItemSize, isVertical) : totalLength * typicalItemSize, snippedPos = Math.floor(scrollSize);
|
|
419
|
+
let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, startIndex;
|
|
420
|
+
if (dynamicSize) {
|
|
421
|
+
let y = 0;
|
|
422
|
+
for (let i = 0, l = collection.length; i < l; i++) {
|
|
423
|
+
const collectionItem = collection[i], map = this._map;
|
|
424
|
+
let itemSize = 0;
|
|
425
|
+
if (map.has(collectionItem.id)) {
|
|
426
|
+
const bounds = map.get(collectionItem.id);
|
|
427
|
+
itemSize = bounds ? bounds[sizeProperty] : typicalItemSize;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
itemSize = typicalItemSize;
|
|
431
|
+
}
|
|
432
|
+
if (itemsFromStartToScrollEnd === -1 && y >= scrollSize && y <= scrollSize + itemSize) {
|
|
433
|
+
leftItemsWeight += itemSize;
|
|
434
|
+
itemsFromStartToScrollEnd = i;
|
|
435
|
+
}
|
|
436
|
+
if (itemsFromStartToDisplayEnd === -1) {
|
|
437
|
+
if (y >= weightToDisplayEnd && y <= weightToDisplayEnd + itemSize) {
|
|
438
|
+
itemsFromStartToDisplayEnd = i;
|
|
439
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + itemsOffset;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
if (i <= itemsFromDisplayEndToOffsetEnd) {
|
|
444
|
+
rightItemsWeight += itemSize;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
y += itemSize;
|
|
448
|
+
}
|
|
449
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
450
|
+
rightItemLength = itemsFromStartToDisplayEnd + itemsOffset > totalLength
|
|
451
|
+
? totalLength - itemsFromStartToDisplayEnd : itemsOffset;
|
|
452
|
+
startIndex = itemsFromStartToScrollEnd - leftItemLength;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
itemsFromStartToScrollEnd = Math.ceil(scrollSize / typicalItemSize);
|
|
456
|
+
itemsFromStartToDisplayEnd = Math.ceil((scrollSize + size) / typicalItemSize);
|
|
457
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
458
|
+
rightItemLength = itemsFromStartToDisplayEnd + itemsOffset > totalLength
|
|
459
|
+
? totalLength - itemsFromStartToDisplayEnd : itemsOffset;
|
|
460
|
+
startIndex = itemsFromStartToScrollEnd - leftItemLength;
|
|
461
|
+
leftItemsWeight = leftItemLength * typicalItemSize;
|
|
462
|
+
rightItemsWeight = rightItemLength * typicalItemSize;
|
|
463
|
+
}
|
|
464
|
+
const leftHiddenItemsWeight = itemsFromStartToScrollEnd * typicalItemSize, totalItemsToDisplayEndWeight = itemsFromStartToDisplayEnd * typicalItemSize, itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight;
|
|
465
|
+
const metrics = {
|
|
466
|
+
itemsFromStartToScrollEnd,
|
|
467
|
+
itemsFromStartToDisplayEnd,
|
|
468
|
+
itemsOnDisplay,
|
|
469
|
+
leftHiddenItemsWeight,
|
|
470
|
+
leftItemLength,
|
|
471
|
+
leftItemsWeight,
|
|
472
|
+
rightItemLength,
|
|
473
|
+
rightItemsWeight,
|
|
474
|
+
snippedPos,
|
|
475
|
+
totalItemsToDisplayEndWeight,
|
|
476
|
+
totalSize,
|
|
477
|
+
typicalItemSize,
|
|
478
|
+
};
|
|
479
|
+
return metrics;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Calculates and returns the maximum size of a repeating item
|
|
483
|
+
*/
|
|
484
|
+
getTypicalItemSize(isVertical, defaultItemSize, fromIndex = 0, to = -1) {
|
|
485
|
+
const sizeProperty = isVertical ? 'height' : 'width', sizes = {};
|
|
486
|
+
let maxRepeatingSize = defaultItemSize, count = 0;
|
|
487
|
+
this._map.forEach(bound => {
|
|
488
|
+
const size = bound[sizeProperty];
|
|
489
|
+
if (sizes.hasOwnProperty(size)) {
|
|
490
|
+
sizes[size] += 1;
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
sizes[size] = 1;
|
|
494
|
+
}
|
|
495
|
+
if (sizes[size] > count) {
|
|
496
|
+
count = sizes[size];
|
|
497
|
+
maxRepeatingSize = size;
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
return maxRepeatingSize;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* tracking by propName
|
|
504
|
+
*/
|
|
505
|
+
track(dynamicSize = false) {
|
|
506
|
+
if (!this._items || !this._displayComponents) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this._tracker.track(this._items, this._displayComponents, dynamicSize ? (component, item) => {
|
|
510
|
+
this.cacheElementBounds(component, item);
|
|
511
|
+
} : undefined);
|
|
512
|
+
}
|
|
513
|
+
setDisplayObjectIndexMapById(v) {
|
|
514
|
+
this._tracker.displayObjectIndexMapById = v;
|
|
515
|
+
}
|
|
516
|
+
untrackComponentByIdProperty(component) {
|
|
517
|
+
this._tracker.untrackComponentByIdProperty(component);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Stores the element bounds in _sizeCacheMap
|
|
521
|
+
*/
|
|
522
|
+
cacheElementBounds(component, item) {
|
|
523
|
+
component.item = item;
|
|
524
|
+
const bounds = component.getBounds();
|
|
525
|
+
this.set(item.id, bounds);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Returns calculated bounds from cache
|
|
529
|
+
*/
|
|
530
|
+
getBoundsFromCache(items, typicalItemSize, isVertical) {
|
|
531
|
+
const sizeProperty = isVertical ? 'height' : 'width', map = this._map;
|
|
532
|
+
let size = 0;
|
|
533
|
+
for (let i = 0, l = items.length; i < l; i++) {
|
|
534
|
+
const item = items[i];
|
|
535
|
+
if (map.has(item.id)) {
|
|
536
|
+
const bounds = map.get(item.id);
|
|
537
|
+
size += bounds ? bounds[sizeProperty] : typicalItemSize;
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
size += typicalItemSize;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return size;
|
|
544
|
+
}
|
|
545
|
+
dispose() {
|
|
546
|
+
super.dispose();
|
|
547
|
+
this.clearchangesTimeouts();
|
|
548
|
+
if (this._tracker) {
|
|
549
|
+
this._tracker.dispose();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Virtual list component.
|
|
556
|
+
* Maximum performance for extremely large lists.
|
|
557
|
+
* It is based on algorithms for virtualization of screen objects.
|
|
109
558
|
* @homepage https://github.com/DjonnyX/ng-virtual-list/tree/main/projects/ng-virtual-list
|
|
110
559
|
* @author Evgenii Grebennikov
|
|
111
560
|
* @email djonnyx@gmail.com
|
|
@@ -151,8 +600,14 @@ class NgVirtualListComponent {
|
|
|
151
600
|
stickyMap = input({});
|
|
152
601
|
/**
|
|
153
602
|
* If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element.
|
|
603
|
+
* Ignored if the dynamicSize property is true.
|
|
154
604
|
*/
|
|
155
605
|
itemSize = input(DEFAULT_ITEM_SIZE);
|
|
606
|
+
/**
|
|
607
|
+
* If true then the items in the list can have different sizes and the itemSize property is ignored.
|
|
608
|
+
* If false then the items in the list have a fixed size specified by the itemSize property. The default value is false.
|
|
609
|
+
*/
|
|
610
|
+
dynamicSize = input(DEFAULT_DYNAMIC_SIZE);
|
|
156
611
|
/**
|
|
157
612
|
* Determines the direction in which elements are placed. Default value is "vertical".
|
|
158
613
|
*/
|
|
@@ -186,40 +641,64 @@ class NgVirtualListComponent {
|
|
|
186
641
|
_elementRef = inject((ElementRef));
|
|
187
642
|
_initialized = signal(false);
|
|
188
643
|
/**
|
|
189
|
-
* Dictionary
|
|
190
|
-
*/
|
|
191
|
-
_trackMap = {};
|
|
192
|
-
/**
|
|
193
|
-
* displayItems dictionary of indexes by id
|
|
644
|
+
* Dictionary of element sizes by their id
|
|
194
645
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
646
|
+
_trackBox = new TrackBox(TRACK_BY_PROPERTY_NAME);
|
|
647
|
+
_onTrackBoxChangeHandler = (v) => {
|
|
648
|
+
this._$cacheVersion.next(v);
|
|
649
|
+
};
|
|
650
|
+
_$cacheVersion = new BehaviorSubject(-1);
|
|
651
|
+
get $cacheVersion() { return this._$cacheVersion.asObservable(); }
|
|
198
652
|
constructor() {
|
|
199
653
|
NgVirtualListComponent.__nextId = NgVirtualListComponent.__nextId + 1 === Number.MAX_SAFE_INTEGER
|
|
200
654
|
? 0 : NgVirtualListComponent.__nextId + 1;
|
|
201
655
|
this._id = NgVirtualListComponent.__nextId;
|
|
202
|
-
|
|
656
|
+
this._trackBox.displayComponents = this._displayComponents;
|
|
657
|
+
const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = toObservable(this.itemsOffset).pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = toObservable(this.stickyMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $cacheVersion = this.$cacheVersion, $displayItems = toObservable(this._displayItems), $initialized = toObservable(this._initialized);
|
|
658
|
+
$isVertical.pipe(takeUntilDestroyed(), tap(v => {
|
|
203
659
|
this._isVertical = v;
|
|
204
660
|
const el = this._elementRef.nativeElement;
|
|
205
661
|
toggleClassName(el, v ? 'vertical' : 'horizontal', true);
|
|
206
|
-
}))
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
662
|
+
})).subscribe();
|
|
663
|
+
$dynamicSize.pipe(takeUntilDestroyed(), tap(dynamicSize => {
|
|
664
|
+
if (dynamicSize) {
|
|
665
|
+
if (!this._trackBox.hasEventListener('change', this._onTrackBoxChangeHandler)) {
|
|
666
|
+
this._trackBox.addEventListener('change', this._onTrackBoxChangeHandler);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
if (this._trackBox.hasEventListener('change', this._onTrackBoxChangeHandler)) {
|
|
671
|
+
this._trackBox.removeEventListener('change', this._onTrackBoxChangeHandler);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
})).subscribe();
|
|
675
|
+
$displayItems.pipe(takeUntilDestroyed(), tap((displayItems) => {
|
|
676
|
+
this._trackBox.items = displayItems;
|
|
677
|
+
})).subscribe();
|
|
678
|
+
combineLatest([$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
|
|
679
|
+
$itemsOffset, $snap, $isVertical, $dynamicSize, $cacheVersion,
|
|
680
|
+
]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, cacheVersion,]) => {
|
|
681
|
+
const { width, height } = bounds, { itemsFromStartToScrollEnd, itemsOnDisplay, leftHiddenItemsWeight, leftItemLength, leftItemsWeight, rightItemsWeight, snippedPos, totalSize, typicalItemSize, } = this._trackBox.recalculateMetrics({
|
|
682
|
+
bounds: { width, height }, collection: items,
|
|
683
|
+
dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap,
|
|
684
|
+
});
|
|
685
|
+
// Нужно сперва сделать корректные расчеты для dynamicSize. Сейчас количества элементов в облостях высчитывается не корректно!
|
|
686
|
+
// Необходима кореляция startDisplayObjectY с помощью дельты от высоты предыдущей и текущей размеченной области по версии кэша.
|
|
687
|
+
// TrackBox может расчитать дельту!
|
|
210
688
|
return of({
|
|
211
|
-
items, stickyMap,
|
|
212
|
-
|
|
689
|
+
items, stickyMap, width, height, isVertical, scrollSize, itemsFromStartToScrollEnd,
|
|
690
|
+
itemsOnDisplay, leftHiddenItemsWeight, itemSize: typicalItemSize, totalSize, snap,
|
|
691
|
+
leftItemLength, leftItemsWeight, rightItemsWeight, snippedPos, dynamicSize,
|
|
213
692
|
});
|
|
214
|
-
}), tap(({ items, stickyMap,
|
|
693
|
+
}), tap(({ items, stickyMap, width, height, isVertical, scrollSize, itemsFromStartToScrollEnd, itemsOnDisplay, leftHiddenItemsWeight, leftItemLength, leftItemsWeight, rightItemsWeight, snippedPos, itemSize, totalSize, snap, dynamicSize: dynamic, }) => {
|
|
215
694
|
const displayItems = [];
|
|
216
695
|
if (items.length) {
|
|
217
|
-
const w = isVertical ? width : itemSize, h = isVertical ? itemSize : height, totalItems = items.length,
|
|
218
|
-
|
|
219
|
-
let pos = leftHiddenItemsWeight - leftItemsWeight, renderWeight = itemsOnDisplay + leftItemsWeight + rightItemsWeight, stickyItem, nextSticky, stickyItemIndex = -1;
|
|
696
|
+
const sizeProperty = isVertical ? 'height' : 'width', w = isVertical ? width : itemSize, h = isVertical ? itemSize : height, totalItems = items.length, startIndex = itemsFromStartToScrollEnd - leftItemLength;
|
|
697
|
+
let pos = leftHiddenItemsWeight - leftItemsWeight, renderWeight = itemsOnDisplay + leftItemsWeight + rightItemsWeight, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0;
|
|
220
698
|
if (snap) {
|
|
221
699
|
for (let i = itemsFromStartToScrollEnd - 1; i >= 0; i--) {
|
|
222
700
|
const id = items[i].id, sticky = stickyMap[id];
|
|
701
|
+
stickyItemSize = dynamic ? this._trackBox.get(id)?.[sizeProperty] || itemSize : itemSize;
|
|
223
702
|
if (sticky > 0) {
|
|
224
703
|
const measures = {
|
|
225
704
|
x: isVertical ? 0 : snippedPos,
|
|
@@ -231,6 +710,7 @@ class NgVirtualListComponent {
|
|
|
231
710
|
sticky,
|
|
232
711
|
snap,
|
|
233
712
|
snapped: true,
|
|
713
|
+
dynamic,
|
|
234
714
|
};
|
|
235
715
|
const itemData = items[i];
|
|
236
716
|
stickyItem = { id, measures, data: itemData, config };
|
|
@@ -245,7 +725,7 @@ class NgVirtualListComponent {
|
|
|
245
725
|
if (i >= totalItems) {
|
|
246
726
|
break;
|
|
247
727
|
}
|
|
248
|
-
const id = items[i].id;
|
|
728
|
+
const id = items[i].id, size = dynamic ? this._trackBox.get(id)?.[sizeProperty] || itemSize : itemSize;
|
|
249
729
|
if (id !== stickyItem?.id) {
|
|
250
730
|
const snaped = snap && stickyMap[id] > 0 && pos <= scrollSize, measures = {
|
|
251
731
|
x: isVertical ? 0 : pos,
|
|
@@ -257,26 +737,25 @@ class NgVirtualListComponent {
|
|
|
257
737
|
sticky: stickyMap[id],
|
|
258
738
|
snap,
|
|
259
739
|
snapped: false,
|
|
740
|
+
dynamic,
|
|
260
741
|
};
|
|
261
742
|
const itemData = items[i];
|
|
262
743
|
const item = { id, measures, data: itemData, config };
|
|
263
|
-
if (!nextSticky && stickyItemIndex < i && snap && stickyMap[id] > 0 && pos <= scrollSize +
|
|
744
|
+
if (!nextSticky && stickyItemIndex < i && snap && stickyMap[id] > 0 && pos <= scrollSize + size) {
|
|
264
745
|
item.measures.x = isVertical ? 0 : snaped ? snippedPos : pos;
|
|
265
746
|
item.measures.y = isVertical ? snaped ? snippedPos : pos : 0;
|
|
266
747
|
nextSticky = item;
|
|
267
748
|
}
|
|
268
749
|
displayItems.push(item);
|
|
269
|
-
// for dynamic item size
|
|
270
|
-
// this._sizeCacheMap.set(id, measures);
|
|
271
750
|
}
|
|
272
|
-
renderWeight -=
|
|
273
|
-
pos +=
|
|
751
|
+
renderWeight -= size;
|
|
752
|
+
pos += size;
|
|
274
753
|
i++;
|
|
275
754
|
}
|
|
276
755
|
const axis = isVertical ? 'y' : 'x';
|
|
277
|
-
if (nextSticky && stickyItem && nextSticky.measures[axis] <= scrollSize +
|
|
756
|
+
if (nextSticky && stickyItem && nextSticky.measures[axis] <= scrollSize + stickyItemSize) {
|
|
278
757
|
if (nextSticky.measures[axis] > scrollSize) {
|
|
279
|
-
stickyItem.measures[axis] = nextSticky.measures[axis] -
|
|
758
|
+
stickyItem.measures[axis] = nextSticky.measures[axis] - stickyItemSize;
|
|
280
759
|
stickyItem.config.snapped = nextSticky.config.snapped = false;
|
|
281
760
|
stickyItem.config.sticky = 1;
|
|
282
761
|
}
|
|
@@ -286,17 +765,14 @@ class NgVirtualListComponent {
|
|
|
286
765
|
}
|
|
287
766
|
}
|
|
288
767
|
this._displayItems.set(displayItems);
|
|
289
|
-
|
|
290
|
-
if (l) {
|
|
291
|
-
l.nativeElement.style[isVertical ? 'height' : 'width'] = `${totalSize}px`;
|
|
292
|
-
}
|
|
768
|
+
this.resetBoundsSize(isVertical, totalSize);
|
|
293
769
|
})).subscribe();
|
|
294
770
|
combineLatest([$initialized, toObservable(this.itemRenderer)]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
|
|
295
771
|
this.resetRenderers(itemRenderer);
|
|
296
772
|
}));
|
|
297
|
-
combineLatest([$initialized,
|
|
773
|
+
combineLatest([$initialized, $displayItems]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, displayItems]) => {
|
|
298
774
|
this.createDisplayComponentsIfNeed(displayItems);
|
|
299
|
-
this.tracking(
|
|
775
|
+
this.tracking();
|
|
300
776
|
})).subscribe();
|
|
301
777
|
}
|
|
302
778
|
ngOnInit() {
|
|
@@ -308,7 +784,7 @@ class NgVirtualListComponent {
|
|
|
308
784
|
}
|
|
309
785
|
createDisplayComponentsIfNeed(displayItems) {
|
|
310
786
|
if (!displayItems || !this._listContainerRef) {
|
|
311
|
-
this.
|
|
787
|
+
this._trackBox.setDisplayObjectIndexMapById({});
|
|
312
788
|
return;
|
|
313
789
|
}
|
|
314
790
|
const _listContainerRef = this._listContainerRef;
|
|
@@ -324,7 +800,7 @@ class NgVirtualListComponent {
|
|
|
324
800
|
comp?.destroy();
|
|
325
801
|
const id = comp?.instance.item?.id;
|
|
326
802
|
if (id !== undefined) {
|
|
327
|
-
|
|
803
|
+
this._trackBox.untrackComponentByIdProperty(comp?.instance);
|
|
328
804
|
}
|
|
329
805
|
}
|
|
330
806
|
this.resetRenderers();
|
|
@@ -336,40 +812,18 @@ class NgVirtualListComponent {
|
|
|
336
812
|
item.instance.renderer = itemRenderer || this.itemRenderer();
|
|
337
813
|
doMap[item.instance.id] = i;
|
|
338
814
|
}
|
|
339
|
-
this.
|
|
815
|
+
this._trackBox.setDisplayObjectIndexMapById(doMap);
|
|
340
816
|
}
|
|
341
817
|
/**
|
|
342
818
|
* tracking by id
|
|
343
819
|
*/
|
|
344
|
-
tracking(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (this._trackMap.hasOwnProperty(item.id)) {
|
|
352
|
-
const lastIndex = this._disMap[diId], el = this._displayComponents[lastIndex], elId = el?.instance.id;
|
|
353
|
-
if (el && elId === diId) {
|
|
354
|
-
const indexByUntrackedItems = untrackedItems.findIndex(v => v.instance.id === elId);
|
|
355
|
-
if (indexByUntrackedItems > -1) {
|
|
356
|
-
el.instance.item = item;
|
|
357
|
-
untrackedItems.splice(indexByUntrackedItems, 1);
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
delete this._trackMap[item.id];
|
|
362
|
-
}
|
|
363
|
-
if (untrackedItems.length > 0) {
|
|
364
|
-
const el = untrackedItems.shift(), item = displayItems[i];
|
|
365
|
-
if (el) {
|
|
366
|
-
el.instance.item = item;
|
|
367
|
-
this._trackMap[item.id] = el.instance.id;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (untrackedItems.length) {
|
|
372
|
-
throw Error('tracking by id caused an error');
|
|
820
|
+
tracking() {
|
|
821
|
+
this._trackBox.track(this.dynamicSize());
|
|
822
|
+
}
|
|
823
|
+
resetBoundsSize(isVertical, totalSize) {
|
|
824
|
+
const l = this._list();
|
|
825
|
+
if (l) {
|
|
826
|
+
l.nativeElement.style[isVertical ? 'height' : 'width'] = `${totalSize}px`;
|
|
373
827
|
}
|
|
374
828
|
}
|
|
375
829
|
/**
|
|
@@ -398,6 +852,9 @@ class NgVirtualListComponent {
|
|
|
398
852
|
}
|
|
399
853
|
}
|
|
400
854
|
ngOnDestroy() {
|
|
855
|
+
if (this._trackBox) {
|
|
856
|
+
this._trackBox.dispose();
|
|
857
|
+
}
|
|
401
858
|
const containerEl = this._container();
|
|
402
859
|
if (containerEl) {
|
|
403
860
|
containerEl.nativeElement.removeEventListener('scroll', this._onScrollHandler);
|
|
@@ -414,7 +871,7 @@ class NgVirtualListComponent {
|
|
|
414
871
|
}
|
|
415
872
|
}
|
|
416
873
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
417
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, snapToItem: { classPropertyName: "snapToItem", publicName: "snapToItem", isSignal: true, isRequired: false, transformFunction: null }, 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 }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
874
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.14", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, snapToItem: { classPropertyName: "snapToItem", publicName: "snapToItem", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, stickyMap: { classPropertyName: "stickyMap", publicName: "stickyMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
418
875
|
}
|
|
419
876
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
420
877
|
type: Component,
|