ng-virtual-list 19.1.41 → 19.1.42
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/fesm2022/ng-virtual-list.mjs +1621 -0
- package/fesm2022/ng-virtual-list.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/components/ng-virtual-list-item.component.d.ts +30 -0
- package/lib/const/index.d.ts +33 -0
- package/{src/lib/enums/direction.ts → lib/enums/direction.d.ts} +8 -9
- package/{src/lib/enums/directions.ts → lib/enums/directions.d.ts} +16 -16
- package/{src/lib/enums/index.ts → lib/enums/index.d.ts} +4 -7
- package/{src/lib/models/collection.model.ts → lib/models/collection.model.d.ts} +9 -9
- package/{src/lib/models/index.ts → lib/models/index.d.ts} +6 -13
- package/{src/lib/models/item.model.ts → lib/models/item.model.d.ts} +14 -15
- package/{src/lib/models/render-collection.model.ts → lib/models/render-collection.model.d.ts} +9 -9
- package/{src/lib/models/render-item-config.model.ts → lib/models/render-item-config.model.d.ts} +33 -33
- package/{src/lib/models/render-item.model.ts → lib/models/render-item.model.d.ts} +28 -29
- package/{src/lib/models/scroll-direction.model.ts → lib/models/scroll-direction.model.d.ts} +5 -5
- package/{src/lib/models/scroll-event.model.ts → lib/models/scroll-event.model.d.ts} +50 -51
- package/{src/lib/models/sticky-map.model.ts → lib/models/sticky-map.model.d.ts} +12 -12
- package/lib/ng-virtual-list.component.d.ts +129 -0
- package/{src/lib/types/id.ts → lib/types/id.d.ts} +7 -7
- package/{src/lib/types/index.ts → lib/types/index.d.ts} +4 -9
- package/{src/lib/types/rect.ts → lib/types/rect.d.ts} +17 -18
- package/{src/lib/types/size.ts → lib/types/size.d.ts} +16 -16
- package/lib/utils/cacheMap.d.ts +60 -0
- package/lib/utils/debounce.d.ts +16 -0
- package/lib/utils/eventEmitter.d.ts +40 -0
- package/{src/lib/utils/index.ts → lib/utils/index.d.ts} +7 -15
- package/lib/utils/isDirection.d.ts +8 -0
- package/lib/utils/scrollEvent.d.ts +39 -0
- package/lib/utils/toggleClassName.d.ts +7 -0
- package/lib/utils/trackBox.d.ts +176 -0
- package/lib/utils/tracker.d.ts +44 -0
- package/package.json +40 -29
- package/{src/public-api.ts → public-api.d.ts} +3 -7
- package/ng-package.json +0 -7
- package/src/lib/components/ng-virtual-list-item.component.html +0 -12
- package/src/lib/components/ng-virtual-list-item.component.scss +0 -17
- package/src/lib/components/ng-virtual-list-item.component.spec.ts +0 -23
- package/src/lib/components/ng-virtual-list-item.component.ts +0 -108
- package/src/lib/const/index.ts +0 -67
- package/src/lib/ng-virtual-list.component.html +0 -5
- package/src/lib/ng-virtual-list.component.scss +0 -28
- package/src/lib/ng-virtual-list.component.spec.ts +0 -23
- package/src/lib/ng-virtual-list.component.ts +0 -547
- package/src/lib/utils/cacheMap.ts +0 -223
- package/src/lib/utils/debounce.ts +0 -31
- package/src/lib/utils/eventEmitter.ts +0 -119
- package/src/lib/utils/isDirection.ts +0 -17
- package/src/lib/utils/scrollEvent.ts +0 -62
- package/src/lib/utils/toggleClassName.ts +0 -14
- package/src/lib/utils/trackBox.ts +0 -839
- package/src/lib/utils/tracker.ts +0 -126
- package/tsconfig.lib.json +0 -16
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -15
|
@@ -0,0 +1,1621 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { signal, inject, ElementRef, ChangeDetectionStrategy, Component, viewChild, output, input, ViewContainerRef, ViewChild, ViewEncapsulation } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
+
import { tap, filter, map, combineLatest, distinctUntilChanged, switchMap, of } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Axis of the arrangement of virtual list elements.
|
|
10
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/enums/directions.ts
|
|
11
|
+
* @author Evgenii Grebennikov
|
|
12
|
+
* @email djonnyx@gmail.com
|
|
13
|
+
*/
|
|
14
|
+
var Directions;
|
|
15
|
+
(function (Directions) {
|
|
16
|
+
/**
|
|
17
|
+
* Horizontal axis.
|
|
18
|
+
*/
|
|
19
|
+
Directions["HORIZONTAL"] = "horizontal";
|
|
20
|
+
/**
|
|
21
|
+
* Vertical axis.
|
|
22
|
+
*/
|
|
23
|
+
Directions["VERTICAL"] = "vertical";
|
|
24
|
+
})(Directions || (Directions = {}));
|
|
25
|
+
|
|
26
|
+
const DEFAULT_ITEM_SIZE = 24;
|
|
27
|
+
const DEFAULT_ITEMS_OFFSET = 2;
|
|
28
|
+
const DEFAULT_LIST_SIZE = 400;
|
|
29
|
+
const DEFAULT_SNAP = false;
|
|
30
|
+
const DEFAULT_ENABLED_BUFFER_OPTIMIZATION = false;
|
|
31
|
+
const DEFAULT_DYNAMIC_SIZE = false;
|
|
32
|
+
const TRACK_BY_PROPERTY_NAME = 'id';
|
|
33
|
+
const DEFAULT_DIRECTION = Directions.VERTICAL;
|
|
34
|
+
const DISPLAY_OBJECTS_LENGTH_MESUREMENT_ERROR = 1;
|
|
35
|
+
const MAX_SCROLL_TO_ITERATIONS = 5;
|
|
36
|
+
// presets
|
|
37
|
+
const BEHAVIOR_AUTO = 'auto';
|
|
38
|
+
const BEHAVIOR_INSTANT = 'instant';
|
|
39
|
+
const BEHAVIOR_SMOOTH = 'smooth';
|
|
40
|
+
const VISIBILITY_VISIBLE = 'visible';
|
|
41
|
+
const VISIBILITY_HIDDEN = 'hidden';
|
|
42
|
+
const SIZE_100_PERSENT = '100%';
|
|
43
|
+
const SIZE_AUTO = 'auto';
|
|
44
|
+
const POSITION_ABSOLUTE = 'absolute';
|
|
45
|
+
const POSITION_STICKY = 'sticky';
|
|
46
|
+
const TRANSLATE_3D = 'translate3d';
|
|
47
|
+
const ZEROS_TRANSLATE_3D = `${TRANSLATE_3D}(0,0,0)`;
|
|
48
|
+
const TOP_PROP_NAME = 'top';
|
|
49
|
+
const LEFT_PROP_NAME = 'left';
|
|
50
|
+
const X_PROP_NAME = 'x';
|
|
51
|
+
const Y_PROP_NAME = 'y';
|
|
52
|
+
const WIDTH_PROP_NAME = 'width';
|
|
53
|
+
const HEIGHT_PROP_NAME = 'height';
|
|
54
|
+
const PX = 'px';
|
|
55
|
+
const SCROLL = 'scroll';
|
|
56
|
+
const SCROLL_END = 'scrollend';
|
|
57
|
+
const CLASS_LIST_VERTICAL = 'vertical';
|
|
58
|
+
const CLASS_LIST_HORIZONTAL = 'horizontal';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Virtual list item component
|
|
62
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/components/ng-virtual-list-item.component.ts
|
|
63
|
+
* @author Evgenii Grebennikov
|
|
64
|
+
* @email djonnyx@gmail.com
|
|
65
|
+
*/
|
|
66
|
+
class NgVirtualListItemComponent {
|
|
67
|
+
static __nextId = 0;
|
|
68
|
+
_id;
|
|
69
|
+
get id() {
|
|
70
|
+
return this._id;
|
|
71
|
+
}
|
|
72
|
+
data = signal(undefined);
|
|
73
|
+
_data = undefined;
|
|
74
|
+
set item(v) {
|
|
75
|
+
if (this._data === v) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const data = this._data = v;
|
|
79
|
+
if (data) {
|
|
80
|
+
const styles = this._elementRef.nativeElement.style;
|
|
81
|
+
styles.zIndex = String(data.config.sticky);
|
|
82
|
+
if (data.config.snapped) {
|
|
83
|
+
styles.transform = ZEROS_TRANSLATE_3D;
|
|
84
|
+
styles.position = POSITION_STICKY;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
styles.position = POSITION_ABSOLUTE;
|
|
88
|
+
styles.transform = `${TRANSLATE_3D}(${data.config.isVertical ? 0 : data.measures.x}${PX}, ${data.config.isVertical ? data.measures.y : 0}${PX} , 0)`;
|
|
89
|
+
}
|
|
90
|
+
styles.height = data.config.isVertical ? data.config.dynamic ? SIZE_AUTO : `${data.measures.height}${PX}` : SIZE_100_PERSENT;
|
|
91
|
+
styles.width = data.config.isVertical ? SIZE_100_PERSENT : data.config.dynamic ? SIZE_AUTO : `${data.measures.width}${PX}`;
|
|
92
|
+
}
|
|
93
|
+
this.data.set(v);
|
|
94
|
+
}
|
|
95
|
+
get item() {
|
|
96
|
+
return this._data;
|
|
97
|
+
}
|
|
98
|
+
get itemId() {
|
|
99
|
+
return this._data?.id;
|
|
100
|
+
}
|
|
101
|
+
itemRenderer = signal(undefined);
|
|
102
|
+
set renderer(v) {
|
|
103
|
+
this.itemRenderer.set(v);
|
|
104
|
+
}
|
|
105
|
+
_elementRef = inject((ElementRef));
|
|
106
|
+
get element() {
|
|
107
|
+
return this._elementRef.nativeElement;
|
|
108
|
+
}
|
|
109
|
+
constructor() {
|
|
110
|
+
this._id = NgVirtualListItemComponent.__nextId = NgVirtualListItemComponent.__nextId === Number.MAX_SAFE_INTEGER
|
|
111
|
+
? 0 : NgVirtualListItemComponent.__nextId + 1;
|
|
112
|
+
}
|
|
113
|
+
getBounds() {
|
|
114
|
+
const el = this._elementRef.nativeElement, { width, height } = el.getBoundingClientRect();
|
|
115
|
+
return { width, height };
|
|
116
|
+
}
|
|
117
|
+
show() {
|
|
118
|
+
const styles = this._elementRef.nativeElement.style;
|
|
119
|
+
if (styles.visibility === VISIBILITY_VISIBLE) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
styles.visibility = VISIBILITY_VISIBLE;
|
|
123
|
+
}
|
|
124
|
+
hide() {
|
|
125
|
+
const styles = this._elementRef.nativeElement.style;
|
|
126
|
+
if (styles.visibility === VISIBILITY_HIDDEN) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
styles.visibility = VISIBILITY_HIDDEN;
|
|
130
|
+
styles.transform = ZEROS_TRANSLATE_3D;
|
|
131
|
+
}
|
|
132
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
133
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NgVirtualListItemComponent, isStandalone: true, selector: "ng-virtual-list-item", host: { classAttribute: "ngvl__item" }, ngImport: i0, template: "@let item = data();\r\n@let renderer = itemRenderer();\r\n\r\n@if (item) {\r\n <li #listItem part=\"item\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut}\">\r\n @if (renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, config: item.config}\" />\r\n }\r\n </li>\r\n}", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
134
|
+
}
|
|
135
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListItemComponent, decorators: [{
|
|
136
|
+
type: Component,
|
|
137
|
+
args: [{ selector: 'ng-virtual-list-item', imports: [CommonModule], host: {
|
|
138
|
+
'class': 'ngvl__item',
|
|
139
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let item = data();\r\n@let renderer = itemRenderer();\r\n\r\n@if (item) {\r\n <li #listItem part=\"item\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': item.config.snapped,\r\n 'snapped-out': item.config.snappedOut}\">\r\n @if (renderer) {\r\n <ng-container [ngTemplateOutlet]=\"renderer\"\r\n [ngTemplateOutletContext]=\"{data: item.data || {}, config: item.config}\" />\r\n }\r\n </li>\r\n}", styles: [":host{display:block;position:absolute;left:0;top:0;box-sizing:border-box;overflow:hidden}.ngvl-item__container{margin:0;padding:0;overflow:hidden;background-color:#fff;width:inherit;height:inherit}\n"] }]
|
|
140
|
+
}], ctorParameters: () => [] });
|
|
141
|
+
|
|
142
|
+
const HORIZONTAL_ALIASES = [Directions.HORIZONTAL, 'horizontal'], VERTICAL_ALIASES = [Directions.VERTICAL, 'vertical'];
|
|
143
|
+
/**
|
|
144
|
+
* Determines the axis membership of a virtual list
|
|
145
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/isDirection.ts
|
|
146
|
+
* @author Evgenii Grebennikov
|
|
147
|
+
* @email djonnyx@gmail.com
|
|
148
|
+
*/
|
|
149
|
+
const isDirection = (src, expected) => {
|
|
150
|
+
if (HORIZONTAL_ALIASES.includes(expected)) {
|
|
151
|
+
return HORIZONTAL_ALIASES.includes(src);
|
|
152
|
+
}
|
|
153
|
+
return VERTICAL_ALIASES.includes(src);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Simple debounce function.
|
|
158
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/debounce.ts
|
|
159
|
+
* @author Evgenii Grebennikov
|
|
160
|
+
* @email djonnyx@gmail.com
|
|
161
|
+
*/
|
|
162
|
+
const debounce = (cb, debounceTime = 0) => {
|
|
163
|
+
let timeout;
|
|
164
|
+
const dispose = () => {
|
|
165
|
+
if (timeout !== undefined) {
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const execute = (...args) => {
|
|
170
|
+
dispose();
|
|
171
|
+
timeout = setTimeout(() => {
|
|
172
|
+
cb(...args);
|
|
173
|
+
}, debounceTime);
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
/**
|
|
177
|
+
* Call handling method
|
|
178
|
+
*/
|
|
179
|
+
execute,
|
|
180
|
+
/**
|
|
181
|
+
* Method of destroying handlers
|
|
182
|
+
*/
|
|
183
|
+
dispose,
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Switch css classes
|
|
189
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/toggleClassName.ts
|
|
190
|
+
* @author Evgenii Grebennikov
|
|
191
|
+
* @email djonnyx@gmail.com
|
|
192
|
+
*/
|
|
193
|
+
const toggleClassName = (el, className, remove = false) => {
|
|
194
|
+
if (!el.classList.contains(className)) {
|
|
195
|
+
el.classList.add(className);
|
|
196
|
+
}
|
|
197
|
+
else if (remove) {
|
|
198
|
+
el.classList.remove(className);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Tracks display items by property
|
|
204
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/tracker.ts
|
|
205
|
+
* @author Evgenii Grebennikov
|
|
206
|
+
* @email djonnyx@gmail.com
|
|
207
|
+
*/
|
|
208
|
+
class Tracker {
|
|
209
|
+
/**
|
|
210
|
+
* display objects dictionary of indexes by id
|
|
211
|
+
*/
|
|
212
|
+
_displayObjectIndexMapById = {};
|
|
213
|
+
set displayObjectIndexMapById(v) {
|
|
214
|
+
if (this._displayObjectIndexMapById === v) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this._displayObjectIndexMapById = v;
|
|
218
|
+
}
|
|
219
|
+
get displayObjectIndexMapById() {
|
|
220
|
+
return this._displayObjectIndexMapById;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Dictionary displayItems propertyNameId by items propertyNameId
|
|
224
|
+
*/
|
|
225
|
+
_trackMap = {};
|
|
226
|
+
get trackMap() {
|
|
227
|
+
return this._trackMap;
|
|
228
|
+
}
|
|
229
|
+
_trackingPropertyName;
|
|
230
|
+
set trackingPropertyName(v) {
|
|
231
|
+
this._trackingPropertyName = v;
|
|
232
|
+
}
|
|
233
|
+
constructor(trackingPropertyName) {
|
|
234
|
+
this._trackingPropertyName = trackingPropertyName;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* tracking by propName
|
|
238
|
+
*/
|
|
239
|
+
track(items, components, direction) {
|
|
240
|
+
if (!items) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const idPropName = this._trackingPropertyName, untrackedItems = [...components], isDown = direction === 0 || direction === 1;
|
|
244
|
+
for (let i = isDown ? 0 : items.length - 1, l = isDown ? items.length : 0; isDown ? i < l : i >= l; isDown ? i++ : i--) {
|
|
245
|
+
const item = items[i], itemTrackingProperty = item[idPropName];
|
|
246
|
+
if (this._trackMap) {
|
|
247
|
+
if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
|
|
248
|
+
const diId = this._trackMap[itemTrackingProperty], compIndex = this._displayObjectIndexMapById[diId], comp = components[compIndex];
|
|
249
|
+
const compId = comp?.instance?.id;
|
|
250
|
+
if (comp !== undefined && compId == diId) {
|
|
251
|
+
const indexByUntrackedItems = untrackedItems.findIndex(v => {
|
|
252
|
+
return v.instance.id == compId;
|
|
253
|
+
});
|
|
254
|
+
if (indexByUntrackedItems > -1) {
|
|
255
|
+
comp.instance.item = item;
|
|
256
|
+
comp.instance.show();
|
|
257
|
+
untrackedItems.splice(indexByUntrackedItems, 1);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
delete this._trackMap[itemTrackingProperty];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (untrackedItems.length > 0) {
|
|
265
|
+
const el = untrackedItems.shift(), item = items[i];
|
|
266
|
+
if (el) {
|
|
267
|
+
el.instance.item = item;
|
|
268
|
+
if (this._trackMap) {
|
|
269
|
+
this._trackMap[itemTrackingProperty] = el.instance.id;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (untrackedItems.length) {
|
|
275
|
+
for (let i = 0, l = untrackedItems.length; i < l; i++) {
|
|
276
|
+
const comp = untrackedItems[i];
|
|
277
|
+
comp.instance.hide();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
untrackComponentByIdProperty(component) {
|
|
282
|
+
if (!component) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const propertyIdName = this._trackingPropertyName;
|
|
286
|
+
if (this._trackMap && component[propertyIdName] !== undefined) {
|
|
287
|
+
delete this._trackMap[propertyIdName];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
dispose() {
|
|
291
|
+
this._trackMap = null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Simple event emitter
|
|
297
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/eventEmitter.ts
|
|
298
|
+
* @author Evgenii Grebennikov
|
|
299
|
+
* @email djonnyx@gmail.com
|
|
300
|
+
*/
|
|
301
|
+
class EventEmitter {
|
|
302
|
+
_listeners = {};
|
|
303
|
+
_disposed = false;
|
|
304
|
+
constructor() { }
|
|
305
|
+
/**
|
|
306
|
+
* Emits the event
|
|
307
|
+
*/
|
|
308
|
+
dispatch(event, ...args) {
|
|
309
|
+
const ctx = this;
|
|
310
|
+
const listeners = this._listeners[event];
|
|
311
|
+
if (Array.isArray(listeners)) {
|
|
312
|
+
for (let i = 0, l = listeners.length; i < l; i++) {
|
|
313
|
+
const listener = listeners[i];
|
|
314
|
+
if (listener) {
|
|
315
|
+
listener.apply(ctx, args);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Emits the event async
|
|
322
|
+
*/
|
|
323
|
+
dispatchAsync(event, ...args) {
|
|
324
|
+
queueMicrotask(() => {
|
|
325
|
+
if (this._disposed) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
this.dispatch(event, ...args);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns true if the event listener is already subscribed.
|
|
333
|
+
*/
|
|
334
|
+
hasEventListener(eventName, handler) {
|
|
335
|
+
const event = eventName;
|
|
336
|
+
if (this._listeners.hasOwnProperty(event)) {
|
|
337
|
+
const listeners = this._listeners[event];
|
|
338
|
+
const index = listeners.findIndex(v => v === handler);
|
|
339
|
+
if (index > -1) {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Add event listener
|
|
347
|
+
*/
|
|
348
|
+
addEventListener(eventName, handler) {
|
|
349
|
+
const event = eventName;
|
|
350
|
+
if (!this._listeners.hasOwnProperty(event)) {
|
|
351
|
+
this._listeners[event] = [];
|
|
352
|
+
}
|
|
353
|
+
this._listeners[event].push(handler);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Remove event listener
|
|
357
|
+
*/
|
|
358
|
+
removeEventListener(eventName, handler) {
|
|
359
|
+
const event = eventName;
|
|
360
|
+
if (!this._listeners.hasOwnProperty(event)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const listeners = this._listeners[event], index = listeners.findIndex(v => v === handler);
|
|
364
|
+
if (index > -1) {
|
|
365
|
+
listeners.splice(index, 1);
|
|
366
|
+
if (listeners.length === 0) {
|
|
367
|
+
delete this._listeners[event];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Remove all listeners
|
|
373
|
+
*/
|
|
374
|
+
removeAllListeners() {
|
|
375
|
+
const events = Object.keys(this._listeners);
|
|
376
|
+
while (events.length > 0) {
|
|
377
|
+
const event = events.pop();
|
|
378
|
+
if (event) {
|
|
379
|
+
const listeners = this._listeners[event];
|
|
380
|
+
if (Array.isArray(listeners)) {
|
|
381
|
+
while (listeners.length > 0) {
|
|
382
|
+
const listener = listeners.pop();
|
|
383
|
+
if (listener) {
|
|
384
|
+
this.removeEventListener(event, listener);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Method of destroying handlers
|
|
393
|
+
*/
|
|
394
|
+
dispose() {
|
|
395
|
+
this._disposed = true;
|
|
396
|
+
this.removeAllListeners();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
class CMap {
|
|
401
|
+
_dict = {};
|
|
402
|
+
constructor(dict) {
|
|
403
|
+
if (dict) {
|
|
404
|
+
this._dict = { ...dict._dict };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
get(key) {
|
|
408
|
+
const k = String(key);
|
|
409
|
+
return this._dict[k];
|
|
410
|
+
}
|
|
411
|
+
set(key, value) {
|
|
412
|
+
const k = String(key);
|
|
413
|
+
this._dict[k] = value;
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
has(key) {
|
|
417
|
+
return this._dict.hasOwnProperty(String(key));
|
|
418
|
+
}
|
|
419
|
+
delete(key) {
|
|
420
|
+
const k = String(key);
|
|
421
|
+
delete this._dict[k];
|
|
422
|
+
}
|
|
423
|
+
clear() {
|
|
424
|
+
this._dict = {};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const CACHE_BOX_CHANGE_EVENT_NAME = 'change';
|
|
428
|
+
const MAX_SCROLL_DIRECTION_POOL = 50, CLEAR_SCROLL_DIRECTION_TO = 10, DIR_BACK = '-1', DIR_NONE = '0', DIR_FORWARD = '1';
|
|
429
|
+
/**
|
|
430
|
+
* Cache map.
|
|
431
|
+
* Emits a change event on each mutation.
|
|
432
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/cacheMap.ts
|
|
433
|
+
* @author Evgenii Grebennikov
|
|
434
|
+
* @email djonnyx@gmail.com
|
|
435
|
+
*/
|
|
436
|
+
class CacheMap extends EventEmitter {
|
|
437
|
+
_map = new CMap();
|
|
438
|
+
_snapshot = new CMap();
|
|
439
|
+
_version = 0;
|
|
440
|
+
_previousVersion = this._version;
|
|
441
|
+
_lifeCircleTimeout;
|
|
442
|
+
_delta = 0;
|
|
443
|
+
get delta() {
|
|
444
|
+
return this._delta;
|
|
445
|
+
}
|
|
446
|
+
_deltaDirection = 0;
|
|
447
|
+
set deltaDirection(v) {
|
|
448
|
+
this._deltaDirection = v;
|
|
449
|
+
this._scrollDirection = this.calcScrollDirection(v);
|
|
450
|
+
}
|
|
451
|
+
get deltaDirection() {
|
|
452
|
+
return this._deltaDirection;
|
|
453
|
+
}
|
|
454
|
+
_scrollDirectionCache = [];
|
|
455
|
+
_scrollDirection = 0;
|
|
456
|
+
get scrollDirection() {
|
|
457
|
+
return this._scrollDirection;
|
|
458
|
+
}
|
|
459
|
+
get version() {
|
|
460
|
+
return this._version;
|
|
461
|
+
}
|
|
462
|
+
_clearScrollDirectionDebounce = debounce(() => {
|
|
463
|
+
while (this._scrollDirectionCache.length > CLEAR_SCROLL_DIRECTION_TO) {
|
|
464
|
+
this._scrollDirectionCache.shift();
|
|
465
|
+
}
|
|
466
|
+
}, 10);
|
|
467
|
+
constructor() {
|
|
468
|
+
super();
|
|
469
|
+
this.lifeCircle();
|
|
470
|
+
}
|
|
471
|
+
changesDetected() {
|
|
472
|
+
return this._version !== this._previousVersion;
|
|
473
|
+
}
|
|
474
|
+
stopLifeCircle() {
|
|
475
|
+
clearTimeout(this._lifeCircleTimeout);
|
|
476
|
+
}
|
|
477
|
+
nextTick(cb) {
|
|
478
|
+
if (this._disposed) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
this._lifeCircleTimeout = setTimeout(() => {
|
|
482
|
+
cb();
|
|
483
|
+
});
|
|
484
|
+
return this._lifeCircleTimeout;
|
|
485
|
+
}
|
|
486
|
+
lifeCircle() {
|
|
487
|
+
this.fireChangeIfNeed();
|
|
488
|
+
this.lifeCircleDo();
|
|
489
|
+
}
|
|
490
|
+
lifeCircleDo() {
|
|
491
|
+
this._previousVersion = this._version;
|
|
492
|
+
this.nextTick(() => {
|
|
493
|
+
this.lifeCircle();
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
clearScrollDirectionCache() {
|
|
497
|
+
this._clearScrollDirectionDebounce.execute();
|
|
498
|
+
}
|
|
499
|
+
calcScrollDirection(v) {
|
|
500
|
+
while (this._scrollDirectionCache.length >= MAX_SCROLL_DIRECTION_POOL) {
|
|
501
|
+
this._scrollDirectionCache.shift();
|
|
502
|
+
}
|
|
503
|
+
this._scrollDirectionCache.push(v);
|
|
504
|
+
const dict = { [DIR_BACK]: 0, [DIR_NONE]: 0, [DIR_FORWARD]: 0 };
|
|
505
|
+
for (let i = 0, l = this._scrollDirectionCache.length, li = l - 1; i < l; i++) {
|
|
506
|
+
const dir = String(this._scrollDirectionCache[i]);
|
|
507
|
+
dict[dir] += 1;
|
|
508
|
+
if (i === li) {
|
|
509
|
+
for (let d in dict) {
|
|
510
|
+
if (d === String(v)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
dict[d] -= 1;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (dict[DIR_BACK] > dict[DIR_NONE] && dict[DIR_BACK] > dict[DIR_FORWARD]) {
|
|
518
|
+
return -1;
|
|
519
|
+
}
|
|
520
|
+
else if (dict[DIR_FORWARD] > dict[DIR_BACK] && dict[DIR_FORWARD] > dict[DIR_NONE]) {
|
|
521
|
+
return 1;
|
|
522
|
+
}
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
bumpVersion() {
|
|
526
|
+
if (this.changesDetected()) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const v = this._version === Number.MAX_SAFE_INTEGER ? 0 : this._version + 1;
|
|
530
|
+
this._version = v;
|
|
531
|
+
}
|
|
532
|
+
fireChangeIfNeed() {
|
|
533
|
+
if (this.changesDetected()) {
|
|
534
|
+
this.dispatch(CACHE_BOX_CHANGE_EVENT_NAME, this.version);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
set(id, bounds) {
|
|
538
|
+
if (this._map.has(id)) {
|
|
539
|
+
const b = this._map.get(id), bb = bounds;
|
|
540
|
+
if (b.width === bb.width && b.height === bb.height) {
|
|
541
|
+
return this._map;
|
|
542
|
+
}
|
|
543
|
+
return this._map;
|
|
544
|
+
}
|
|
545
|
+
const v = this._map.set(id, bounds);
|
|
546
|
+
this.bumpVersion();
|
|
547
|
+
return v;
|
|
548
|
+
}
|
|
549
|
+
has(id) {
|
|
550
|
+
return this._map.has(id);
|
|
551
|
+
}
|
|
552
|
+
get(id) {
|
|
553
|
+
return this._map.get(id);
|
|
554
|
+
}
|
|
555
|
+
snapshot() {
|
|
556
|
+
this._snapshot = new CMap(this._map);
|
|
557
|
+
}
|
|
558
|
+
dispose() {
|
|
559
|
+
super.dispose();
|
|
560
|
+
this.stopLifeCircle();
|
|
561
|
+
this._snapshot.clear();
|
|
562
|
+
this._map.clear();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const TRACK_BOX_CHANGE_EVENT_NAME = 'change';
|
|
567
|
+
var ItemDisplayMethods;
|
|
568
|
+
(function (ItemDisplayMethods) {
|
|
569
|
+
ItemDisplayMethods[ItemDisplayMethods["CREATE"] = 0] = "CREATE";
|
|
570
|
+
ItemDisplayMethods[ItemDisplayMethods["UPDATE"] = 1] = "UPDATE";
|
|
571
|
+
ItemDisplayMethods[ItemDisplayMethods["DELETE"] = 2] = "DELETE";
|
|
572
|
+
ItemDisplayMethods[ItemDisplayMethods["NOT_CHANGED"] = 3] = "NOT_CHANGED";
|
|
573
|
+
})(ItemDisplayMethods || (ItemDisplayMethods = {}));
|
|
574
|
+
/**
|
|
575
|
+
* An object that performs tracking, calculations and caching.
|
|
576
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/trackBox.ts
|
|
577
|
+
* @author Evgenii Grebennikov
|
|
578
|
+
* @email djonnyx@gmail.com
|
|
579
|
+
*/
|
|
580
|
+
class TrackBox extends CacheMap {
|
|
581
|
+
_tracker;
|
|
582
|
+
_items;
|
|
583
|
+
set items(v) {
|
|
584
|
+
if (this._items === v) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
this._items = v;
|
|
588
|
+
}
|
|
589
|
+
_displayComponents;
|
|
590
|
+
set displayComponents(v) {
|
|
591
|
+
if (this._displayComponents === v) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this._displayComponents = v;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Set the trackBy property
|
|
598
|
+
*/
|
|
599
|
+
set trackingPropertyName(v) {
|
|
600
|
+
this._tracker.trackingPropertyName = v;
|
|
601
|
+
}
|
|
602
|
+
constructor(trackingPropertyName) {
|
|
603
|
+
super();
|
|
604
|
+
this._tracker = new Tracker(trackingPropertyName);
|
|
605
|
+
}
|
|
606
|
+
set(id, bounds) {
|
|
607
|
+
if (this._map.has(id)) {
|
|
608
|
+
const b = this._map.get(id);
|
|
609
|
+
if (b?.width === bounds.width && b.height === bounds.height) {
|
|
610
|
+
return this._map;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
const v = this._map.set(id, bounds);
|
|
614
|
+
this.bumpVersion();
|
|
615
|
+
return v;
|
|
616
|
+
}
|
|
617
|
+
_previousCollection;
|
|
618
|
+
_deletedItemsMap = {};
|
|
619
|
+
_crudDetected = false;
|
|
620
|
+
get crudDetected() { return this._crudDetected; }
|
|
621
|
+
fireChangeIfNeed() {
|
|
622
|
+
if (this.changesDetected()) {
|
|
623
|
+
this.dispatch(TRACK_BOX_CHANGE_EVENT_NAME, this._version);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
_previousTotalSize = 0;
|
|
627
|
+
_scrollDelta = 0;
|
|
628
|
+
get scrollDelta() { return this._scrollDelta; }
|
|
629
|
+
lifeCircle() {
|
|
630
|
+
this.fireChangeIfNeed();
|
|
631
|
+
this.lifeCircleDo();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Scans the collection for deleted items and flushes the deleted item cache.
|
|
635
|
+
*/
|
|
636
|
+
resetCollection(currentCollection, itemSize) {
|
|
637
|
+
if (currentCollection !== undefined && currentCollection !== null && currentCollection === this._previousCollection) {
|
|
638
|
+
console.warn('Attention! The collection must be immutable.');
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
this.updateCache(this._previousCollection, currentCollection, itemSize);
|
|
642
|
+
this._previousCollection = currentCollection;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Update the cache of items from the list
|
|
646
|
+
*/
|
|
647
|
+
updateCache(previousCollection, currentCollection, itemSize) {
|
|
648
|
+
let crudDetected = false;
|
|
649
|
+
if (!currentCollection || currentCollection.length === 0) {
|
|
650
|
+
if (previousCollection) {
|
|
651
|
+
// deleted
|
|
652
|
+
for (let i = 0, l = previousCollection.length; i < l; i++) {
|
|
653
|
+
const item = previousCollection[i], id = item.id;
|
|
654
|
+
crudDetected = true;
|
|
655
|
+
if (this._map.has(id)) {
|
|
656
|
+
this._map.delete(id);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (!previousCollection || previousCollection.length === 0) {
|
|
663
|
+
if (currentCollection) {
|
|
664
|
+
// added
|
|
665
|
+
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
666
|
+
crudDetected = true;
|
|
667
|
+
const item = currentCollection[i], id = item.id;
|
|
668
|
+
this._map.set(id, { width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const collectionDict = {};
|
|
674
|
+
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
675
|
+
const item = currentCollection[i];
|
|
676
|
+
if (item) {
|
|
677
|
+
collectionDict[item.id] = item;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const notChangedMap = {}, deletedMap = {}, deletedItemsMap = {}, updatedMap = {};
|
|
681
|
+
for (let i = 0, l = previousCollection.length; i < l; i++) {
|
|
682
|
+
const item = previousCollection[i], id = item.id;
|
|
683
|
+
if (item) {
|
|
684
|
+
if (collectionDict.hasOwnProperty(id)) {
|
|
685
|
+
if (item === collectionDict[id]) {
|
|
686
|
+
// not changed
|
|
687
|
+
notChangedMap[item.id] = item;
|
|
688
|
+
this._map.set(id, { ...(this._map.get(id) || { width: itemSize, height: itemSize }), method: ItemDisplayMethods.NOT_CHANGED });
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
// updated
|
|
693
|
+
crudDetected = true;
|
|
694
|
+
updatedMap[item.id] = item;
|
|
695
|
+
this._map.set(id, { ...(this._map.get(id) || { width: itemSize, height: itemSize }), method: ItemDisplayMethods.UPDATE });
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// deleted
|
|
700
|
+
crudDetected = true;
|
|
701
|
+
deletedMap[item.id] = item;
|
|
702
|
+
deletedItemsMap[i] = this._map.get(item.id);
|
|
703
|
+
this._map.delete(id);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
for (let i = 0, l = currentCollection.length; i < l; i++) {
|
|
707
|
+
const item = currentCollection[i], id = item.id;
|
|
708
|
+
if (item && !deletedMap.hasOwnProperty(id) && !updatedMap.hasOwnProperty(id) && !notChangedMap.hasOwnProperty(id)) {
|
|
709
|
+
// added
|
|
710
|
+
crudDetected = true;
|
|
711
|
+
this._map.set(id, { width: itemSize, height: itemSize, method: ItemDisplayMethods.CREATE });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
this._crudDetected = crudDetected;
|
|
715
|
+
this._deletedItemsMap = deletedItemsMap;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Finds the position of a collection element by the given Id
|
|
719
|
+
*/
|
|
720
|
+
getItemPosition(id, stickyMap, options) {
|
|
721
|
+
const opt = { fromItemId: id, stickyMap, ...options };
|
|
722
|
+
const { scrollSize } = this.recalculateMetrics({
|
|
723
|
+
...opt,
|
|
724
|
+
dynamicSize: this._crudDetected || opt.dynamicSize,
|
|
725
|
+
previousTotalSize: this._previousTotalSize,
|
|
726
|
+
crudDetected: this._crudDetected,
|
|
727
|
+
deletedItemsMap: this._deletedItemsMap,
|
|
728
|
+
});
|
|
729
|
+
return scrollSize;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Updates the collection of display objects
|
|
733
|
+
*/
|
|
734
|
+
updateCollection(items, stickyMap, options) {
|
|
735
|
+
const opt = { stickyMap, ...options }, crudDetected = this._crudDetected, deletedItemsMap = this._deletedItemsMap;
|
|
736
|
+
if (opt.dynamicSize) {
|
|
737
|
+
this.cacheElements();
|
|
738
|
+
}
|
|
739
|
+
const metrics = this.recalculateMetrics({
|
|
740
|
+
...opt,
|
|
741
|
+
collection: items,
|
|
742
|
+
previousTotalSize: this._previousTotalSize,
|
|
743
|
+
crudDetected: this._crudDetected,
|
|
744
|
+
deletedItemsMap,
|
|
745
|
+
});
|
|
746
|
+
this._delta += metrics.delta;
|
|
747
|
+
this._previousTotalSize = metrics.totalSize;
|
|
748
|
+
this._deletedItemsMap = {};
|
|
749
|
+
this._crudDetected = false;
|
|
750
|
+
if (opt.dynamicSize) {
|
|
751
|
+
this.snapshot();
|
|
752
|
+
}
|
|
753
|
+
const displayItems = this.generateDisplayCollection(items, stickyMap, { ...metrics, });
|
|
754
|
+
return { displayItems, totalSize: metrics.totalSize, delta: metrics.delta, crudDetected };
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Finds the closest element in the collection by scrollSize
|
|
758
|
+
*/
|
|
759
|
+
getNearestItem(scrollSize, items, itemSize, isVertical) {
|
|
760
|
+
return this.getElementFromStart(scrollSize, items, this._map, itemSize, isVertical);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Calculates the position of an element based on the given scrollSize
|
|
764
|
+
*/
|
|
765
|
+
getElementFromStart(scrollSize, collection, map, typicalItemSize, isVertical) {
|
|
766
|
+
const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME;
|
|
767
|
+
let offset = 0;
|
|
768
|
+
for (let i = 0, l = collection.length; i < l; i++) {
|
|
769
|
+
const item = collection[i];
|
|
770
|
+
let itemSize = 0;
|
|
771
|
+
if (map.has(item.id)) {
|
|
772
|
+
const bounds = map.get(item.id);
|
|
773
|
+
itemSize = bounds ? bounds[sizeProperty] : typicalItemSize;
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
itemSize = typicalItemSize;
|
|
777
|
+
}
|
|
778
|
+
if (offset > scrollSize) {
|
|
779
|
+
return item;
|
|
780
|
+
}
|
|
781
|
+
offset += itemSize;
|
|
782
|
+
}
|
|
783
|
+
return undefined;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Calculates the entry into the overscroll area and returns the number of overscroll elements
|
|
787
|
+
*/
|
|
788
|
+
getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical, indexOffset = 0) {
|
|
789
|
+
const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME;
|
|
790
|
+
let offset = 0, num = 0;
|
|
791
|
+
for (let j = collection.length - indexOffset - 1; j >= i; j--) {
|
|
792
|
+
const item = collection[j];
|
|
793
|
+
let itemSize = 0;
|
|
794
|
+
if (map.has(item.id)) {
|
|
795
|
+
const bounds = map.get(item.id);
|
|
796
|
+
itemSize = bounds ? bounds[sizeProperty] : typicalItemSize;
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
itemSize = typicalItemSize;
|
|
800
|
+
}
|
|
801
|
+
offset += itemSize;
|
|
802
|
+
num++;
|
|
803
|
+
if (offset > size) {
|
|
804
|
+
return { num: 0, offset };
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return { num, offset };
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Calculates list metrics
|
|
811
|
+
*/
|
|
812
|
+
recalculateMetrics(options) {
|
|
813
|
+
const { fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap, enabledBufferOptimization, previousTotalSize, crudDetected, deletedItemsMap } = options;
|
|
814
|
+
const { width, height } = bounds, sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, size = isVertical ? height : width, totalLength = collection.length, typicalItemSize = itemSize, w = isVertical ? width : typicalItemSize, h = isVertical ? typicalItemSize : height, map = this._map, snapshot = this._snapshot, checkOverscrollItemsLimit = Math.ceil(size / typicalItemSize), snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
|
|
815
|
+
|| (typeof fromItemId === 'string' && fromItemId > '-1');
|
|
816
|
+
let leftItemsOffset = 0, rightItemsOffset = 0;
|
|
817
|
+
if (enabledBufferOptimization) {
|
|
818
|
+
switch (this.scrollDirection) {
|
|
819
|
+
case 1: {
|
|
820
|
+
leftItemsOffset = 0;
|
|
821
|
+
rightItemsOffset = itemsOffset;
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
case -1: {
|
|
825
|
+
leftItemsOffset = itemsOffset;
|
|
826
|
+
rightItemsOffset = 0;
|
|
827
|
+
break;
|
|
828
|
+
}
|
|
829
|
+
case 0:
|
|
830
|
+
default: {
|
|
831
|
+
leftItemsOffset = rightItemsOffset = itemsOffset;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
leftItemsOffset = rightItemsOffset = itemsOffset;
|
|
837
|
+
}
|
|
838
|
+
let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, leftSizeOfAddedItems = 0, leftSizeOfUpdatedItems = 0, leftSizeOfDeletedItems = 0, itemById = undefined, itemByIdPos = 0, targetDisplayItemIndex = -1, isTargetInOverscroll = false, actualScrollSize = itemByIdPos, totalSize = 0, startIndex;
|
|
839
|
+
// If the list is dynamic or there are new elements in the collection, then it switches to the long algorithm.
|
|
840
|
+
if (dynamicSize) {
|
|
841
|
+
let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
|
|
842
|
+
for (let i = 0, l = collection.length; i < l; i++) {
|
|
843
|
+
const ii = i + 1, collectionItem = collection[i], id = collectionItem.id;
|
|
844
|
+
let componentSize = 0, componentSizeDelta = 0, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
|
|
845
|
+
if (map.has(id)) {
|
|
846
|
+
const bounds = map.get(id) || { width: typicalItemSize, height: typicalItemSize };
|
|
847
|
+
componentSize = bounds[sizeProperty];
|
|
848
|
+
itemDisplayMethod = bounds?.method ?? ItemDisplayMethods.UPDATE;
|
|
849
|
+
switch (itemDisplayMethod) {
|
|
850
|
+
case ItemDisplayMethods.UPDATE: {
|
|
851
|
+
const snapshotBounds = snapshot.get(id);
|
|
852
|
+
const componentSnapshotSize = componentSize - (snapshotBounds ? snapshotBounds[sizeProperty] : typicalItemSize);
|
|
853
|
+
componentSizeDelta = componentSnapshotSize;
|
|
854
|
+
map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
case ItemDisplayMethods.CREATE: {
|
|
858
|
+
componentSizeDelta = typicalItemSize;
|
|
859
|
+
map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (deletedItemsMap.hasOwnProperty(i)) {
|
|
865
|
+
const bounds = deletedItemsMap[i], size = bounds[sizeProperty] ?? typicalItemSize;
|
|
866
|
+
if (y < scrollSize - size) {
|
|
867
|
+
leftSizeOfDeletedItems += size;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
totalSize += componentSize;
|
|
871
|
+
if (isFromId) {
|
|
872
|
+
if (itemById === undefined) {
|
|
873
|
+
if (id !== fromItemId && stickyMap && stickyMap[id] > 0) {
|
|
874
|
+
stickyComponentSize = componentSize;
|
|
875
|
+
stickyCollectionItem = collectionItem;
|
|
876
|
+
}
|
|
877
|
+
if (id === fromItemId) {
|
|
878
|
+
targetDisplayItemIndex = i;
|
|
879
|
+
if (stickyCollectionItem && stickyMap) {
|
|
880
|
+
const { num } = this.getElementNumToEnd(i, collection, map, typicalItemSize, size, isVertical);
|
|
881
|
+
if (num > 0) {
|
|
882
|
+
isTargetInOverscroll = true;
|
|
883
|
+
y -= size - componentSize;
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
if (stickyMap && !stickyMap[collectionItem.id] && y >= scrollSize && y < scrollSize + stickyComponentSize) {
|
|
887
|
+
const snappedY = scrollSize - stickyComponentSize;
|
|
888
|
+
leftHiddenItemsWeight -= (snappedY - y);
|
|
889
|
+
y = snappedY;
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
y -= stickyComponentSize;
|
|
893
|
+
leftHiddenItemsWeight -= stickyComponentSize;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
itemById = collectionItem;
|
|
898
|
+
itemByIdPos = y;
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
leftItemsWeights.push(componentSize);
|
|
902
|
+
leftHiddenItemsWeight += componentSize;
|
|
903
|
+
itemsFromStartToScrollEnd = ii;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
else if (y <= scrollSize - componentSize) {
|
|
908
|
+
leftItemsWeights.push(componentSize);
|
|
909
|
+
leftHiddenItemsWeight += componentSize;
|
|
910
|
+
itemsFromStartToScrollEnd = ii;
|
|
911
|
+
}
|
|
912
|
+
if (isFromId) {
|
|
913
|
+
if (itemById === undefined || y < itemByIdPos + size + componentSize) {
|
|
914
|
+
itemsFromStartToDisplayEnd = ii;
|
|
915
|
+
totalItemsToDisplayEndWeight += componentSize;
|
|
916
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
else if (y <= scrollSize + size + componentSize) {
|
|
920
|
+
itemsFromStartToDisplayEnd = ii;
|
|
921
|
+
totalItemsToDisplayEndWeight += componentSize;
|
|
922
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + rightItemsOffset;
|
|
923
|
+
if (y <= scrollSize - componentSize) {
|
|
924
|
+
switch (itemDisplayMethod) {
|
|
925
|
+
case ItemDisplayMethods.CREATE: {
|
|
926
|
+
leftSizeOfAddedItems += componentSizeDelta;
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
case ItemDisplayMethods.UPDATE: {
|
|
930
|
+
leftSizeOfUpdatedItems += componentSizeDelta;
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
case ItemDisplayMethods.DELETE: {
|
|
934
|
+
leftSizeOfDeletedItems += componentSizeDelta;
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
if (i < itemsFromDisplayEndToOffsetEnd) {
|
|
942
|
+
rightItemsWeight += componentSize;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
y += componentSize;
|
|
946
|
+
}
|
|
947
|
+
if (isTargetInOverscroll) {
|
|
948
|
+
const { num } = this.getElementNumToEnd(collection.length - (checkOverscrollItemsLimit < 0 ? 0 : collection.length - checkOverscrollItemsLimit), collection, map, typicalItemSize, size, isVertical, collection.length - (collection.length - (targetDisplayItemIndex + 1)));
|
|
949
|
+
if (num > 0) {
|
|
950
|
+
itemsFromStartToScrollEnd -= num;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (itemsFromStartToScrollEnd <= -1) {
|
|
954
|
+
itemsFromStartToScrollEnd = 0;
|
|
955
|
+
}
|
|
956
|
+
if (itemsFromStartToDisplayEnd <= -1) {
|
|
957
|
+
itemsFromStartToDisplayEnd = 0;
|
|
958
|
+
}
|
|
959
|
+
actualScrollSize = isFromId ? itemByIdPos : scrollSize;
|
|
960
|
+
leftItemsWeights.splice(0, leftItemsWeights.length - leftItemsOffset);
|
|
961
|
+
leftItemsWeights.forEach(v => {
|
|
962
|
+
leftItemsWeight += v;
|
|
963
|
+
});
|
|
964
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, leftItemsOffset);
|
|
965
|
+
rightItemLength = itemsFromStartToDisplayEnd + rightItemsOffset > totalLength
|
|
966
|
+
? totalLength - itemsFromStartToDisplayEnd : rightItemsOffset;
|
|
967
|
+
}
|
|
968
|
+
else
|
|
969
|
+
// Buffer optimization does not work on fast linear algorithm
|
|
970
|
+
{
|
|
971
|
+
if (crudDetected) {
|
|
972
|
+
let y = 0;
|
|
973
|
+
for (let i = 0, l = collection.length; i < l; i++) {
|
|
974
|
+
const collectionItem = collection[i], id = collectionItem.id;
|
|
975
|
+
let componentSize = typicalItemSize, itemDisplayMethod = ItemDisplayMethods.NOT_CHANGED;
|
|
976
|
+
if (map.has(id)) {
|
|
977
|
+
const bounds = map.get(id);
|
|
978
|
+
itemDisplayMethod = bounds?.method ?? ItemDisplayMethods.UPDATE;
|
|
979
|
+
if (itemDisplayMethod === ItemDisplayMethods.CREATE) {
|
|
980
|
+
map.set(id, { ...bounds, method: ItemDisplayMethods.NOT_CHANGED });
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (deletedItemsMap.hasOwnProperty(i)) {
|
|
984
|
+
const bounds = deletedItemsMap[i], size = bounds[sizeProperty] ?? typicalItemSize;
|
|
985
|
+
if (y < scrollSize - size) {
|
|
986
|
+
leftSizeOfDeletedItems += size;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (y < scrollSize - componentSize) {
|
|
990
|
+
switch (itemDisplayMethod) {
|
|
991
|
+
case ItemDisplayMethods.CREATE: {
|
|
992
|
+
leftSizeOfUpdatedItems += componentSize;
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
case ItemDisplayMethods.UPDATE: {
|
|
996
|
+
leftSizeOfUpdatedItems += componentSize;
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
case ItemDisplayMethods.DELETE: {
|
|
1000
|
+
leftSizeOfDeletedItems += componentSize;
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
y += componentSize;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
itemsFromStartToScrollEnd = Math.floor(scrollSize / typicalItemSize);
|
|
1009
|
+
itemsFromStartToDisplayEnd = Math.ceil((scrollSize + size) / typicalItemSize);
|
|
1010
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
1011
|
+
rightItemLength = itemsFromStartToDisplayEnd + itemsOffset > totalLength
|
|
1012
|
+
? totalLength - itemsFromStartToDisplayEnd : itemsOffset;
|
|
1013
|
+
leftItemsWeight = leftItemLength * typicalItemSize;
|
|
1014
|
+
rightItemsWeight = rightItemLength * typicalItemSize;
|
|
1015
|
+
leftHiddenItemsWeight = itemsFromStartToScrollEnd * typicalItemSize;
|
|
1016
|
+
totalItemsToDisplayEndWeight = itemsFromStartToDisplayEnd * typicalItemSize;
|
|
1017
|
+
totalSize = totalLength * typicalItemSize;
|
|
1018
|
+
const k = totalSize !== 0 ? previousTotalSize / totalSize : 0;
|
|
1019
|
+
actualScrollSize = scrollSize * k;
|
|
1020
|
+
}
|
|
1021
|
+
startIndex = Math.min(itemsFromStartToScrollEnd - leftItemLength, totalLength > 0 ? totalLength - 1 : 0);
|
|
1022
|
+
const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = leftSizeOfUpdatedItems + leftSizeOfAddedItems - leftSizeOfDeletedItems;
|
|
1023
|
+
const metrics = {
|
|
1024
|
+
delta,
|
|
1025
|
+
normalizedItemWidth: w,
|
|
1026
|
+
normalizedItemHeight: h,
|
|
1027
|
+
width,
|
|
1028
|
+
height,
|
|
1029
|
+
dynamicSize,
|
|
1030
|
+
itemSize,
|
|
1031
|
+
itemsFromStartToScrollEnd,
|
|
1032
|
+
itemsFromStartToDisplayEnd,
|
|
1033
|
+
itemsOnDisplay,
|
|
1034
|
+
itemsOnDisplayLength,
|
|
1035
|
+
isVertical,
|
|
1036
|
+
leftHiddenItemsWeight,
|
|
1037
|
+
leftItemLength,
|
|
1038
|
+
leftItemsWeight,
|
|
1039
|
+
renderItems,
|
|
1040
|
+
rightItemLength,
|
|
1041
|
+
rightItemsWeight,
|
|
1042
|
+
scrollSize: actualScrollSize,
|
|
1043
|
+
leftSizeOfAddedItems,
|
|
1044
|
+
sizeProperty,
|
|
1045
|
+
snap,
|
|
1046
|
+
snippedPos,
|
|
1047
|
+
startIndex,
|
|
1048
|
+
startPosition,
|
|
1049
|
+
totalItemsToDisplayEndWeight,
|
|
1050
|
+
totalLength,
|
|
1051
|
+
totalSize,
|
|
1052
|
+
typicalItemSize,
|
|
1053
|
+
};
|
|
1054
|
+
return metrics;
|
|
1055
|
+
}
|
|
1056
|
+
clearDeltaDirection() {
|
|
1057
|
+
this.clearScrollDirectionCache();
|
|
1058
|
+
}
|
|
1059
|
+
clearDelta(clearDirectionDetector = false) {
|
|
1060
|
+
this._delta = 0;
|
|
1061
|
+
if (clearDirectionDetector) {
|
|
1062
|
+
this.clearScrollDirectionCache();
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
changes() {
|
|
1066
|
+
this.bumpVersion();
|
|
1067
|
+
}
|
|
1068
|
+
generateDisplayCollection(items, stickyMap, metrics) {
|
|
1069
|
+
const { normalizedItemWidth, normalizedItemHeight, dynamicSize, itemsFromStartToScrollEnd, isVertical, renderItems: renderItemsLength, scrollSize, sizeProperty, snap, snippedPos, startPosition, totalLength, startIndex, typicalItemSize, } = metrics, displayItems = [];
|
|
1070
|
+
if (items.length) {
|
|
1071
|
+
const actualSnippedPosition = snippedPos;
|
|
1072
|
+
let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0;
|
|
1073
|
+
if (snap) {
|
|
1074
|
+
for (let i = Math.min(itemsFromStartToScrollEnd > 0 ? itemsFromStartToScrollEnd : 0, totalLength - 1); i >= 0; i--) {
|
|
1075
|
+
const id = items[i].id, sticky = stickyMap[id], size = dynamicSize ? this.get(id)?.[sizeProperty] || typicalItemSize : typicalItemSize;
|
|
1076
|
+
if (sticky > 0) {
|
|
1077
|
+
const measures = {
|
|
1078
|
+
x: isVertical ? 0 : actualSnippedPosition,
|
|
1079
|
+
y: isVertical ? actualSnippedPosition : 0,
|
|
1080
|
+
width: normalizedItemWidth,
|
|
1081
|
+
height: normalizedItemHeight,
|
|
1082
|
+
}, config = {
|
|
1083
|
+
isVertical,
|
|
1084
|
+
sticky,
|
|
1085
|
+
snap,
|
|
1086
|
+
snapped: true,
|
|
1087
|
+
snappedOut: false,
|
|
1088
|
+
dynamic: dynamicSize,
|
|
1089
|
+
};
|
|
1090
|
+
const itemData = items[i];
|
|
1091
|
+
stickyItem = { id, measures, data: itemData, config };
|
|
1092
|
+
stickyItemIndex = i;
|
|
1093
|
+
stickyItemSize = size;
|
|
1094
|
+
displayItems.push(stickyItem);
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
let i = startIndex;
|
|
1100
|
+
while (renderItems > 0) {
|
|
1101
|
+
if (i >= totalLength) {
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
const id = items[i].id, size = dynamicSize ? this.get(id)?.[sizeProperty] || typicalItemSize : typicalItemSize;
|
|
1105
|
+
if (id !== stickyItem?.id) {
|
|
1106
|
+
const snapped = snap && stickyMap[id] > 0 && pos <= scrollSize, measures = {
|
|
1107
|
+
x: isVertical ? 0 : pos,
|
|
1108
|
+
y: isVertical ? pos : 0,
|
|
1109
|
+
width: normalizedItemWidth,
|
|
1110
|
+
height: normalizedItemHeight,
|
|
1111
|
+
}, config = {
|
|
1112
|
+
isVertical,
|
|
1113
|
+
sticky: stickyMap[id],
|
|
1114
|
+
snap,
|
|
1115
|
+
snapped: false,
|
|
1116
|
+
snappedOut: false,
|
|
1117
|
+
dynamic: dynamicSize,
|
|
1118
|
+
};
|
|
1119
|
+
const itemData = items[i];
|
|
1120
|
+
const item = { id, measures, data: itemData, config };
|
|
1121
|
+
if (!nextSticky && stickyItemIndex < i && stickyMap[id] > 0 && pos <= scrollSize + size + stickyItemSize) {
|
|
1122
|
+
item.measures.x = isVertical ? 0 : snapped ? actualSnippedPosition : pos;
|
|
1123
|
+
item.measures.y = isVertical ? snapped ? actualSnippedPosition : pos : 0;
|
|
1124
|
+
nextSticky = item;
|
|
1125
|
+
nextSticky.config.snapped = snapped;
|
|
1126
|
+
}
|
|
1127
|
+
displayItems.push(item);
|
|
1128
|
+
}
|
|
1129
|
+
renderItems -= 1;
|
|
1130
|
+
pos += size;
|
|
1131
|
+
i++;
|
|
1132
|
+
}
|
|
1133
|
+
const axis = isVertical ? Y_PROP_NAME : X_PROP_NAME;
|
|
1134
|
+
if (nextSticky && stickyItem && nextSticky.measures[axis] <= scrollSize + stickyItemSize) {
|
|
1135
|
+
if (nextSticky.measures[axis] > scrollSize) {
|
|
1136
|
+
stickyItem.measures[axis] = nextSticky.measures[axis] - stickyItemSize;
|
|
1137
|
+
stickyItem.config.snapped = nextSticky.config.snapped = false;
|
|
1138
|
+
stickyItem.config.snappedOut = true;
|
|
1139
|
+
stickyItem.config.sticky = 1;
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
nextSticky.config.snapped = true;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return displayItems;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* tracking by propName
|
|
1150
|
+
*/
|
|
1151
|
+
track() {
|
|
1152
|
+
if (!this._items || !this._displayComponents) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
this._tracker.track(this._items, this._displayComponents, this.scrollDirection);
|
|
1156
|
+
}
|
|
1157
|
+
setDisplayObjectIndexMapById(v) {
|
|
1158
|
+
this._tracker.displayObjectIndexMapById = v;
|
|
1159
|
+
}
|
|
1160
|
+
untrackComponentByIdProperty(component) {
|
|
1161
|
+
this._tracker.untrackComponentByIdProperty(component);
|
|
1162
|
+
}
|
|
1163
|
+
getItemBounds(id) {
|
|
1164
|
+
if (this.has(id)) {
|
|
1165
|
+
return this.get(id);
|
|
1166
|
+
}
|
|
1167
|
+
return undefined;
|
|
1168
|
+
}
|
|
1169
|
+
cacheElements() {
|
|
1170
|
+
if (!this._displayComponents) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
for (let i = 0, l = this._displayComponents.length; i < l; i++) {
|
|
1174
|
+
const component = this._displayComponents[i], itemId = component.instance.itemId;
|
|
1175
|
+
if (itemId === undefined) {
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
const bounds = component.instance.getBounds();
|
|
1179
|
+
this.set(itemId, bounds);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
dispose() {
|
|
1183
|
+
super.dispose();
|
|
1184
|
+
if (this._tracker) {
|
|
1185
|
+
this._tracker.dispose();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Scroll event.
|
|
1192
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/utils/scrollEvent.ts
|
|
1193
|
+
* @author Evgenii Grebennikov
|
|
1194
|
+
* @email djonnyx@gmail.com
|
|
1195
|
+
*/
|
|
1196
|
+
class ScrollEvent {
|
|
1197
|
+
_direction = 1;
|
|
1198
|
+
get direction() { return this._direction; }
|
|
1199
|
+
_scrollSize = 0;
|
|
1200
|
+
get scrollSize() { return this._scrollSize; }
|
|
1201
|
+
_scrollWeight = 0;
|
|
1202
|
+
get scrollWeight() { return this._scrollWeight; }
|
|
1203
|
+
_isVertical = true;
|
|
1204
|
+
get isVertical() { return this._isVertical; }
|
|
1205
|
+
_listSize = 0;
|
|
1206
|
+
get listSize() { return this._listSize; }
|
|
1207
|
+
_size = 0;
|
|
1208
|
+
get size() { return this._size; }
|
|
1209
|
+
_isStart = true;
|
|
1210
|
+
get isStart() { return this._isStart; }
|
|
1211
|
+
_isEnd = false;
|
|
1212
|
+
get isEnd() { return this._isEnd; }
|
|
1213
|
+
_delta = 0;
|
|
1214
|
+
get delta() { return this._delta; }
|
|
1215
|
+
_scrollDelta = 0;
|
|
1216
|
+
get scrollDelta() { return this._scrollDelta; }
|
|
1217
|
+
constructor(params) {
|
|
1218
|
+
const { direction, isVertical, container, list, delta, scrollDelta } = params;
|
|
1219
|
+
this._direction = direction;
|
|
1220
|
+
this._isVertical = isVertical;
|
|
1221
|
+
this._scrollSize = isVertical ? container.scrollTop : container.scrollLeft;
|
|
1222
|
+
this._scrollWeight = isVertical ? container.scrollHeight : container.scrollWidth;
|
|
1223
|
+
this._listSize = isVertical ? list.offsetHeight : list.offsetWidth;
|
|
1224
|
+
this._size = isVertical ? container.offsetHeight : container.offsetWidth;
|
|
1225
|
+
this._isEnd = (this._scrollSize + this._size) === this._scrollWeight;
|
|
1226
|
+
this._delta = delta;
|
|
1227
|
+
this._scrollDelta = scrollDelta;
|
|
1228
|
+
this._isStart = this._scrollSize === 0;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Virtual list component.
|
|
1234
|
+
* Maximum performance for extremely large lists.
|
|
1235
|
+
* It is based on algorithms for virtualization of screen objects.
|
|
1236
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/19.x/projects/ng-virtual-list/src/lib/ng-virtual-list.component.ts
|
|
1237
|
+
* @author Evgenii Grebennikov
|
|
1238
|
+
* @email djonnyx@gmail.com
|
|
1239
|
+
*/
|
|
1240
|
+
class NgVirtualListComponent {
|
|
1241
|
+
static __nextId = 0;
|
|
1242
|
+
_id = NgVirtualListComponent.__nextId;
|
|
1243
|
+
/**
|
|
1244
|
+
* Readonly. Returns the unique identifier of the component.
|
|
1245
|
+
*/
|
|
1246
|
+
get id() { return this._id; }
|
|
1247
|
+
_listContainerRef;
|
|
1248
|
+
_container = viewChild('container');
|
|
1249
|
+
_list = viewChild('list');
|
|
1250
|
+
/**
|
|
1251
|
+
* Fires when the list has been scrolled.
|
|
1252
|
+
*/
|
|
1253
|
+
onScroll = output();
|
|
1254
|
+
/**
|
|
1255
|
+
* Fires when the list has completed scrolling.
|
|
1256
|
+
*/
|
|
1257
|
+
onScrollEnd = output();
|
|
1258
|
+
_itemsOptions = {
|
|
1259
|
+
transform: (v) => {
|
|
1260
|
+
this._trackBox.resetCollection(v, this.itemSize());
|
|
1261
|
+
return v;
|
|
1262
|
+
},
|
|
1263
|
+
};
|
|
1264
|
+
/**
|
|
1265
|
+
* Collection of list items.
|
|
1266
|
+
*/
|
|
1267
|
+
items = input.required({
|
|
1268
|
+
...this._itemsOptions,
|
|
1269
|
+
});
|
|
1270
|
+
/**
|
|
1271
|
+
* Determines whether elements will snap. Default value is "true".
|
|
1272
|
+
*/
|
|
1273
|
+
snap = input(DEFAULT_SNAP);
|
|
1274
|
+
/**
|
|
1275
|
+
* Experimental!
|
|
1276
|
+
* Enables buffer optimization.
|
|
1277
|
+
* 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.
|
|
1278
|
+
* Works only if the property dynamic = true
|
|
1279
|
+
*/
|
|
1280
|
+
enabledBufferOptimization = input(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
|
|
1281
|
+
/**
|
|
1282
|
+
* Rendering element template.
|
|
1283
|
+
*/
|
|
1284
|
+
itemRenderer = input.required();
|
|
1285
|
+
/**
|
|
1286
|
+
* Dictionary zIndex by id of the list element. If the value is not set or equal to 0,
|
|
1287
|
+
* then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element.
|
|
1288
|
+
*/
|
|
1289
|
+
stickyMap = input({});
|
|
1290
|
+
_itemSizeOptions = {
|
|
1291
|
+
transform: (v) => {
|
|
1292
|
+
if (v === undefined) {
|
|
1293
|
+
return DEFAULT_ITEM_SIZE;
|
|
1294
|
+
}
|
|
1295
|
+
const val = Number(v);
|
|
1296
|
+
return Number.isNaN(val) || val <= 0 ? DEFAULT_ITEM_SIZE : val;
|
|
1297
|
+
},
|
|
1298
|
+
};
|
|
1299
|
+
/**
|
|
1300
|
+
* If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element.
|
|
1301
|
+
* Ignored if the dynamicSize property is true.
|
|
1302
|
+
*/
|
|
1303
|
+
itemSize = input(DEFAULT_ITEM_SIZE, { ...this._itemSizeOptions });
|
|
1304
|
+
/**
|
|
1305
|
+
* If true then the items in the list can have different sizes and the itemSize property is ignored.
|
|
1306
|
+
* If false then the items in the list have a fixed size specified by the itemSize property. The default value is false.
|
|
1307
|
+
*/
|
|
1308
|
+
dynamicSize = input(DEFAULT_DYNAMIC_SIZE);
|
|
1309
|
+
/**
|
|
1310
|
+
* Determines the direction in which elements are placed. Default value is "vertical".
|
|
1311
|
+
*/
|
|
1312
|
+
direction = input(DEFAULT_DIRECTION);
|
|
1313
|
+
/**
|
|
1314
|
+
* Number of elements outside the scope of visibility. Default value is 2.
|
|
1315
|
+
*/
|
|
1316
|
+
itemsOffset = input(DEFAULT_ITEMS_OFFSET);
|
|
1317
|
+
_isVertical = this.getIsVertical();
|
|
1318
|
+
_displayComponents = [];
|
|
1319
|
+
_bounds = signal(null);
|
|
1320
|
+
_scrollSize = signal(0);
|
|
1321
|
+
_resizeObserver = null;
|
|
1322
|
+
_onResizeHandler = () => {
|
|
1323
|
+
this._bounds.set(this._container()?.nativeElement?.getBoundingClientRect() ?? null);
|
|
1324
|
+
};
|
|
1325
|
+
_onScrollHandler = (e) => {
|
|
1326
|
+
this.clearScrollToRepeatExecutionTimeout();
|
|
1327
|
+
const container = this._container()?.nativeElement;
|
|
1328
|
+
if (container) {
|
|
1329
|
+
const scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft), actualScrollSize = scrollSize;
|
|
1330
|
+
this._scrollSize.set(actualScrollSize);
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
_elementRef = inject((ElementRef));
|
|
1334
|
+
_initialized;
|
|
1335
|
+
$initialized;
|
|
1336
|
+
/**
|
|
1337
|
+
* The name of the property by which tracking is performed
|
|
1338
|
+
*/
|
|
1339
|
+
trackBy = input(TRACK_BY_PROPERTY_NAME);
|
|
1340
|
+
/**
|
|
1341
|
+
* Dictionary of element sizes by their id
|
|
1342
|
+
*/
|
|
1343
|
+
_trackBox = new TrackBox(this.trackBy());
|
|
1344
|
+
_onTrackBoxChangeHandler = (v) => {
|
|
1345
|
+
this._cacheVersion.set(v);
|
|
1346
|
+
};
|
|
1347
|
+
_cacheVersion = signal(-1);
|
|
1348
|
+
constructor() {
|
|
1349
|
+
NgVirtualListComponent.__nextId = NgVirtualListComponent.__nextId + 1 === Number.MAX_SAFE_INTEGER
|
|
1350
|
+
? 0 : NgVirtualListComponent.__nextId + 1;
|
|
1351
|
+
this._id = NgVirtualListComponent.__nextId;
|
|
1352
|
+
this._initialized = signal(false);
|
|
1353
|
+
this.$initialized = toObservable(this._initialized);
|
|
1354
|
+
this._trackBox.displayComponents = this._displayComponents;
|
|
1355
|
+
const $trackBy = toObservable(this.trackBy);
|
|
1356
|
+
$trackBy.pipe(takeUntilDestroyed(), tap(v => {
|
|
1357
|
+
this._trackBox.trackingPropertyName = v;
|
|
1358
|
+
})).subscribe();
|
|
1359
|
+
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), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $cacheVersion = toObservable(this._cacheVersion);
|
|
1360
|
+
$isVertical.pipe(takeUntilDestroyed(), tap(v => {
|
|
1361
|
+
this._isVertical = v;
|
|
1362
|
+
const el = this._elementRef.nativeElement;
|
|
1363
|
+
toggleClassName(el, v ? CLASS_LIST_VERTICAL : CLASS_LIST_HORIZONTAL, true);
|
|
1364
|
+
})).subscribe();
|
|
1365
|
+
$dynamicSize.pipe(takeUntilDestroyed(), tap(dynamicSize => {
|
|
1366
|
+
this.listenCacheChangesIfNeed(dynamicSize);
|
|
1367
|
+
})).subscribe();
|
|
1368
|
+
combineLatest([this.$initialized, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
|
|
1369
|
+
$itemsOffset, $snap, $isVertical, $dynamicSize, $enabledBufferOptimization, $cacheVersion,
|
|
1370
|
+
]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), switchMap(([, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, enabledBufferOptimization, cacheVersion,]) => {
|
|
1371
|
+
const { width, height } = bounds;
|
|
1372
|
+
let actualScrollSize = (this._isVertical ? this._container()?.nativeElement.scrollTop ?? 0 : this._container()?.nativeElement.scrollLeft) ?? 0;
|
|
1373
|
+
const opts = {
|
|
1374
|
+
bounds: { width, height }, dynamicSize, isVertical, itemSize,
|
|
1375
|
+
itemsOffset, scrollSize: scrollSize, snap, enabledBufferOptimization,
|
|
1376
|
+
};
|
|
1377
|
+
const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
|
|
1378
|
+
...opts, scrollSize: actualScrollSize,
|
|
1379
|
+
});
|
|
1380
|
+
this.resetBoundsSize(isVertical, totalSize);
|
|
1381
|
+
this.createDisplayComponentsIfNeed(displayItems);
|
|
1382
|
+
this.tracking();
|
|
1383
|
+
const container = this._container();
|
|
1384
|
+
if (container) {
|
|
1385
|
+
const delta = this._trackBox.delta;
|
|
1386
|
+
actualScrollSize = actualScrollSize + delta;
|
|
1387
|
+
this._trackBox.clearDelta();
|
|
1388
|
+
if (scrollSize !== actualScrollSize) {
|
|
1389
|
+
const params = {
|
|
1390
|
+
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
1391
|
+
behavior: BEHAVIOR_INSTANT
|
|
1392
|
+
};
|
|
1393
|
+
container.nativeElement.scrollTo(params);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return of(displayItems);
|
|
1397
|
+
})).subscribe();
|
|
1398
|
+
combineLatest([this.$initialized, toObservable(this.itemRenderer)]).pipe(takeUntilDestroyed(), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
|
|
1399
|
+
this.resetRenderers(itemRenderer);
|
|
1400
|
+
}));
|
|
1401
|
+
}
|
|
1402
|
+
/** @internal */
|
|
1403
|
+
ngOnInit() {
|
|
1404
|
+
this._initialized.set(true);
|
|
1405
|
+
}
|
|
1406
|
+
listenCacheChangesIfNeed(value) {
|
|
1407
|
+
if (value) {
|
|
1408
|
+
if (!this._trackBox.hasEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler)) {
|
|
1409
|
+
this._trackBox.addEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
if (this._trackBox.hasEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler)) {
|
|
1414
|
+
this._trackBox.removeEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
getIsVertical(d) {
|
|
1419
|
+
const dir = d || this.direction();
|
|
1420
|
+
return isDirection(dir, Directions.VERTICAL);
|
|
1421
|
+
}
|
|
1422
|
+
_componentsResizeObserver = new ResizeObserver(() => {
|
|
1423
|
+
this._trackBox.changes();
|
|
1424
|
+
});
|
|
1425
|
+
createDisplayComponentsIfNeed(displayItems) {
|
|
1426
|
+
if (!displayItems || !this._listContainerRef) {
|
|
1427
|
+
this._trackBox.setDisplayObjectIndexMapById({});
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
this._trackBox.items = displayItems;
|
|
1431
|
+
const _listContainerRef = this._listContainerRef;
|
|
1432
|
+
const maxLength = displayItems.length, components = this._displayComponents;
|
|
1433
|
+
while (components.length < maxLength) {
|
|
1434
|
+
if (_listContainerRef) {
|
|
1435
|
+
const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
|
|
1436
|
+
components.push(comp);
|
|
1437
|
+
this._componentsResizeObserver.observe(comp.instance.element);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
this.resetRenderers();
|
|
1441
|
+
}
|
|
1442
|
+
resetRenderers(itemRenderer) {
|
|
1443
|
+
const doMap = {};
|
|
1444
|
+
for (let i = 0, l = this._displayComponents.length; i < l; i++) {
|
|
1445
|
+
const item = this._displayComponents[i];
|
|
1446
|
+
item.instance.renderer = itemRenderer || this.itemRenderer();
|
|
1447
|
+
doMap[item.instance.id] = i;
|
|
1448
|
+
}
|
|
1449
|
+
this._trackBox.setDisplayObjectIndexMapById(doMap);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Tracking by id
|
|
1453
|
+
*/
|
|
1454
|
+
tracking() {
|
|
1455
|
+
this._trackBox.track();
|
|
1456
|
+
}
|
|
1457
|
+
resetBoundsSize(isVertical, totalSize) {
|
|
1458
|
+
const l = this._list();
|
|
1459
|
+
if (l) {
|
|
1460
|
+
l.nativeElement.style[isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME] = `${totalSize}${PX}`;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Returns the bounds of an element with a given id
|
|
1465
|
+
*/
|
|
1466
|
+
getItemBounds(id) {
|
|
1467
|
+
return this._trackBox.getItemBounds(id);
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* The method scrolls the list to the element with the given id and returns the value of the scrolled area.
|
|
1471
|
+
* Behavior accepts the values "auto", "instant" and "smooth".
|
|
1472
|
+
*/
|
|
1473
|
+
scrollTo(id, behavior = BEHAVIOR_AUTO) {
|
|
1474
|
+
this.scrollToExecutor(id, behavior);
|
|
1475
|
+
}
|
|
1476
|
+
_scrollToRepeatExecutionTimeout;
|
|
1477
|
+
clearScrollToRepeatExecutionTimeout() {
|
|
1478
|
+
clearTimeout(this._scrollToRepeatExecutionTimeout);
|
|
1479
|
+
}
|
|
1480
|
+
scrollToExecutor(id, behavior, iteration = 0, isLastIteration = false) {
|
|
1481
|
+
const items = this.items();
|
|
1482
|
+
if (!items || !items.length) {
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
const dynamicSize = this.dynamicSize(), container = this._container(), itemSize = this.itemSize();
|
|
1486
|
+
if (container) {
|
|
1487
|
+
this.clearScrollToRepeatExecutionTimeout();
|
|
1488
|
+
if (dynamicSize) {
|
|
1489
|
+
if (container) {
|
|
1490
|
+
container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1491
|
+
}
|
|
1492
|
+
const { width, height } = this._bounds() || { width: 0, height: 0 }, stickyMap = this.stickyMap(), items = this.items(), isVertical = this._isVertical, delta = this._trackBox.delta, opts = {
|
|
1493
|
+
bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
|
|
1494
|
+
itemsOffset: this.itemsOffset(), scrollSize: (isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft) + delta,
|
|
1495
|
+
snap: this.snap(), fromItemId: id, enabledBufferOptimization: this.enabledBufferOptimization(),
|
|
1496
|
+
}, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
|
|
1497
|
+
this._trackBox.clearDelta();
|
|
1498
|
+
if (container) {
|
|
1499
|
+
const { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
|
|
1500
|
+
...opts, scrollSize, fromItemId: isLastIteration ? undefined : id,
|
|
1501
|
+
}), delta = this._trackBox.delta;
|
|
1502
|
+
this._trackBox.clearDelta();
|
|
1503
|
+
let actualScrollSize = scrollSize + delta;
|
|
1504
|
+
this.resetBoundsSize(isVertical, totalSize);
|
|
1505
|
+
this.createDisplayComponentsIfNeed(displayItems);
|
|
1506
|
+
this.tracking();
|
|
1507
|
+
const _scrollSize = this._trackBox.getItemPosition(id, stickyMap, { ...opts, scrollSize: actualScrollSize, fromItemId: id });
|
|
1508
|
+
const notChanged = actualScrollSize === _scrollSize;
|
|
1509
|
+
if (!notChanged || iteration < MAX_SCROLL_TO_ITERATIONS) {
|
|
1510
|
+
this.clearScrollToRepeatExecutionTimeout();
|
|
1511
|
+
this._scrollToRepeatExecutionTimeout = setTimeout(() => {
|
|
1512
|
+
this.scrollToExecutor(id, BEHAVIOR_INSTANT, iteration + 1, notChanged);
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
this._scrollSize.set(actualScrollSize);
|
|
1517
|
+
container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
container.nativeElement.scrollTo(params);
|
|
1521
|
+
this._scrollSize.set(scrollSize);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize();
|
|
1525
|
+
const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
|
|
1526
|
+
container.nativeElement.scrollTo(params);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Scrolls the scroll area to the desired element with the specified ID.
|
|
1532
|
+
*/
|
|
1533
|
+
scrollToEnd(behavior = BEHAVIOR_INSTANT) {
|
|
1534
|
+
const items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0];
|
|
1535
|
+
this.scrollTo(latItem.id, behavior);
|
|
1536
|
+
}
|
|
1537
|
+
_onContainerScrollHandler = (e) => {
|
|
1538
|
+
const containerEl = this._container();
|
|
1539
|
+
if (containerEl) {
|
|
1540
|
+
const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
|
|
1541
|
+
this._trackBox.deltaDirection = this._scrollSize() > scrollSize ? -1 : this._scrollSize() < scrollSize ? 1 : 0;
|
|
1542
|
+
const event = new ScrollEvent({
|
|
1543
|
+
direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
|
|
1544
|
+
list: this._list().nativeElement, delta: this._trackBox.delta,
|
|
1545
|
+
scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
|
|
1546
|
+
});
|
|
1547
|
+
this.onScroll.emit(event);
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
_onContainerScrollEndHandler = (e) => {
|
|
1551
|
+
const containerEl = this._container();
|
|
1552
|
+
if (containerEl) {
|
|
1553
|
+
const scrollSize = (this._isVertical ? containerEl.nativeElement.scrollTop : containerEl.nativeElement.scrollLeft);
|
|
1554
|
+
this._trackBox.deltaDirection = this._scrollSize() > scrollSize ? -1 : 0;
|
|
1555
|
+
const event = new ScrollEvent({
|
|
1556
|
+
direction: this._trackBox.scrollDirection, container: containerEl.nativeElement,
|
|
1557
|
+
list: this._list().nativeElement, delta: this._trackBox.delta,
|
|
1558
|
+
scrollDelta: this._trackBox.scrollDelta, isVertical: this._isVertical,
|
|
1559
|
+
});
|
|
1560
|
+
this.onScrollEnd.emit(event);
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
/** @internal */
|
|
1564
|
+
ngAfterViewInit() {
|
|
1565
|
+
const containerEl = this._container();
|
|
1566
|
+
if (containerEl) {
|
|
1567
|
+
// for direction calculation
|
|
1568
|
+
containerEl.nativeElement.addEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1569
|
+
containerEl.nativeElement.addEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1570
|
+
containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1571
|
+
this._resizeObserver = new ResizeObserver(this._onResizeHandler);
|
|
1572
|
+
this._resizeObserver.observe(containerEl.nativeElement);
|
|
1573
|
+
this._onResizeHandler();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/** @internal */
|
|
1577
|
+
ngOnDestroy() {
|
|
1578
|
+
this.clearScrollToRepeatExecutionTimeout();
|
|
1579
|
+
if (this._trackBox) {
|
|
1580
|
+
this._trackBox.dispose();
|
|
1581
|
+
}
|
|
1582
|
+
const containerEl = this._container();
|
|
1583
|
+
if (containerEl) {
|
|
1584
|
+
containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1585
|
+
containerEl.nativeElement.removeEventListener(SCROLL, this._onContainerScrollHandler);
|
|
1586
|
+
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onContainerScrollEndHandler);
|
|
1587
|
+
if (this._componentsResizeObserver) {
|
|
1588
|
+
this._componentsResizeObserver.disconnect();
|
|
1589
|
+
}
|
|
1590
|
+
if (this._resizeObserver) {
|
|
1591
|
+
this._resizeObserver.disconnect();
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (this._displayComponents) {
|
|
1595
|
+
while (this._displayComponents.length > 0) {
|
|
1596
|
+
const comp = this._displayComponents.pop();
|
|
1597
|
+
comp?.destroy();
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1602
|
+
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 }, enabledBufferOptimization: { classPropertyName: "enabledBufferOptimization", publicName: "enabledBufferOptimization", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, stickyMap: { classPropertyName: "stickyMap", publicName: "stickyMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, itemsOffset: { classPropertyName: "itemsOffset", publicName: "itemsOffset", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<div #container part=\"scroller\" class=\"ngvl__container\">\r\n <ul #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </ul>\r\n</div>", styles: [":host{display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.vertical){height:320px}.ngvl__container{overflow:auto;width:100%;height:100%}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
1603
|
+
}
|
|
1604
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
1605
|
+
type: Component,
|
|
1606
|
+
args: [{ selector: 'ng-virtual-list', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, 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"] }]
|
|
1607
|
+
}], ctorParameters: () => [], propDecorators: { _listContainerRef: [{
|
|
1608
|
+
type: ViewChild,
|
|
1609
|
+
args: ['renderersContainer', { read: ViewContainerRef }]
|
|
1610
|
+
}] } });
|
|
1611
|
+
|
|
1612
|
+
/*
|
|
1613
|
+
* Public API Surface of ng-virtual-list
|
|
1614
|
+
*/
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Generated bundle index. Do not edit.
|
|
1618
|
+
*/
|
|
1619
|
+
|
|
1620
|
+
export { NgVirtualListComponent };
|
|
1621
|
+
//# sourceMappingURL=ng-virtual-list.mjs.map
|