ng-virtual-list 0.7.2 → 14.0.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 +44 -71
- package/esm2020/lib/components/ng-virtual-list-item.component.mjs +80 -0
- package/esm2020/lib/const/index.mjs +34 -0
- package/esm2020/lib/enums/direction.mjs +2 -0
- package/esm2020/lib/enums/directions.mjs +18 -0
- package/esm2020/lib/enums/index.mjs +3 -0
- package/esm2020/lib/models/collection.model.mjs +3 -0
- package/esm2020/lib/models/index.mjs +2 -0
- package/esm2020/lib/models/item.model.mjs +3 -0
- package/esm2020/lib/models/render-collection.model.mjs +3 -0
- package/esm2020/lib/models/render-item-config.model.mjs +2 -0
- package/esm2020/lib/models/render-item.model.mjs +3 -0
- package/esm2020/lib/models/sticky-map.model.mjs +2 -0
- package/esm2020/lib/ng-virtual-list.component.mjs +510 -0
- package/esm2020/lib/ng-virtual-list.module.mjs +20 -0
- package/esm2020/lib/types/id.mjs +2 -0
- package/esm2020/lib/types/index.mjs +2 -0
- package/esm2020/lib/types/rect.mjs +2 -0
- package/esm2020/lib/types/size.mjs +2 -0
- package/esm2020/lib/utils/cacheMap.mjs +52 -0
- package/esm2020/lib/utils/debounce.mjs +31 -0
- package/esm2020/lib/utils/disposableComponent.mjs +29 -0
- package/esm2020/lib/utils/eventEmitter.mjs +106 -0
- package/esm2020/lib/utils/index.mjs +8 -0
- package/esm2020/lib/utils/isDirection.mjs +15 -0
- package/esm2020/lib/utils/toggleClassName.mjs +15 -0
- package/esm2020/lib/utils/trackBox.mjs +352 -0
- package/esm2020/lib/utils/tracker.mjs +108 -0
- package/esm2020/ng-virtual-list.mjs +5 -0
- package/esm2020/public-api.mjs +8 -0
- package/fesm2015/ng-virtual-list.mjs +1360 -0
- package/fesm2015/ng-virtual-list.mjs.map +1 -0
- package/fesm2020/ng-virtual-list.mjs +1359 -0
- package/fesm2020/ng-virtual-list.mjs.map +1 -0
- package/index.d.ts +5 -5
- package/lib/components/ng-virtual-list-item.component.d.ts +28 -35
- package/lib/const/index.d.ts +32 -31
- package/lib/enums/direction.d.ts +8 -2
- package/lib/enums/directions.d.ts +16 -4
- package/lib/enums/index.d.ts +4 -4
- package/lib/models/collection.model.d.ts +9 -3
- package/lib/models/index.d.ts +4 -4
- package/lib/models/item.model.d.ts +14 -5
- package/lib/models/render-collection.model.d.ts +9 -3
- package/lib/models/render-item-config.model.d.ts +33 -7
- package/lib/models/render-item.model.d.ts +28 -10
- package/lib/models/sticky-map.model.d.ts +12 -6
- package/lib/ng-virtual-list.component.d.ts +151 -110
- package/lib/ng-virtual-list.module.d.ts +9 -0
- package/lib/types/id.d.ts +7 -1
- package/lib/types/index.d.ts +4 -4
- package/lib/types/rect.d.ts +17 -5
- package/lib/types/size.d.ts +16 -4
- package/lib/utils/cacheMap.d.ts +34 -31
- package/lib/utils/debounce.d.ts +16 -10
- package/lib/utils/disposableComponent.d.ts +15 -0
- package/lib/utils/eventEmitter.d.ts +40 -37
- package/lib/utils/index.d.ts +7 -6
- package/lib/utils/isDirection.d.ts +8 -2
- package/lib/utils/toggleClassName.d.ts +7 -1
- package/lib/utils/trackBox.d.ts +113 -73
- package/lib/utils/tracker.d.ts +38 -38
- package/package.json +18 -6
- package/public-api.d.ts +4 -3
- package/fesm2022/ng-virtual-list.mjs +0 -942
- package/fesm2022/ng-virtual-list.mjs.map +0 -1
|
@@ -0,0 +1,1359 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, ChangeDetectionStrategy, EventEmitter as EventEmitter$1, ViewContainerRef, ElementRef, ViewEncapsulation, ViewChild, Output, Input, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import { Subject, BehaviorSubject, filter, map, takeUntil, tap, combineLatest, distinctUntilChanged, debounceTime, switchMap, of } from 'rxjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Axis of the arrangement of virtual list elements.
|
|
9
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/enums/directions.ts
|
|
10
|
+
* @author Evgenii Grebennikov
|
|
11
|
+
* @email djonnyx@gmail.com
|
|
12
|
+
*/
|
|
13
|
+
var Directions;
|
|
14
|
+
(function (Directions) {
|
|
15
|
+
/**
|
|
16
|
+
* Horizontal axis.
|
|
17
|
+
*/
|
|
18
|
+
Directions["HORIZONTAL"] = "horizontal";
|
|
19
|
+
/**
|
|
20
|
+
* Vertical axis.
|
|
21
|
+
*/
|
|
22
|
+
Directions["VERTICAL"] = "vertical";
|
|
23
|
+
})(Directions || (Directions = {}));
|
|
24
|
+
|
|
25
|
+
const DEFAULT_ITEM_SIZE = 24;
|
|
26
|
+
const DEFAULT_ITEMS_OFFSET = 2;
|
|
27
|
+
const DEFAULT_LIST_SIZE = 400;
|
|
28
|
+
const DEFAULT_SNAP = false;
|
|
29
|
+
const DEFAULT_SNAP_TO_ITEM = false;
|
|
30
|
+
const DEFAULT_DYNAMIC_SIZE = false;
|
|
31
|
+
const TRACK_BY_PROPERTY_NAME = 'id';
|
|
32
|
+
const DEFAULT_DIRECTION = Directions.VERTICAL;
|
|
33
|
+
const DISPLAY_OBJECTS_LENGTH_MESUREMENT_ERROR = 1;
|
|
34
|
+
// presets
|
|
35
|
+
const BEHAVIOR_AUTO = 'auto';
|
|
36
|
+
const BEHAVIOR_INSTANT = 'instant';
|
|
37
|
+
const BEHAVIOR_SMOOTH = 'smooth';
|
|
38
|
+
const VISIBILITY_VISIBLE = 'visible';
|
|
39
|
+
const VISIBILITY_HIDDEN = 'hidden';
|
|
40
|
+
const SIZE_100_PERSENT = '100%';
|
|
41
|
+
const SIZE_AUTO = 'auto';
|
|
42
|
+
const POSITION_ABSOLUTE = 'absolute';
|
|
43
|
+
const POSITION_STICKY = 'sticky';
|
|
44
|
+
const TRANSLATE_3D = 'translate3d';
|
|
45
|
+
const ZEROS_TRANSLATE_3D = `${TRANSLATE_3D}(0,0,0)`;
|
|
46
|
+
const TOP_PROP_NAME = 'top';
|
|
47
|
+
const LEFT_PROP_NAME = 'left';
|
|
48
|
+
const X_PROP_NAME = 'x';
|
|
49
|
+
const Y_PROP_NAME = 'y';
|
|
50
|
+
const WIDTH_PROP_NAME = 'width';
|
|
51
|
+
const HEIGHT_PROP_NAME = 'height';
|
|
52
|
+
const PX = 'px';
|
|
53
|
+
const SCROLL = 'scroll';
|
|
54
|
+
const SCROLL_END = 'scrollend';
|
|
55
|
+
const CLASS_LIST_VERTICAL = 'vertical';
|
|
56
|
+
const CLASS_LIST_HORIZONTAL = 'horizontal';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Virtual list item component
|
|
60
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/components/ng-virtual-list-item.component.ts
|
|
61
|
+
* @author Evgenii Grebennikov
|
|
62
|
+
* @email djonnyx@gmail.com
|
|
63
|
+
*/
|
|
64
|
+
class NgVirtualListItemComponent {
|
|
65
|
+
constructor(_cdr, _elementRef) {
|
|
66
|
+
this._cdr = _cdr;
|
|
67
|
+
this._elementRef = _elementRef;
|
|
68
|
+
this._id = NgVirtualListItemComponent.__nextId = NgVirtualListItemComponent.__nextId === Number.MAX_SAFE_INTEGER
|
|
69
|
+
? 0 : NgVirtualListItemComponent.__nextId + 1;
|
|
70
|
+
}
|
|
71
|
+
get id() {
|
|
72
|
+
return this._id;
|
|
73
|
+
}
|
|
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._cdr.detectChanges();
|
|
94
|
+
}
|
|
95
|
+
get itemId() {
|
|
96
|
+
return this.data?.id;
|
|
97
|
+
}
|
|
98
|
+
set renderer(v) {
|
|
99
|
+
if (this.itemRenderer === v) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.itemRenderer = v;
|
|
103
|
+
this._cdr.markForCheck();
|
|
104
|
+
}
|
|
105
|
+
getBounds() {
|
|
106
|
+
const el = this._elementRef.nativeElement, { width, height, left, top } = el.getBoundingClientRect();
|
|
107
|
+
return { width, height, x: left, y: top };
|
|
108
|
+
}
|
|
109
|
+
showIfNeed() {
|
|
110
|
+
const styles = this._elementRef.nativeElement.style;
|
|
111
|
+
if (styles.visibility === VISIBILITY_VISIBLE) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
styles.visibility = VISIBILITY_VISIBLE;
|
|
115
|
+
}
|
|
116
|
+
hide() {
|
|
117
|
+
const styles = this._elementRef.nativeElement.style;
|
|
118
|
+
if (styles.visibility === VISIBILITY_HIDDEN) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
styles.visibility = VISIBILITY_HIDDEN;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
NgVirtualListItemComponent.__nextId = 0;
|
|
125
|
+
NgVirtualListItemComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListItemComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
126
|
+
NgVirtualListItemComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListItemComponent, selector: "ng-virtual-list-item", host: { classAttribute: "ngvl__item" }, ngImport: i0, template: "<ng-container *ngIf=\"data\">\r\n <li #listItem part=\"item\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': data.config.snapped,\r\n 'snapped-out': data.config.snappedOut}\">\r\n <ng-container *ngIf=\"itemRenderer\">\r\n <ng-container [ngTemplateOutlet]=\"itemRenderer\"\r\n [ngTemplateOutletContext]=\"{data: data.data || {}, config: data.config}\"></ng-container>\r\n </ng-container>\r\n </li>\r\n</ng-container>", 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: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
127
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListItemComponent, decorators: [{
|
|
128
|
+
type: Component,
|
|
129
|
+
args: [{ selector: 'ng-virtual-list-item', host: {
|
|
130
|
+
'class': 'ngvl__item',
|
|
131
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *ngIf=\"data\">\r\n <li #listItem part=\"item\" class=\"ngvl-item__container\" [ngClass]=\"{'snapped': data.config.snapped,\r\n 'snapped-out': data.config.snappedOut}\">\r\n <ng-container *ngIf=\"itemRenderer\">\r\n <ng-container [ngTemplateOutlet]=\"itemRenderer\"\r\n [ngTemplateOutletContext]=\"{data: data.data || {}, config: data.config}\"></ng-container>\r\n </ng-container>\r\n </li>\r\n</ng-container>", 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"] }]
|
|
132
|
+
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }]; } });
|
|
133
|
+
|
|
134
|
+
const HORIZONTAL_ALIASES = [Directions.HORIZONTAL, 'horizontal'], VERTICAL_ALIASES = [Directions.VERTICAL, 'vertical'];
|
|
135
|
+
/**
|
|
136
|
+
* Determines the axis membership of a virtual list
|
|
137
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/isDirection.ts
|
|
138
|
+
* @author Evgenii Grebennikov
|
|
139
|
+
* @email djonnyx@gmail.com
|
|
140
|
+
*/
|
|
141
|
+
const isDirection = (src, expected) => {
|
|
142
|
+
if (HORIZONTAL_ALIASES.includes(expected)) {
|
|
143
|
+
return HORIZONTAL_ALIASES.includes(src);
|
|
144
|
+
}
|
|
145
|
+
return VERTICAL_ALIASES.includes(src);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Simple debounce function.
|
|
150
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/debounce.ts
|
|
151
|
+
* @author Evgenii Grebennikov
|
|
152
|
+
* @email djonnyx@gmail.com
|
|
153
|
+
*/
|
|
154
|
+
const debounce = (cb, debounceTime = 0) => {
|
|
155
|
+
let timeout;
|
|
156
|
+
const dispose = () => {
|
|
157
|
+
if (timeout !== undefined) {
|
|
158
|
+
clearTimeout(timeout);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const execute = (...args) => {
|
|
162
|
+
dispose();
|
|
163
|
+
timeout = setTimeout(() => {
|
|
164
|
+
cb(...args);
|
|
165
|
+
}, debounceTime);
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
/**
|
|
169
|
+
* Call handling method
|
|
170
|
+
*/
|
|
171
|
+
execute,
|
|
172
|
+
/**
|
|
173
|
+
* Method of destroying handlers
|
|
174
|
+
*/
|
|
175
|
+
dispose,
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Switch css classes
|
|
181
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/toggleClassName.ts
|
|
182
|
+
* @author Evgenii Grebennikov
|
|
183
|
+
* @email djonnyx@gmail.com
|
|
184
|
+
*/
|
|
185
|
+
const toggleClassName = (el, className, remove = false) => {
|
|
186
|
+
if (!el.classList.contains(className)) {
|
|
187
|
+
el.classList.add(className);
|
|
188
|
+
}
|
|
189
|
+
else if (remove) {
|
|
190
|
+
el.classList.remove(className);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Tracks display items by property
|
|
196
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/tracker.ts
|
|
197
|
+
* @author Evgenii Grebennikov
|
|
198
|
+
* @email djonnyx@gmail.com
|
|
199
|
+
*/
|
|
200
|
+
class Tracker {
|
|
201
|
+
constructor(trackingPropertyName) {
|
|
202
|
+
/**
|
|
203
|
+
* display objects dictionary of indexes by id
|
|
204
|
+
*/
|
|
205
|
+
this._displayObjectIndexMapById = {};
|
|
206
|
+
/**
|
|
207
|
+
* Dictionary displayItems propertyNameId by items propertyNameId
|
|
208
|
+
*/
|
|
209
|
+
this._trackMap = {};
|
|
210
|
+
this._trackingPropertyName = trackingPropertyName;
|
|
211
|
+
}
|
|
212
|
+
set displayObjectIndexMapById(v) {
|
|
213
|
+
if (this._displayObjectIndexMapById === v) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this._displayObjectIndexMapById = v;
|
|
217
|
+
}
|
|
218
|
+
get displayObjectIndexMapById() {
|
|
219
|
+
return this._displayObjectIndexMapById;
|
|
220
|
+
}
|
|
221
|
+
get trackMap() {
|
|
222
|
+
return this._trackMap;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* tracking by propName
|
|
226
|
+
*/
|
|
227
|
+
track(items, components, afterComponentSetup) {
|
|
228
|
+
if (!items) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const idPropName = this._trackingPropertyName, untrackedItems = [...components];
|
|
232
|
+
for (let i = 0, l = items.length; i < l; i++) {
|
|
233
|
+
const item = items[i], itemTrackingProperty = item[idPropName];
|
|
234
|
+
if (this._trackMap) {
|
|
235
|
+
const diId = this._trackMap[itemTrackingProperty];
|
|
236
|
+
if (this._trackMap.hasOwnProperty(itemTrackingProperty)) {
|
|
237
|
+
const lastIndex = this._displayObjectIndexMapById[diId], el = components[lastIndex];
|
|
238
|
+
this._checkComponentProperty(el?.instance);
|
|
239
|
+
const elId = el?.instance?.[itemTrackingProperty];
|
|
240
|
+
if (el && elId === diId) {
|
|
241
|
+
const indexByUntrackedItems = untrackedItems.findIndex(v => {
|
|
242
|
+
this._checkComponentProperty(v.instance);
|
|
243
|
+
return v.instance[itemTrackingProperty] === elId;
|
|
244
|
+
});
|
|
245
|
+
if (indexByUntrackedItems > -1) {
|
|
246
|
+
el.instance.item = item;
|
|
247
|
+
if (afterComponentSetup !== undefined) {
|
|
248
|
+
afterComponentSetup(el.instance, item);
|
|
249
|
+
}
|
|
250
|
+
untrackedItems.splice(indexByUntrackedItems, 1);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
delete this._trackMap[itemTrackingProperty];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (untrackedItems.length > 0) {
|
|
258
|
+
const el = untrackedItems.shift(), item = items[i];
|
|
259
|
+
if (el) {
|
|
260
|
+
el.instance.item = item;
|
|
261
|
+
if (this._trackMap) {
|
|
262
|
+
this._checkComponentProperty(el.instance);
|
|
263
|
+
this._trackMap[itemTrackingProperty] = el.instance[itemTrackingProperty];
|
|
264
|
+
}
|
|
265
|
+
if (afterComponentSetup !== undefined) {
|
|
266
|
+
afterComponentSetup(el.instance, item);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (untrackedItems.length) {
|
|
272
|
+
throw Error('Tracking by id caused an error.');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
untrackComponentByIdProperty(component) {
|
|
276
|
+
if (!component) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const propertyIdName = this._trackingPropertyName;
|
|
280
|
+
this._checkComponentProperty(component);
|
|
281
|
+
if (this._trackMap && component[propertyIdName] !== undefined) {
|
|
282
|
+
delete this._trackMap[propertyIdName];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
_checkComponentProperty(component) {
|
|
286
|
+
if (!component) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const propertyIdName = this._trackingPropertyName;
|
|
290
|
+
try {
|
|
291
|
+
component[propertyIdName];
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
throw Error(`Property ${propertyIdName} does not exist.`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
dispose() {
|
|
298
|
+
this._trackMap = null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Simple event emitter
|
|
304
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/eventEmitter.ts
|
|
305
|
+
* @author Evgenii Grebennikov
|
|
306
|
+
* @email djonnyx@gmail.com
|
|
307
|
+
*/
|
|
308
|
+
class EventEmitter {
|
|
309
|
+
constructor() {
|
|
310
|
+
this._listeners = {};
|
|
311
|
+
this._disposed = false;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Emits the event
|
|
315
|
+
*/
|
|
316
|
+
dispatch(event, ...args) {
|
|
317
|
+
const ctx = this;
|
|
318
|
+
const listeners = this._listeners[event];
|
|
319
|
+
if (Array.isArray(listeners)) {
|
|
320
|
+
for (let i = 0, l = listeners.length; i < l; i++) {
|
|
321
|
+
const listener = listeners[i];
|
|
322
|
+
if (listener) {
|
|
323
|
+
listener.apply(ctx, args);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Emits the event async
|
|
330
|
+
*/
|
|
331
|
+
dispatchAsync(event, ...args) {
|
|
332
|
+
queueMicrotask(() => {
|
|
333
|
+
if (this._disposed) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
this.dispatch(event, ...args);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Returns true if the event listener is already subscribed.
|
|
341
|
+
*/
|
|
342
|
+
hasEventListener(eventName, handler) {
|
|
343
|
+
const event = eventName;
|
|
344
|
+
if (this._listeners.hasOwnProperty(event)) {
|
|
345
|
+
const listeners = this._listeners[event];
|
|
346
|
+
const index = listeners.findIndex(v => v === handler);
|
|
347
|
+
if (index > -1) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Add event listener
|
|
355
|
+
*/
|
|
356
|
+
addEventListener(eventName, handler) {
|
|
357
|
+
const event = eventName;
|
|
358
|
+
if (!this._listeners.hasOwnProperty(event)) {
|
|
359
|
+
this._listeners[event] = [];
|
|
360
|
+
}
|
|
361
|
+
this._listeners[event].push(handler);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Remove event listener
|
|
365
|
+
*/
|
|
366
|
+
removeEventListener(eventName, handler) {
|
|
367
|
+
const event = eventName;
|
|
368
|
+
if (!this._listeners.hasOwnProperty(event)) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const listeners = this._listeners[event], index = listeners.findIndex(v => v === handler);
|
|
372
|
+
if (index > -1) {
|
|
373
|
+
listeners.splice(index, 1);
|
|
374
|
+
if (listeners.length === 0) {
|
|
375
|
+
delete this._listeners[event];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Remove all listeners
|
|
381
|
+
*/
|
|
382
|
+
removeAllListeners() {
|
|
383
|
+
const events = Object.keys(this._listeners);
|
|
384
|
+
while (events.length > 0) {
|
|
385
|
+
const event = events.pop();
|
|
386
|
+
if (event) {
|
|
387
|
+
const listeners = this._listeners[event];
|
|
388
|
+
if (Array.isArray(listeners)) {
|
|
389
|
+
while (listeners.length > 0) {
|
|
390
|
+
const listener = listeners.pop();
|
|
391
|
+
if (listener) {
|
|
392
|
+
this.removeEventListener(event, listener);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Method of destroying handlers
|
|
401
|
+
*/
|
|
402
|
+
dispose() {
|
|
403
|
+
this._disposed = true;
|
|
404
|
+
this.removeAllListeners();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Cache map.
|
|
410
|
+
* Emits a change event on each mutation.
|
|
411
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/cacheMap.ts
|
|
412
|
+
* @author Evgenii Grebennikov
|
|
413
|
+
* @email djonnyx@gmail.com
|
|
414
|
+
*/
|
|
415
|
+
class CacheMap extends EventEmitter {
|
|
416
|
+
constructor() {
|
|
417
|
+
super();
|
|
418
|
+
this._map = new Map();
|
|
419
|
+
this._version = 0;
|
|
420
|
+
this._previouseFullHeigh = 0;
|
|
421
|
+
this._delta = 0;
|
|
422
|
+
}
|
|
423
|
+
get delta() {
|
|
424
|
+
return this._delta;
|
|
425
|
+
}
|
|
426
|
+
get version() {
|
|
427
|
+
return this._version;
|
|
428
|
+
}
|
|
429
|
+
bumpVersion() {
|
|
430
|
+
this._version = this._version === Number.MAX_SAFE_INTEGER ? 0 : this._version + 1;
|
|
431
|
+
}
|
|
432
|
+
fireChange() {
|
|
433
|
+
this.dispatch('change', this.version);
|
|
434
|
+
}
|
|
435
|
+
set(id, bounds) {
|
|
436
|
+
if (this._map.has(id) && JSON.stringify(this._map.get(id)) === JSON.stringify(bounds)) {
|
|
437
|
+
return this._map;
|
|
438
|
+
}
|
|
439
|
+
const v = this._map.set(id, bounds);
|
|
440
|
+
this.bumpVersion();
|
|
441
|
+
this.fireChange();
|
|
442
|
+
return v;
|
|
443
|
+
}
|
|
444
|
+
has(id) {
|
|
445
|
+
return this._map.has(id);
|
|
446
|
+
}
|
|
447
|
+
get(id) {
|
|
448
|
+
return this._map.get(id);
|
|
449
|
+
}
|
|
450
|
+
forEach(callbackfn, thisArg) {
|
|
451
|
+
return this._map.forEach(callbackfn, thisArg);
|
|
452
|
+
}
|
|
453
|
+
dispose() {
|
|
454
|
+
super.dispose();
|
|
455
|
+
this._map.clear();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const TRACK_BOX_CHANGE_EVENT_NAME = 'change';
|
|
460
|
+
/**
|
|
461
|
+
* An object that performs tracking, calculations and caching.
|
|
462
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/trackBox.ts
|
|
463
|
+
* @author Evgenii Grebennikov
|
|
464
|
+
* @email djonnyx@gmail.com
|
|
465
|
+
*/
|
|
466
|
+
class TrackBox extends CacheMap {
|
|
467
|
+
constructor(trackingPropertyName) {
|
|
468
|
+
super();
|
|
469
|
+
this._fireChanges = (version) => {
|
|
470
|
+
this.dispatch(TRACK_BOX_CHANGE_EVENT_NAME, version);
|
|
471
|
+
};
|
|
472
|
+
this._debounceChanges = debounce(this._fireChanges, 0);
|
|
473
|
+
this._tracker = new Tracker(trackingPropertyName);
|
|
474
|
+
}
|
|
475
|
+
set items(v) {
|
|
476
|
+
if (this._items === v) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
this._items = v;
|
|
480
|
+
}
|
|
481
|
+
set displayComponents(v) {
|
|
482
|
+
if (this._displayComponents === v) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this._displayComponents = v;
|
|
486
|
+
}
|
|
487
|
+
set(id, bounds) {
|
|
488
|
+
if (this._map.has(id) && JSON.stringify(this._map.get(id)) === JSON.stringify(bounds)) {
|
|
489
|
+
return this._map;
|
|
490
|
+
}
|
|
491
|
+
const v = this._map.set(id, bounds);
|
|
492
|
+
this.bumpVersion();
|
|
493
|
+
this.fireChange();
|
|
494
|
+
return v;
|
|
495
|
+
}
|
|
496
|
+
fireChange() {
|
|
497
|
+
this._debounceChanges.execute(this._version);
|
|
498
|
+
}
|
|
499
|
+
getItemPosition(id, stickyMap, options) {
|
|
500
|
+
const opt = { fromItemId: id, stickyMap, scrollDirection: undefined, ...options };
|
|
501
|
+
const { scrollSize } = this.recalculateMetrics(opt);
|
|
502
|
+
return scrollSize;
|
|
503
|
+
}
|
|
504
|
+
updateCollection(items, stickyMap, options) {
|
|
505
|
+
const opt = { stickyMap, scrollDirection: undefined, ...options };
|
|
506
|
+
this.cacheElements();
|
|
507
|
+
const metrics = this.recalculateMetrics({
|
|
508
|
+
...opt,
|
|
509
|
+
collection: items,
|
|
510
|
+
});
|
|
511
|
+
const displayItems = this.generateDisplayCollection(items, stickyMap, metrics);
|
|
512
|
+
return { displayItems, totalSize: metrics.totalSize, delta: metrics.delta };
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Calculates list metrics
|
|
516
|
+
*/
|
|
517
|
+
recalculateMetrics(options) {
|
|
518
|
+
const { scrollDirection = 0, fromItemId, bounds, collection, dynamicSize, isVertical, itemSize, itemsOffset, scrollSize, snap, stickyMap } = options;
|
|
519
|
+
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, totalSize = dynamicSize ? this.getBoundsFromCache(collection, typicalItemSize, isVertical) : totalLength * typicalItemSize, snippedPos = Math.floor(scrollSize), leftItemsWeights = [], isFromId = fromItemId !== undefined && (typeof fromItemId === 'number' && fromItemId > -1)
|
|
520
|
+
|| (typeof fromItemId === 'string' && fromItemId > '-1');
|
|
521
|
+
let itemsFromStartToScrollEnd = -1, itemsFromDisplayEndToOffsetEnd = 0, itemsFromStartToDisplayEnd = -1, leftItemLength = 0, rightItemLength = 0, leftItemsWeight = 0, rightItemsWeight = 0, leftHiddenItemsWeight = 0, totalItemsToDisplayEndWeight = 0, itemById = undefined, itemByIdPos = 0, lastDisplayItemId = undefined, actualScrollSize = itemByIdPos, fullHeight = 0, startIndex;
|
|
522
|
+
if (dynamicSize) {
|
|
523
|
+
let y = 0, stickyCollectionItem = undefined, stickyComponentSize = 0;
|
|
524
|
+
for (let i = 0, l = collection.length; i < l; i++) {
|
|
525
|
+
const ii = i + 1, collectionItem = collection[i], map = this._map;
|
|
526
|
+
let componentSize = 0;
|
|
527
|
+
if (map.has(collectionItem.id)) {
|
|
528
|
+
const bounds = map.get(collectionItem.id);
|
|
529
|
+
componentSize = bounds ? bounds[sizeProperty] : typicalItemSize;
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
componentSize = typicalItemSize;
|
|
533
|
+
}
|
|
534
|
+
if (isFromId) {
|
|
535
|
+
if (itemById === undefined) {
|
|
536
|
+
leftItemsWeights.push(componentSize);
|
|
537
|
+
leftHiddenItemsWeight += componentSize;
|
|
538
|
+
itemsFromStartToScrollEnd = ii;
|
|
539
|
+
if (stickyMap && stickyMap[collectionItem.id] > 0) {
|
|
540
|
+
stickyComponentSize = componentSize;
|
|
541
|
+
stickyCollectionItem = collectionItem;
|
|
542
|
+
}
|
|
543
|
+
if (collectionItem.id === fromItemId) {
|
|
544
|
+
itemById = collectionItem;
|
|
545
|
+
itemByIdPos = y;
|
|
546
|
+
if (stickyCollectionItem && stickyMap && stickyMap[stickyCollectionItem.id] > 0) {
|
|
547
|
+
itemByIdPos = itemByIdPos - stickyComponentSize;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
else if (y < scrollSize - componentSize) {
|
|
553
|
+
leftItemsWeights.push(componentSize);
|
|
554
|
+
leftHiddenItemsWeight += componentSize;
|
|
555
|
+
itemsFromStartToScrollEnd = ii;
|
|
556
|
+
}
|
|
557
|
+
if (isFromId) {
|
|
558
|
+
if (!lastDisplayItemId) {
|
|
559
|
+
if (itemById === undefined || y < itemByIdPos + size + componentSize) {
|
|
560
|
+
itemsFromStartToDisplayEnd = ii;
|
|
561
|
+
totalItemsToDisplayEndWeight += componentSize;
|
|
562
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + itemsOffset;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else if (y < scrollSize + size + componentSize) {
|
|
567
|
+
itemsFromStartToDisplayEnd = ii;
|
|
568
|
+
totalItemsToDisplayEndWeight += componentSize;
|
|
569
|
+
itemsFromDisplayEndToOffsetEnd = itemsFromStartToDisplayEnd + itemsOffset;
|
|
570
|
+
}
|
|
571
|
+
else if (i < itemsFromDisplayEndToOffsetEnd) {
|
|
572
|
+
rightItemsWeight += componentSize;
|
|
573
|
+
}
|
|
574
|
+
y += componentSize;
|
|
575
|
+
}
|
|
576
|
+
fullHeight = y;
|
|
577
|
+
if (itemsFromStartToScrollEnd === -1) {
|
|
578
|
+
itemsFromStartToScrollEnd = 0;
|
|
579
|
+
}
|
|
580
|
+
if (itemsFromStartToDisplayEnd === -1) {
|
|
581
|
+
itemsFromStartToDisplayEnd = 0;
|
|
582
|
+
}
|
|
583
|
+
actualScrollSize = isFromId ? itemByIdPos : scrollSize;
|
|
584
|
+
leftItemsWeights.splice(0, leftItemsWeights.length - itemsOffset);
|
|
585
|
+
leftItemsWeights.forEach(v => {
|
|
586
|
+
leftItemsWeight += v;
|
|
587
|
+
});
|
|
588
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
589
|
+
rightItemLength = itemsFromStartToDisplayEnd + itemsOffset > totalLength
|
|
590
|
+
? totalLength - itemsFromStartToDisplayEnd : itemsOffset;
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
itemsFromStartToScrollEnd = Math.floor(scrollSize / typicalItemSize);
|
|
594
|
+
itemsFromStartToDisplayEnd = Math.ceil((scrollSize + size) / typicalItemSize);
|
|
595
|
+
leftItemLength = Math.min(itemsFromStartToScrollEnd, itemsOffset);
|
|
596
|
+
rightItemLength = itemsFromStartToDisplayEnd + itemsOffset > totalLength
|
|
597
|
+
? totalLength - itemsFromStartToDisplayEnd : itemsOffset;
|
|
598
|
+
leftItemsWeight = leftItemLength * typicalItemSize;
|
|
599
|
+
rightItemsWeight = rightItemLength * typicalItemSize,
|
|
600
|
+
leftHiddenItemsWeight = itemsFromStartToScrollEnd * typicalItemSize,
|
|
601
|
+
totalItemsToDisplayEndWeight = itemsFromStartToDisplayEnd * typicalItemSize;
|
|
602
|
+
actualScrollSize = scrollSize,
|
|
603
|
+
fullHeight = totalLength * typicalItemSize;
|
|
604
|
+
}
|
|
605
|
+
startIndex = Math.min(itemsFromStartToScrollEnd - leftItemLength, totalLength > 0 ? totalLength - 1 : 0);
|
|
606
|
+
const itemsOnDisplay = totalItemsToDisplayEndWeight - leftHiddenItemsWeight, itemsOnDisplayLength = itemsFromStartToDisplayEnd - itemsFromStartToScrollEnd, startPosition = leftHiddenItemsWeight - leftItemsWeight, renderItems = itemsOnDisplayLength + leftItemLength + rightItemLength, delta = fullHeight - this._previouseFullHeigh;
|
|
607
|
+
if (scrollDirection === -1) {
|
|
608
|
+
this._delta += delta;
|
|
609
|
+
}
|
|
610
|
+
const metrics = {
|
|
611
|
+
delta: this._delta,
|
|
612
|
+
normalizedItemWidth: w,
|
|
613
|
+
normalizedItemHeight: h,
|
|
614
|
+
width,
|
|
615
|
+
height,
|
|
616
|
+
dynamicSize,
|
|
617
|
+
itemSize,
|
|
618
|
+
itemsFromStartToScrollEnd,
|
|
619
|
+
itemsFromStartToDisplayEnd,
|
|
620
|
+
itemsOnDisplay,
|
|
621
|
+
itemsOnDisplayLength,
|
|
622
|
+
isVertical,
|
|
623
|
+
leftHiddenItemsWeight,
|
|
624
|
+
leftItemLength,
|
|
625
|
+
leftItemsWeight,
|
|
626
|
+
renderItems,
|
|
627
|
+
rightItemLength,
|
|
628
|
+
rightItemsWeight,
|
|
629
|
+
scrollSize: actualScrollSize,
|
|
630
|
+
sizeProperty,
|
|
631
|
+
snap,
|
|
632
|
+
snippedPos,
|
|
633
|
+
startIndex,
|
|
634
|
+
startPosition,
|
|
635
|
+
totalItemsToDisplayEndWeight,
|
|
636
|
+
totalLength,
|
|
637
|
+
totalSize,
|
|
638
|
+
typicalItemSize,
|
|
639
|
+
};
|
|
640
|
+
if (scrollDirection !== 0) {
|
|
641
|
+
this._previouseFullHeigh = fullHeight;
|
|
642
|
+
}
|
|
643
|
+
return metrics;
|
|
644
|
+
}
|
|
645
|
+
clearDelta() {
|
|
646
|
+
this._delta = 0;
|
|
647
|
+
}
|
|
648
|
+
generateDisplayCollection(items, stickyMap, metrics) {
|
|
649
|
+
const {
|
|
650
|
+
// delta,
|
|
651
|
+
normalizedItemWidth, normalizedItemHeight,
|
|
652
|
+
// width,
|
|
653
|
+
// height,
|
|
654
|
+
dynamicSize,
|
|
655
|
+
// itemSize,
|
|
656
|
+
itemsFromStartToScrollEnd,
|
|
657
|
+
// itemsFromStartToDisplayEnd,
|
|
658
|
+
// itemsOnDisplay,
|
|
659
|
+
// itemsOnDisplayLength,
|
|
660
|
+
isVertical,
|
|
661
|
+
// leftHiddenItemsWeight,
|
|
662
|
+
// leftItemLength,
|
|
663
|
+
// leftItemsWeight,
|
|
664
|
+
renderItems: renderItemsLength,
|
|
665
|
+
// rightItemLength,
|
|
666
|
+
// rightItemsWeight,
|
|
667
|
+
scrollSize, sizeProperty, snap, snippedPos, startPosition,
|
|
668
|
+
// totalItemsToDisplayEndWeight,
|
|
669
|
+
totalLength,
|
|
670
|
+
// totalSize,
|
|
671
|
+
startIndex, typicalItemSize, } = metrics, displayItems = [];
|
|
672
|
+
if (items.length) {
|
|
673
|
+
const actualSnippedPosition = snippedPos;
|
|
674
|
+
let pos = startPosition, renderItems = renderItemsLength, stickyItem, nextSticky, stickyItemIndex = -1, stickyItemSize = 0;
|
|
675
|
+
if (snap) {
|
|
676
|
+
for (let i = Math.min(itemsFromStartToScrollEnd > 0 ? itemsFromStartToScrollEnd : 0, totalLength - 1); i >= 0; i--) {
|
|
677
|
+
const id = items[i].id, sticky = stickyMap[id], size = dynamicSize ? this.get(id)?.[sizeProperty] || typicalItemSize : typicalItemSize;
|
|
678
|
+
stickyItemSize = size;
|
|
679
|
+
if (sticky > 0) {
|
|
680
|
+
const measures = {
|
|
681
|
+
x: isVertical ? 0 : actualSnippedPosition,
|
|
682
|
+
y: isVertical ? actualSnippedPosition : 0,
|
|
683
|
+
width: normalizedItemWidth,
|
|
684
|
+
height: normalizedItemHeight,
|
|
685
|
+
}, config = {
|
|
686
|
+
isVertical,
|
|
687
|
+
sticky,
|
|
688
|
+
snap,
|
|
689
|
+
snapped: true,
|
|
690
|
+
snappedOut: false,
|
|
691
|
+
dynamic: dynamicSize,
|
|
692
|
+
};
|
|
693
|
+
const itemData = items[i];
|
|
694
|
+
stickyItem = { id, measures, data: itemData, config };
|
|
695
|
+
stickyItemIndex = i;
|
|
696
|
+
displayItems.push(stickyItem);
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
let i = startIndex;
|
|
702
|
+
while (renderItems > 0) {
|
|
703
|
+
if (i >= totalLength) {
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
const id = items[i].id, size = dynamicSize ? this.get(id)?.[sizeProperty] || typicalItemSize : typicalItemSize;
|
|
707
|
+
if (id !== stickyItem?.id) {
|
|
708
|
+
const snapped = snap && stickyMap[id] > 0 && pos <= scrollSize, measures = {
|
|
709
|
+
x: isVertical ? 0 : pos,
|
|
710
|
+
y: isVertical ? pos : 0,
|
|
711
|
+
width: normalizedItemWidth,
|
|
712
|
+
height: normalizedItemHeight,
|
|
713
|
+
}, config = {
|
|
714
|
+
isVertical,
|
|
715
|
+
sticky: stickyMap[id],
|
|
716
|
+
snap,
|
|
717
|
+
snapped: false,
|
|
718
|
+
snappedOut: false,
|
|
719
|
+
dynamic: dynamicSize,
|
|
720
|
+
};
|
|
721
|
+
const itemData = items[i];
|
|
722
|
+
const item = { id, measures, data: itemData, config };
|
|
723
|
+
if (!nextSticky && stickyItemIndex < i && snap && stickyMap[id] > 0 && pos <= scrollSize + size) {
|
|
724
|
+
item.measures.x = isVertical ? 0 : snapped ? actualSnippedPosition : pos;
|
|
725
|
+
item.measures.y = isVertical ? snapped ? actualSnippedPosition : pos : 0;
|
|
726
|
+
nextSticky = item;
|
|
727
|
+
nextSticky.config.snapped = snapped;
|
|
728
|
+
}
|
|
729
|
+
displayItems.push(item);
|
|
730
|
+
}
|
|
731
|
+
renderItems -= 1;
|
|
732
|
+
pos += size;
|
|
733
|
+
i++;
|
|
734
|
+
}
|
|
735
|
+
const axis = isVertical ? Y_PROP_NAME : X_PROP_NAME;
|
|
736
|
+
if (nextSticky && stickyItem && nextSticky.measures[axis] <= scrollSize + stickyItemSize) {
|
|
737
|
+
if (nextSticky.measures[axis] > scrollSize) {
|
|
738
|
+
stickyItem.measures[axis] = nextSticky.measures[axis] - stickyItemSize;
|
|
739
|
+
stickyItem.config.snapped = nextSticky.config.snapped = false;
|
|
740
|
+
stickyItem.config.snappedOut = true;
|
|
741
|
+
stickyItem.config.sticky = 1;
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
nextSticky.config.snapped = true;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return displayItems;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* tracking by propName
|
|
752
|
+
*/
|
|
753
|
+
track() {
|
|
754
|
+
if (!this._items || !this._displayComponents) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
this._tracker.track(this._items, this._displayComponents);
|
|
758
|
+
}
|
|
759
|
+
setDisplayObjectIndexMapById(v) {
|
|
760
|
+
this._tracker.displayObjectIndexMapById = v;
|
|
761
|
+
}
|
|
762
|
+
untrackComponentByIdProperty(component) {
|
|
763
|
+
this._tracker.untrackComponentByIdProperty(component);
|
|
764
|
+
}
|
|
765
|
+
cacheElements() {
|
|
766
|
+
if (!this._displayComponents) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
for (let i = 0, l = this._displayComponents.length; i < l; i++) {
|
|
770
|
+
const component = this._displayComponents[i], itemId = component.instance.itemId;
|
|
771
|
+
if (itemId === undefined) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const bounds = component.instance.getBounds();
|
|
775
|
+
this.set(itemId, bounds);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Returns calculated bounds from cache
|
|
780
|
+
*/
|
|
781
|
+
getBoundsFromCache(items, typicalItemSize, isVertical) {
|
|
782
|
+
const sizeProperty = isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME, map = this._map;
|
|
783
|
+
let size = 0;
|
|
784
|
+
for (let i = 0, l = items.length; i < l; i++) {
|
|
785
|
+
const item = items[i];
|
|
786
|
+
if (map.has(item.id)) {
|
|
787
|
+
const bounds = map.get(item.id);
|
|
788
|
+
size += bounds ? bounds[sizeProperty] : typicalItemSize;
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
size += typicalItemSize;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return size;
|
|
795
|
+
}
|
|
796
|
+
dispose() {
|
|
797
|
+
super.dispose();
|
|
798
|
+
if (this._debounceChanges) {
|
|
799
|
+
this._debounceChanges.dispose();
|
|
800
|
+
}
|
|
801
|
+
if (this._tracker) {
|
|
802
|
+
this._tracker.dispose();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Base disposable component
|
|
809
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/utils/disposableComponent.ts
|
|
810
|
+
* @author Evgenii Grebennikov
|
|
811
|
+
* @email djonnyx@gmail.com
|
|
812
|
+
*/
|
|
813
|
+
class DisposableComponent {
|
|
814
|
+
constructor() {
|
|
815
|
+
this._$unsubscribe = new Subject();
|
|
816
|
+
}
|
|
817
|
+
ngOnDestroy() {
|
|
818
|
+
if (this._$unsubscribe) {
|
|
819
|
+
this._$unsubscribe.next();
|
|
820
|
+
this._$unsubscribe.complete();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
DisposableComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DisposableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
825
|
+
DisposableComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: DisposableComponent, selector: "ng-component", ngImport: i0, template: ``, isInline: true });
|
|
826
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: DisposableComponent, decorators: [{
|
|
827
|
+
type: Component,
|
|
828
|
+
args: [{
|
|
829
|
+
template: ``,
|
|
830
|
+
}]
|
|
831
|
+
}] });
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Virtual list component.
|
|
835
|
+
* Maximum performance for extremely large lists.
|
|
836
|
+
* It is based on algorithms for virtualization of screen objects.
|
|
837
|
+
* @link https://github.com/DjonnyX/ng-virtual-list/blob/main/projects/ng-virtual-list/src/lib/ng-virtual-list.component.ts
|
|
838
|
+
* @author Evgenii Grebennikov
|
|
839
|
+
* @email djonnyx@gmail.com
|
|
840
|
+
*/
|
|
841
|
+
class NgVirtualListComponent extends DisposableComponent {
|
|
842
|
+
constructor(_cdr, _elementRef) {
|
|
843
|
+
super();
|
|
844
|
+
this._cdr = _cdr;
|
|
845
|
+
this._elementRef = _elementRef;
|
|
846
|
+
this._id = NgVirtualListComponent.__nextId;
|
|
847
|
+
/**
|
|
848
|
+
* Fires when the list has been scrolled.
|
|
849
|
+
*/
|
|
850
|
+
this.onScroll = new EventEmitter$1();
|
|
851
|
+
/**
|
|
852
|
+
* Fires when the list has completed scrolling.
|
|
853
|
+
*/
|
|
854
|
+
this.onScrollEnd = new EventEmitter$1();
|
|
855
|
+
this._$items = new BehaviorSubject(undefined);
|
|
856
|
+
this.$items = this._$items.asObservable();
|
|
857
|
+
this._$snap = new BehaviorSubject(DEFAULT_SNAP);
|
|
858
|
+
this.$snap = this._$snap.asObservable();
|
|
859
|
+
this._$snapToItem = new BehaviorSubject(DEFAULT_SNAP_TO_ITEM);
|
|
860
|
+
this.$snapToItem = this._$snapToItem.asObservable();
|
|
861
|
+
this._$itemRenderer = new BehaviorSubject(undefined);
|
|
862
|
+
this.$itemRenderer = this._$itemRenderer.asObservable();
|
|
863
|
+
this._$stickyMap = new BehaviorSubject({});
|
|
864
|
+
this.$stickyMap = this._$stickyMap.asObservable();
|
|
865
|
+
this._itemSizeOptions = (v) => {
|
|
866
|
+
if (v === undefined) {
|
|
867
|
+
return DEFAULT_ITEM_SIZE;
|
|
868
|
+
}
|
|
869
|
+
const val = Number(v);
|
|
870
|
+
return Number.isNaN(val) || val <= 0 ? DEFAULT_ITEM_SIZE : val;
|
|
871
|
+
};
|
|
872
|
+
this._$itemSize = new BehaviorSubject(DEFAULT_ITEM_SIZE);
|
|
873
|
+
this.$itemSize = this._$itemSize.asObservable();
|
|
874
|
+
this._$dynamicSize = new BehaviorSubject(DEFAULT_DYNAMIC_SIZE);
|
|
875
|
+
this.$dynamicSize = this._$dynamicSize.asObservable();
|
|
876
|
+
this._$direction = new BehaviorSubject(DEFAULT_DIRECTION);
|
|
877
|
+
this.$direction = this._$direction.asObservable();
|
|
878
|
+
this._$itemsOffset = new BehaviorSubject(DEFAULT_ITEMS_OFFSET);
|
|
879
|
+
this.$itemsOffset = this._$itemsOffset.asObservable();
|
|
880
|
+
this._isVertical = this.getIsVertical();
|
|
881
|
+
this._displayComponents = [];
|
|
882
|
+
this._$bounds = new BehaviorSubject(null);
|
|
883
|
+
this._$scrollSize = new BehaviorSubject(0);
|
|
884
|
+
this._resizeObserver = null;
|
|
885
|
+
/**
|
|
886
|
+
* only dynamic
|
|
887
|
+
*/
|
|
888
|
+
this._$scrolledItemId = new BehaviorSubject(undefined);
|
|
889
|
+
this._onResizeHandler = () => {
|
|
890
|
+
this._$bounds.next(this._container?.nativeElement?.getBoundingClientRect() ?? null);
|
|
891
|
+
};
|
|
892
|
+
this._scrollDirection = 0;
|
|
893
|
+
this._onScrollHandler = (e) => {
|
|
894
|
+
this._$scrolledItemId.next(undefined);
|
|
895
|
+
const container = this._container?.nativeElement;
|
|
896
|
+
if (container) {
|
|
897
|
+
const dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.scrollTop : container.scrollLeft), previouseScrollSize = this._$scrollSize.getValue();
|
|
898
|
+
let actualScrollSize = scrollSize;
|
|
899
|
+
this._scrollDirection = previouseScrollSize > scrollSize ? -1 : 1;
|
|
900
|
+
if (dynamicSize && delta !== 0) {
|
|
901
|
+
actualScrollSize = scrollSize + delta;
|
|
902
|
+
const params = {
|
|
903
|
+
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
904
|
+
behavior: BEHAVIOR_INSTANT
|
|
905
|
+
};
|
|
906
|
+
const container = this._container;
|
|
907
|
+
if (container) {
|
|
908
|
+
this.scrollImmediately(container, params);
|
|
909
|
+
this._trackBox.clearDelta();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
this._$scrollSize.next(actualScrollSize);
|
|
913
|
+
this.onScroll.emit(actualScrollSize);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
this._scrollImmediatelyHandler = undefined;
|
|
917
|
+
this._onScrollEndHandler = (e, fireEvent = true) => {
|
|
918
|
+
const container = this._container;
|
|
919
|
+
if (container) {
|
|
920
|
+
const itemSize = this.itemSize, snapToItem = this.snapToItem, dynamicSize = this.dynamicSize, delta = this._trackBox.delta, scrollSize = (this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft);
|
|
921
|
+
let actualScrollSize = scrollSize;
|
|
922
|
+
if (dynamicSize && delta !== 0) {
|
|
923
|
+
actualScrollSize = scrollSize + delta;
|
|
924
|
+
if (scrollSize !== actualScrollSize) {
|
|
925
|
+
const params = {
|
|
926
|
+
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
927
|
+
behavior: BEHAVIOR_INSTANT
|
|
928
|
+
};
|
|
929
|
+
this._trackBox.clearDelta();
|
|
930
|
+
this._$scrollSize.next(actualScrollSize);
|
|
931
|
+
container.nativeElement.scroll(params);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
const scrollItems = Math.round(scrollSize / itemSize);
|
|
937
|
+
actualScrollSize = snapToItem ? scrollItems * itemSize : scrollSize;
|
|
938
|
+
if (scrollSize !== actualScrollSize) {
|
|
939
|
+
const params = {
|
|
940
|
+
[this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: actualScrollSize,
|
|
941
|
+
behavior: BEHAVIOR_INSTANT
|
|
942
|
+
};
|
|
943
|
+
container.nativeElement.scroll(params);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
this._$scrollSize.next(actualScrollSize);
|
|
947
|
+
if (fireEvent) {
|
|
948
|
+
this.onScrollEnd.emit(actualScrollSize);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
this._$initialized = new BehaviorSubject(false);
|
|
953
|
+
/**
|
|
954
|
+
* Dictionary of element sizes by their id
|
|
955
|
+
*/
|
|
956
|
+
this._trackBox = new TrackBox(TRACK_BY_PROPERTY_NAME);
|
|
957
|
+
this._onTrackBoxChangeHandler = (v) => {
|
|
958
|
+
this._$cacheVersion.next(v);
|
|
959
|
+
};
|
|
960
|
+
this._$cacheVersion = new BehaviorSubject(-1);
|
|
961
|
+
NgVirtualListComponent.__nextId = NgVirtualListComponent.__nextId + 1 === Number.MAX_SAFE_INTEGER
|
|
962
|
+
? 0 : NgVirtualListComponent.__nextId + 1;
|
|
963
|
+
this._id = NgVirtualListComponent.__nextId;
|
|
964
|
+
this._$initialized = new BehaviorSubject(false);
|
|
965
|
+
this.$initialized = this._$initialized.asObservable();
|
|
966
|
+
this._trackBox.displayComponents = this._displayComponents;
|
|
967
|
+
const $bounds = this._$bounds.asObservable().pipe(filter(b => !!b)), $items = this.$items.pipe(map(i => !i ? [] : i)), $scrollSize = this._$scrollSize.asObservable(), $itemSize = this.$itemSize.pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $itemsOffset = this.$itemsOffset.pipe(map(v => v < 0 ? DEFAULT_ITEMS_OFFSET : v)), $stickyMap = this.$stickyMap.pipe(map(v => !v ? {} : v)), $snap = this.$snap, $isVertical = this.$direction.pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = this.$dynamicSize, $cacheVersion = this.$cacheVersion, $scrolledItemId = this._$scrolledItemId.asObservable();
|
|
968
|
+
$isVertical.pipe(takeUntil(this._$unsubscribe), tap(v => {
|
|
969
|
+
this._isVertical = v;
|
|
970
|
+
const el = this._elementRef.nativeElement;
|
|
971
|
+
toggleClassName(el, v ? CLASS_LIST_VERTICAL : CLASS_LIST_HORIZONTAL, true);
|
|
972
|
+
})).subscribe();
|
|
973
|
+
$dynamicSize.pipe(takeUntil(this._$unsubscribe), tap(dynamicSize => {
|
|
974
|
+
this.listenCacheChangesIfNeed(dynamicSize);
|
|
975
|
+
})).subscribe();
|
|
976
|
+
combineLatest([this.$initialized, $scrolledItemId, $bounds, $items, $stickyMap, $scrollSize, $itemSize,
|
|
977
|
+
$itemsOffset, $snap, $isVertical, $dynamicSize, $cacheVersion,
|
|
978
|
+
]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), debounceTime(0), filter(([initialized]) => !!initialized), switchMap(([, scrolledItemId, bounds, items, stickyMap, scrollSize, itemSize, itemsOffset, snap, isVertical, dynamicSize, cacheVersion,]) => {
|
|
979
|
+
const { width, height } = bounds;
|
|
980
|
+
let actualScrollSize = scrollSize;
|
|
981
|
+
const opts = {
|
|
982
|
+
bounds: { width, height }, collection: items, dynamicSize, isVertical, itemSize,
|
|
983
|
+
itemsOffset, scrollSize: scrollSize, snap, fromItemId: scrolledItemId,
|
|
984
|
+
};
|
|
985
|
+
if (dynamicSize && scrolledItemId !== undefined) {
|
|
986
|
+
const scrollSize = this._trackBox.getItemPosition(scrolledItemId, stickyMap, { ...opts, scrollSize: actualScrollSize });
|
|
987
|
+
actualScrollSize = scrollSize;
|
|
988
|
+
this._$scrollSize.next(actualScrollSize);
|
|
989
|
+
}
|
|
990
|
+
const scrollDirection = this._scrollDirection, { displayItems, totalSize } = this._trackBox.updateCollection(items, stickyMap, {
|
|
991
|
+
...opts, scrollSize: actualScrollSize, scrollDirection
|
|
992
|
+
});
|
|
993
|
+
this.resetBoundsSize(isVertical, totalSize);
|
|
994
|
+
this.createDisplayComponentsIfNeed(displayItems);
|
|
995
|
+
this.tracking();
|
|
996
|
+
if (dynamicSize && scrolledItemId !== undefined) {
|
|
997
|
+
const container = this._container;
|
|
998
|
+
if (container) {
|
|
999
|
+
const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior: BEHAVIOR_AUTO };
|
|
1000
|
+
this.scrollImmediately(container, params);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return of(displayItems);
|
|
1004
|
+
})).subscribe();
|
|
1005
|
+
combineLatest([this.$initialized, this.$itemRenderer]).pipe(takeUntil(this._$unsubscribe), distinctUntilChanged(), filter(([initialized]) => !!initialized), tap(([, itemRenderer]) => {
|
|
1006
|
+
this.resetRenderers(itemRenderer);
|
|
1007
|
+
}));
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Readonly. Returns the unique identifier of the component.
|
|
1011
|
+
*/
|
|
1012
|
+
get id() { return this._id; }
|
|
1013
|
+
/**
|
|
1014
|
+
* Collection of list items.
|
|
1015
|
+
*/
|
|
1016
|
+
set items(v) {
|
|
1017
|
+
if (this._$items.getValue() === v) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
this._$items.next(v);
|
|
1021
|
+
this._cdr.markForCheck();
|
|
1022
|
+
}
|
|
1023
|
+
;
|
|
1024
|
+
get items() { return this._$items.getValue(); }
|
|
1025
|
+
/**
|
|
1026
|
+
* Determines whether elements will snap. Default value is "true".
|
|
1027
|
+
*/
|
|
1028
|
+
set snap(v) {
|
|
1029
|
+
if (this._$snap.getValue() === v) {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
this._$snap.next(v);
|
|
1033
|
+
this._cdr.markForCheck();
|
|
1034
|
+
}
|
|
1035
|
+
;
|
|
1036
|
+
get snap() { return this._$snap.getValue(); }
|
|
1037
|
+
/**
|
|
1038
|
+
* Determines whether scroll positions will be snapped to the element. Default value is "false".
|
|
1039
|
+
*/
|
|
1040
|
+
set snapToItem(v) {
|
|
1041
|
+
if (this._$snapToItem.getValue() === v) {
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
this._$snapToItem.next(v);
|
|
1045
|
+
this._cdr.markForCheck();
|
|
1046
|
+
}
|
|
1047
|
+
;
|
|
1048
|
+
get snapToItem() { return this._$snapToItem.getValue(); }
|
|
1049
|
+
/**
|
|
1050
|
+
* Rendering element template.
|
|
1051
|
+
*/
|
|
1052
|
+
set itemRenderer(v) {
|
|
1053
|
+
if (this._$itemRenderer.getValue() === v) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
this._$itemRenderer.next(v);
|
|
1057
|
+
this._cdr.markForCheck();
|
|
1058
|
+
}
|
|
1059
|
+
;
|
|
1060
|
+
get itemRenderer() { return this._$itemRenderer.getValue(); }
|
|
1061
|
+
/**
|
|
1062
|
+
* Dictionary zIndex by id of the list element. If the value is not set or equal to 0,
|
|
1063
|
+
* then a simple element is displayed, if the value is greater than 0, then the sticky position mode is enabled for the element.
|
|
1064
|
+
*/
|
|
1065
|
+
set stickyMap(v) {
|
|
1066
|
+
if (this._$stickyMap.getValue() === v) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
this._$stickyMap.next(v);
|
|
1070
|
+
this._cdr.markForCheck();
|
|
1071
|
+
}
|
|
1072
|
+
;
|
|
1073
|
+
get stickyMap() { return this._$stickyMap.getValue(); }
|
|
1074
|
+
/**
|
|
1075
|
+
* If direction = 'vertical', then the height of a typical element. If direction = 'horizontal', then the width of a typical element.
|
|
1076
|
+
* Ignored if the dynamicSize property is true.
|
|
1077
|
+
*/
|
|
1078
|
+
set itemSize(v) {
|
|
1079
|
+
if (this._$itemSize.getValue() === v) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
this._$itemSize.next(this._itemSizeOptions(v));
|
|
1083
|
+
this._cdr.markForCheck();
|
|
1084
|
+
}
|
|
1085
|
+
;
|
|
1086
|
+
get itemSize() { return this._$itemSize.getValue(); }
|
|
1087
|
+
/**
|
|
1088
|
+
* If true then the items in the list can have different sizes and the itemSize property is ignored.
|
|
1089
|
+
* If false then the items in the list have a fixed size specified by the itemSize property. The default value is false.
|
|
1090
|
+
*/
|
|
1091
|
+
set dynamicSize(v) {
|
|
1092
|
+
if (this._$dynamicSize.getValue() === v) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
this._$dynamicSize.next(v);
|
|
1096
|
+
this._cdr.markForCheck();
|
|
1097
|
+
}
|
|
1098
|
+
;
|
|
1099
|
+
get dynamicSize() { return this._$dynamicSize.getValue(); }
|
|
1100
|
+
/**
|
|
1101
|
+
* Determines the direction in which elements are placed. Default value is "vertical".
|
|
1102
|
+
*/
|
|
1103
|
+
set direction(v) {
|
|
1104
|
+
if (this._$direction.getValue() === v) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
this._$direction.next(v);
|
|
1108
|
+
this._cdr.markForCheck();
|
|
1109
|
+
}
|
|
1110
|
+
;
|
|
1111
|
+
get direction() { return this._$direction.getValue(); }
|
|
1112
|
+
/**
|
|
1113
|
+
* Number of elements outside the scope of visibility. Default value is 2.
|
|
1114
|
+
*/
|
|
1115
|
+
set itemsOffset(v) {
|
|
1116
|
+
if (this._$itemsOffset.getValue() === v) {
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
this._$itemsOffset.next(v);
|
|
1120
|
+
}
|
|
1121
|
+
;
|
|
1122
|
+
get itemsOffset() { return this._$itemsOffset.getValue(); }
|
|
1123
|
+
scrollImmediately(container, params) {
|
|
1124
|
+
this.clearScrollImmediately();
|
|
1125
|
+
container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1126
|
+
const handler = () => {
|
|
1127
|
+
if (container) {
|
|
1128
|
+
container.nativeElement.removeEventListener(SCROLL_END, handler);
|
|
1129
|
+
container.nativeElement.scroll(params);
|
|
1130
|
+
container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
container.nativeElement.addEventListener(SCROLL_END, handler);
|
|
1134
|
+
container.nativeElement.scroll(params);
|
|
1135
|
+
this._scrollImmediatelyHandler = handler;
|
|
1136
|
+
}
|
|
1137
|
+
clearScrollImmediately() {
|
|
1138
|
+
if (this._scrollImmediatelyHandler === undefined) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const container = this._container;
|
|
1142
|
+
if (container) {
|
|
1143
|
+
container.nativeElement.removeEventListener(SCROLL_END, this._scrollImmediatelyHandler);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
get $cacheVersion() { return this._$cacheVersion.asObservable(); }
|
|
1147
|
+
ngOnInit() {
|
|
1148
|
+
this._$initialized.next(true);
|
|
1149
|
+
}
|
|
1150
|
+
listenCacheChangesIfNeed(value) {
|
|
1151
|
+
if (value) {
|
|
1152
|
+
if (!this._trackBox.hasEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler)) {
|
|
1153
|
+
this._trackBox.addEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
else {
|
|
1157
|
+
if (this._trackBox.hasEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler)) {
|
|
1158
|
+
this._trackBox.removeEventListener(TRACK_BOX_CHANGE_EVENT_NAME, this._onTrackBoxChangeHandler);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
getIsVertical(d) {
|
|
1163
|
+
const dir = d || this.direction;
|
|
1164
|
+
return isDirection(dir, Directions.VERTICAL);
|
|
1165
|
+
}
|
|
1166
|
+
createDisplayComponentsIfNeed(displayItems) {
|
|
1167
|
+
if (!displayItems || !this._listContainerRef) {
|
|
1168
|
+
this._trackBox.setDisplayObjectIndexMapById({});
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
this._trackBox.items = displayItems;
|
|
1172
|
+
const _listContainerRef = this._listContainerRef;
|
|
1173
|
+
while (this._displayComponents.length < displayItems.length) {
|
|
1174
|
+
if (_listContainerRef) {
|
|
1175
|
+
const comp = _listContainerRef.createComponent(NgVirtualListItemComponent);
|
|
1176
|
+
this._displayComponents.push(comp);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
const maxLength = displayItems.length;
|
|
1180
|
+
while (this._displayComponents.length > maxLength) {
|
|
1181
|
+
const comp = this._displayComponents.pop();
|
|
1182
|
+
comp?.destroy();
|
|
1183
|
+
const id = comp?.instance.item?.id;
|
|
1184
|
+
if (id !== undefined) {
|
|
1185
|
+
this._trackBox.untrackComponentByIdProperty(comp?.instance);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
this.resetRenderers();
|
|
1189
|
+
}
|
|
1190
|
+
resetRenderers(itemRenderer) {
|
|
1191
|
+
const doMap = {};
|
|
1192
|
+
for (let i = 0, l = this._displayComponents.length; i < l; i++) {
|
|
1193
|
+
const item = this._displayComponents[i];
|
|
1194
|
+
item.instance.renderer = itemRenderer || this.itemRenderer;
|
|
1195
|
+
doMap[item.instance.id] = i;
|
|
1196
|
+
}
|
|
1197
|
+
this._trackBox.setDisplayObjectIndexMapById(doMap);
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Tracking by id
|
|
1201
|
+
*/
|
|
1202
|
+
tracking() {
|
|
1203
|
+
this._trackBox.track();
|
|
1204
|
+
}
|
|
1205
|
+
resetBoundsSize(isVertical, totalSize) {
|
|
1206
|
+
const l = this._list;
|
|
1207
|
+
if (l) {
|
|
1208
|
+
l.nativeElement.style[isVertical ? HEIGHT_PROP_NAME : WIDTH_PROP_NAME] = `${totalSize}${PX}`;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* The method scrolls the list to the element with the given id and returns the value of the scrolled area.
|
|
1213
|
+
* Behavior accepts the values "auto", "instant" and "smooth".
|
|
1214
|
+
*/
|
|
1215
|
+
scrollTo(id, behavior = BEHAVIOR_AUTO) {
|
|
1216
|
+
const items = this.items;
|
|
1217
|
+
if (!items || !items.length) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const dynamicSize = this.dynamicSize, container = this._container, itemSize = this.itemSize;
|
|
1221
|
+
if (container) {
|
|
1222
|
+
if (dynamicSize) {
|
|
1223
|
+
if (container) {
|
|
1224
|
+
container.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1225
|
+
container.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1226
|
+
}
|
|
1227
|
+
const { width, height } = this._$bounds.getValue() || { width: 0, height: 0 }, stickyMap = this.stickyMap, items = this.items, opts = {
|
|
1228
|
+
bounds: { width, height }, collection: items, dynamicSize, isVertical: this._isVertical, itemSize,
|
|
1229
|
+
itemsOffset: this.itemsOffset, scrollSize: this._isVertical ? container.nativeElement.scrollTop : container.nativeElement.scrollLeft,
|
|
1230
|
+
snap: this.snap, fromItemId: id,
|
|
1231
|
+
}, scrollSize = this._trackBox.getItemPosition(id, stickyMap, opts), params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
|
|
1232
|
+
this._$scrolledItemId.next(id);
|
|
1233
|
+
this._$scrollSize.next(scrollSize);
|
|
1234
|
+
if (container) {
|
|
1235
|
+
const handler = () => {
|
|
1236
|
+
if (container) {
|
|
1237
|
+
container.nativeElement.removeEventListener(SCROLL_END, handler);
|
|
1238
|
+
clearTimeout(this._scrollToTimeout);
|
|
1239
|
+
this._scrollToTimeout = setTimeout(() => {
|
|
1240
|
+
container.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1241
|
+
container.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1242
|
+
}, 100);
|
|
1243
|
+
this.listenCacheChangesIfNeed(dynamicSize);
|
|
1244
|
+
this.onScroll.emit(scrollSize);
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
container.nativeElement.addEventListener(SCROLL_END, handler);
|
|
1248
|
+
}
|
|
1249
|
+
container.nativeElement.scroll(params);
|
|
1250
|
+
}
|
|
1251
|
+
else {
|
|
1252
|
+
const index = items.findIndex(item => item.id === id), scrollSize = index * this.itemSize;
|
|
1253
|
+
const params = { [this._isVertical ? TOP_PROP_NAME : LEFT_PROP_NAME]: scrollSize, behavior };
|
|
1254
|
+
container.nativeElement.scroll(params);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
scrollToEnd(behavior = BEHAVIOR_INSTANT) {
|
|
1259
|
+
const items = this.items, latItem = items[items.length > 0 ? items.length - 1 : 0];
|
|
1260
|
+
this.scrollTo(latItem.id, behavior);
|
|
1261
|
+
}
|
|
1262
|
+
ngAfterViewInit() {
|
|
1263
|
+
const containerEl = this._container;
|
|
1264
|
+
if (containerEl) {
|
|
1265
|
+
containerEl.nativeElement.addEventListener(SCROLL, this._onScrollHandler);
|
|
1266
|
+
containerEl.nativeElement.addEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1267
|
+
this._resizeObserver = new ResizeObserver(this._onResizeHandler);
|
|
1268
|
+
this._resizeObserver.observe(containerEl.nativeElement);
|
|
1269
|
+
this._onResizeHandler();
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
ngOnDestroy() {
|
|
1273
|
+
super.ngOnDestroy();
|
|
1274
|
+
if (this._scrollToTimeout) {
|
|
1275
|
+
clearTimeout(this._scrollToTimeout);
|
|
1276
|
+
}
|
|
1277
|
+
if (this._trackBox) {
|
|
1278
|
+
this._trackBox.dispose();
|
|
1279
|
+
}
|
|
1280
|
+
const containerEl = this._container;
|
|
1281
|
+
if (containerEl) {
|
|
1282
|
+
containerEl.nativeElement.removeEventListener(SCROLL, this._onScrollHandler);
|
|
1283
|
+
containerEl.nativeElement.removeEventListener(SCROLL_END, this._onScrollEndHandler);
|
|
1284
|
+
if (this._resizeObserver) {
|
|
1285
|
+
this._resizeObserver.unobserve(containerEl.nativeElement);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (this._displayComponents) {
|
|
1289
|
+
while (this._displayComponents.length > 0) {
|
|
1290
|
+
const comp = this._displayComponents.pop();
|
|
1291
|
+
comp?.destroy();
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
NgVirtualListComponent.__nextId = 0;
|
|
1297
|
+
NgVirtualListComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1298
|
+
NgVirtualListComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NgVirtualListComponent, selector: "ng-virtual-list", inputs: { items: "items", snap: "snap", snapToItem: "snapToItem", itemRenderer: "itemRenderer", stickyMap: "stickyMap", itemSize: "itemSize", dynamicSize: "dynamicSize", direction: "direction", itemsOffset: "itemsOffset" }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd" }, viewQueries: [{ propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, read: (ElementRef) }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, read: (ElementRef) }], usesInheritance: true, 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"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
1299
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListComponent, decorators: [{
|
|
1300
|
+
type: Component,
|
|
1301
|
+
args: [{ selector: 'ng-virtual-list', 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"] }]
|
|
1302
|
+
}], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }]; }, propDecorators: { _listContainerRef: [{
|
|
1303
|
+
type: ViewChild,
|
|
1304
|
+
args: ['renderersContainer', { read: ViewContainerRef }]
|
|
1305
|
+
}], _container: [{
|
|
1306
|
+
type: ViewChild,
|
|
1307
|
+
args: ['container', { read: (ElementRef) }]
|
|
1308
|
+
}], _list: [{
|
|
1309
|
+
type: ViewChild,
|
|
1310
|
+
args: ['list', { read: (ElementRef) }]
|
|
1311
|
+
}], onScroll: [{
|
|
1312
|
+
type: Output
|
|
1313
|
+
}], onScrollEnd: [{
|
|
1314
|
+
type: Output
|
|
1315
|
+
}], items: [{
|
|
1316
|
+
type: Input
|
|
1317
|
+
}], snap: [{
|
|
1318
|
+
type: Input
|
|
1319
|
+
}], snapToItem: [{
|
|
1320
|
+
type: Input
|
|
1321
|
+
}], itemRenderer: [{
|
|
1322
|
+
type: Input
|
|
1323
|
+
}], stickyMap: [{
|
|
1324
|
+
type: Input
|
|
1325
|
+
}], itemSize: [{
|
|
1326
|
+
type: Input
|
|
1327
|
+
}], dynamicSize: [{
|
|
1328
|
+
type: Input
|
|
1329
|
+
}], direction: [{
|
|
1330
|
+
type: Input
|
|
1331
|
+
}], itemsOffset: [{
|
|
1332
|
+
type: Input
|
|
1333
|
+
}] } });
|
|
1334
|
+
|
|
1335
|
+
class NgVirtualListModule {
|
|
1336
|
+
}
|
|
1337
|
+
NgVirtualListModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1338
|
+
NgVirtualListModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListModule, declarations: [NgVirtualListComponent, NgVirtualListItemComponent], imports: [CommonModule], exports: [NgVirtualListComponent] });
|
|
1339
|
+
NgVirtualListModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListModule, imports: [CommonModule] });
|
|
1340
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NgVirtualListModule, decorators: [{
|
|
1341
|
+
type: NgModule,
|
|
1342
|
+
args: [{
|
|
1343
|
+
declarations: [NgVirtualListComponent, NgVirtualListItemComponent],
|
|
1344
|
+
exports: [NgVirtualListComponent],
|
|
1345
|
+
imports: [CommonModule],
|
|
1346
|
+
schemas: [NO_ERRORS_SCHEMA],
|
|
1347
|
+
}]
|
|
1348
|
+
}] });
|
|
1349
|
+
|
|
1350
|
+
/*
|
|
1351
|
+
* Public API Surface of ng-virtual-list
|
|
1352
|
+
*/
|
|
1353
|
+
|
|
1354
|
+
/**
|
|
1355
|
+
* Generated bundle index. Do not edit.
|
|
1356
|
+
*/
|
|
1357
|
+
|
|
1358
|
+
export { NgVirtualListComponent, NgVirtualListModule };
|
|
1359
|
+
//# sourceMappingURL=ng-virtual-list.mjs.map
|