@libs-ui/components-scroll-measure-items-direction-horizontal 0.2.356-41 → 0.2.356-43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,111 +1,393 @@
|
|
|
1
1
|
# @libs-ui/components-scroll-measure-items-direction-horizontal
|
|
2
2
|
|
|
3
|
-
> Directive
|
|
3
|
+
> Directive ảo hóa danh sách theo chiều ngang (horizontal virtual scrolling) với kích thước item động (dynamic width).
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
`LibsUiScrollMeasureItemDirectionHorizontalDirective` là
|
|
7
|
+
`LibsUiScrollMeasureItemDirectionHorizontalDirective` là directive Angular giúp tối ưu hiệu năng khi render danh sách lớn theo chiều ngang. Thay vì render toàn bộ DOM, directive chỉ giữ trong DOM các item đang nằm trong viewport (cộng thêm một số buffer), đồng thời dùng một element ảo để duy trì kích thước scroll chính xác. Directive hỗ trợ item có chiều rộng không đồng đều thông qua hàm đo width bất đồng bộ do consumer cung cấp.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Tính năng
|
|
10
10
|
|
|
11
|
-
- ✅ **Virtual Scrolling**: Chỉ render
|
|
12
|
-
- ✅ **Dynamic Width**: Hỗ trợ item có
|
|
13
|
-
- ✅ **
|
|
14
|
-
- ✅ **
|
|
11
|
+
- ✅ **Virtual Scrolling chiều ngang**: Chỉ render item trong vùng nhìn thấy, giảm số lượng DOM node đáng kể.
|
|
12
|
+
- ✅ **Dynamic Width**: Hỗ trợ mỗi item có chiều rộng khác nhau thông qua hàm `functionGetWidthItem` bất đồng bộ.
|
|
13
|
+
- ✅ **Buffer ahead**: Tự động render thêm 3 item ngoài viewport để tránh khoảng trắng khi scroll nhanh.
|
|
14
|
+
- ✅ **Scroll Control API**: Cung cấp `scrollInto`, `scrollToIndex`, `scrollToPosition`, `reCalculatorViewPort` qua output `outFunctionControl`.
|
|
15
|
+
- ✅ **ResizeObserver tích hợp**: Tự động tính lại viewport khi kích thước container thay đổi.
|
|
16
|
+
- ✅ **Đo width song song**: Dùng `Promise.all` để đo width toàn bộ item cùng lúc — tối ưu cho list hàng nghìn cột.
|
|
17
|
+
- ✅ **Tự cleanup**: Hủy observer, xóa element ảo và timer khi directive bị destroy.
|
|
15
18
|
|
|
16
19
|
## Khi nào sử dụng
|
|
17
20
|
|
|
18
|
-
- Hiển thị danh sách ảnh, thẻ,
|
|
19
|
-
- Kích thước các item không đều nhau (ví dụ:
|
|
20
|
-
- Cần
|
|
21
|
+
- Hiển thị danh sách ảnh, thẻ, tab, cột bảng theo chiều ngang với số lượng lớn (hàng trăm đến hàng nghìn item).
|
|
22
|
+
- Kích thước các item không đều nhau (ví dụ: tên cột dài ngắn khác nhau trong bảng dữ liệu lớn).
|
|
23
|
+
- Cần cuộn đến một item hoặc index cụ thể theo chương trình (`scrollToIndex`, `scrollInto`).
|
|
24
|
+
- Cần tính lại viewport khi dữ liệu hoặc kích thước container thay đổi động (`reCalculatorViewPort`).
|
|
21
25
|
|
|
22
26
|
## Cài đặt
|
|
23
27
|
|
|
24
28
|
```bash
|
|
25
|
-
# npm
|
|
26
29
|
npm install @libs-ui/components-scroll-measure-items-direction-horizontal
|
|
27
|
-
|
|
28
|
-
# yarn
|
|
29
|
-
yarn add @libs-ui/components-scroll-measure-items-direction-horizontal
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Import
|
|
33
33
|
|
|
34
34
|
```typescript
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective,
|
|
37
|
+
IScrollMeasureItemDirectionHorizontalFunctionsControl,
|
|
38
|
+
IScrollMeasureStoreItemConvert,
|
|
39
|
+
} from '@libs-ui/components-scroll-measure-items-direction-horizontal';
|
|
36
40
|
|
|
37
41
|
@Component({
|
|
38
42
|
standalone: true,
|
|
39
43
|
imports: [LibsUiScrollMeasureItemDirectionHorizontalDirective],
|
|
40
44
|
// ...
|
|
41
45
|
})
|
|
42
|
-
export class
|
|
46
|
+
export class MyComponent {}
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
## Ví dụ
|
|
49
|
+
## Ví dụ sử dụng
|
|
50
|
+
|
|
51
|
+
### Ví dụ 1 — Virtual scroll cơ bản (1000 item, chiều rộng đồng đều)
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// my-list.component.ts
|
|
55
|
+
import { Component, signal } from '@angular/core';
|
|
56
|
+
import {
|
|
57
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective,
|
|
58
|
+
IScrollMeasureItemDirectionHorizontalFunctionsControl,
|
|
59
|
+
} from '@libs-ui/components-scroll-measure-items-direction-horizontal';
|
|
60
|
+
|
|
61
|
+
interface T_Column {
|
|
62
|
+
id: number;
|
|
63
|
+
label: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Component({
|
|
67
|
+
selector: 'app-my-list',
|
|
68
|
+
standalone: true,
|
|
69
|
+
imports: [LibsUiScrollMeasureItemDirectionHorizontalDirective],
|
|
70
|
+
templateUrl: './my-list.component.html',
|
|
71
|
+
})
|
|
72
|
+
export class MyListComponent {
|
|
73
|
+
protected columns = signal<T_Column[]>(
|
|
74
|
+
Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, label: `Cột ${i + 1}` }))
|
|
75
|
+
);
|
|
76
|
+
protected viewPortColumns = signal<T_Column[]>([]);
|
|
77
|
+
protected scrollControl: IScrollMeasureItemDirectionHorizontalFunctionsControl | null = null;
|
|
78
|
+
|
|
79
|
+
protected measureColumnWidth = async (_item: T_Column): Promise<number> => {
|
|
80
|
+
// Chiều rộng cố định 120px mỗi cột
|
|
81
|
+
return 120;
|
|
82
|
+
};
|
|
46
83
|
|
|
47
|
-
|
|
84
|
+
protected handlerViewPortItem(items: T_Column[]): void {
|
|
85
|
+
this.viewPortColumns.set(items);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected handlerFunctionControl(control: IScrollMeasureItemDirectionHorizontalFunctionsControl): void {
|
|
89
|
+
this.scrollControl = control;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected handlerScrollToFirst(): void {
|
|
93
|
+
this.scrollControl?.scrollToIndex(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
protected handlerScrollToLast(): void {
|
|
97
|
+
this.scrollControl?.scrollToIndex(this.columns().length - 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
48
101
|
|
|
49
102
|
```html
|
|
103
|
+
<!-- my-list.component.html -->
|
|
104
|
+
<div class="flex gap-2 mb-4">
|
|
105
|
+
<button (click)="handlerScrollToFirst()">Đến đầu</button>
|
|
106
|
+
<button (click)="handlerScrollToLast()">Đến cuối</button>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- Outer container: overflow-x-auto, có thanh cuộn ngang -->
|
|
50
110
|
<div
|
|
51
111
|
#scrollContainer
|
|
52
|
-
class="
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[items]="items"
|
|
56
|
-
[functionGetWidthItem]="measureWidth"
|
|
57
|
-
(outViewPortItem)="viewPortItems = $event">
|
|
58
|
-
@for (item of viewPortItems; track item.id) {
|
|
112
|
+
class="w-full h-[80px] overflow-x-auto border border-gray-300 rounded relative">
|
|
113
|
+
|
|
114
|
+
<!-- Inner container: gắn directive, nằm bên trong outer -->
|
|
59
115
|
<div
|
|
60
|
-
class="
|
|
61
|
-
|
|
62
|
-
|
|
116
|
+
class="flex items-center h-full"
|
|
117
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective
|
|
118
|
+
[elementScroll]="scrollContainer"
|
|
119
|
+
[items]="columns()"
|
|
120
|
+
[functionGetWidthItem]="measureColumnWidth"
|
|
121
|
+
(outViewPortItem)="handlerViewPortItem($event)"
|
|
122
|
+
(outFunctionControl)="handlerFunctionControl($event)">
|
|
123
|
+
|
|
124
|
+
@for (col of viewPortColumns(); track col.id) {
|
|
125
|
+
<div class="shrink-0 w-[120px] h-[60px] flex items-center justify-center border-r border-gray-200 text-sm">
|
|
126
|
+
{{ col.label }}
|
|
127
|
+
</div>
|
|
128
|
+
}
|
|
63
129
|
</div>
|
|
130
|
+
</div>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### Ví dụ 2 — Item có chiều rộng động (tính từ nội dung)
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// dynamic-width-list.component.ts
|
|
139
|
+
import { Component, signal } from '@angular/core';
|
|
140
|
+
import {
|
|
141
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective,
|
|
142
|
+
IScrollMeasureItemDirectionHorizontalFunctionsControl,
|
|
143
|
+
} from '@libs-ui/components-scroll-measure-items-direction-horizontal';
|
|
144
|
+
|
|
145
|
+
interface T_Tag {
|
|
146
|
+
id: number;
|
|
147
|
+
name: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@Component({
|
|
151
|
+
selector: 'app-dynamic-width-list',
|
|
152
|
+
standalone: true,
|
|
153
|
+
imports: [LibsUiScrollMeasureItemDirectionHorizontalDirective],
|
|
154
|
+
templateUrl: './dynamic-width-list.component.html',
|
|
155
|
+
})
|
|
156
|
+
export class DynamicWidthListComponent {
|
|
157
|
+
protected tags = signal<T_Tag[]>([
|
|
158
|
+
{ id: 1, name: 'Angular' },
|
|
159
|
+
{ id: 2, name: 'Virtual Scroll' },
|
|
160
|
+
{ id: 3, name: 'Performance Optimization' },
|
|
161
|
+
{ id: 4, name: 'UI' },
|
|
162
|
+
{ id: 5, name: 'Horizontal Scrolling with Dynamic Items' },
|
|
163
|
+
{ id: 6, name: 'TypeScript' },
|
|
164
|
+
{ id: 7, name: 'RxJS' },
|
|
165
|
+
{ id: 8, name: 'Signal' },
|
|
166
|
+
]);
|
|
167
|
+
protected viewPortTags = signal<T_Tag[]>([]);
|
|
168
|
+
protected scrollControl: IScrollMeasureItemDirectionHorizontalFunctionsControl | null = null;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Ước tính chiều rộng dựa trên số ký tự.
|
|
172
|
+
* Cộng thêm 32px padding trái/phải (16px mỗi bên) và 8px gap.
|
|
173
|
+
*/
|
|
174
|
+
protected measureTagWidth = async (item: T_Tag): Promise<number> => {
|
|
175
|
+
const charWidth = 8;
|
|
176
|
+
return item.name.length * charWidth + 32 + 8;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
protected handlerViewPortItem(items: T_Tag[]): void {
|
|
180
|
+
this.viewPortTags.set(items);
|
|
64
181
|
}
|
|
182
|
+
|
|
183
|
+
protected handlerFunctionControl(control: IScrollMeasureItemDirectionHorizontalFunctionsControl): void {
|
|
184
|
+
this.scrollControl = control;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected handlerScrollIntoTag(tag: T_Tag): void {
|
|
188
|
+
this.scrollControl?.scrollInto(tag);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```html
|
|
194
|
+
<!-- dynamic-width-list.component.html -->
|
|
195
|
+
<div
|
|
196
|
+
#tagScrollContainer
|
|
197
|
+
class="w-full h-[60px] overflow-x-auto relative">
|
|
198
|
+
|
|
199
|
+
<div
|
|
200
|
+
class="flex items-center gap-2 h-full"
|
|
201
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective
|
|
202
|
+
[elementScroll]="tagScrollContainer"
|
|
203
|
+
[items]="tags()"
|
|
204
|
+
[functionGetWidthItem]="measureTagWidth"
|
|
205
|
+
(outViewPortItem)="handlerViewPortItem($event)"
|
|
206
|
+
(outFunctionControl)="handlerFunctionControl($event)">
|
|
207
|
+
|
|
208
|
+
@for (tag of viewPortTags(); track tag.id) {
|
|
209
|
+
<span
|
|
210
|
+
class="shrink-0 px-4 py-1 bg-blue-100 text-blue-800 rounded-full text-sm whitespace-nowrap cursor-pointer"
|
|
211
|
+
(click)="handlerScrollIntoTag(tag)">
|
|
212
|
+
{{ tag.name }}
|
|
213
|
+
</span>
|
|
214
|
+
}
|
|
215
|
+
</div>
|
|
65
216
|
</div>
|
|
66
217
|
```
|
|
67
218
|
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Ví dụ 3 — Cuộn theo chương trình và tính lại viewport
|
|
222
|
+
|
|
68
223
|
```typescript
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
224
|
+
// programmatic-scroll.component.ts
|
|
225
|
+
import { Component, signal } from '@angular/core';
|
|
226
|
+
import {
|
|
227
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective,
|
|
228
|
+
IScrollMeasureItemDirectionHorizontalFunctionsControl,
|
|
229
|
+
} from '@libs-ui/components-scroll-measure-items-direction-horizontal';
|
|
230
|
+
|
|
231
|
+
interface T_Photo {
|
|
232
|
+
id: number;
|
|
233
|
+
width: number;
|
|
234
|
+
src: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@Component({
|
|
238
|
+
selector: 'app-programmatic-scroll',
|
|
239
|
+
standalone: true,
|
|
240
|
+
imports: [LibsUiScrollMeasureItemDirectionHorizontalDirective],
|
|
241
|
+
templateUrl: './programmatic-scroll.component.html',
|
|
242
|
+
})
|
|
243
|
+
export class ProgrammaticScrollComponent {
|
|
244
|
+
protected photos = signal<T_Photo[]>(
|
|
245
|
+
Array.from({ length: 500 }, (_, i) => ({
|
|
246
|
+
id: i + 1,
|
|
247
|
+
width: 100 + (i % 4) * 50, // 100, 150, 200, 250 lặp lại
|
|
248
|
+
src: `https://picsum.photos/id/${i + 1}/${100 + (i % 4) * 50}/80`,
|
|
249
|
+
}))
|
|
250
|
+
);
|
|
251
|
+
protected viewPortPhotos = signal<T_Photo[]>([]);
|
|
252
|
+
protected scrollControl: IScrollMeasureItemDirectionHorizontalFunctionsControl | null = null;
|
|
253
|
+
|
|
254
|
+
protected measurePhotoWidth = async (item: T_Photo): Promise<number> => {
|
|
255
|
+
// Cộng thêm 8px gap giữa các ảnh
|
|
256
|
+
return item.width + 8;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
protected handlerViewPortItem(items: T_Photo[]): void {
|
|
260
|
+
this.viewPortPhotos.set(items);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
protected handlerFunctionControl(control: IScrollMeasureItemDirectionHorizontalFunctionsControl): void {
|
|
264
|
+
this.scrollControl = control;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
protected handlerScrollToIndex(index: number): void {
|
|
268
|
+
this.scrollControl?.scrollToIndex(index);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
protected handlerScrollToPosition(px: number): void {
|
|
272
|
+
this.scrollControl?.scrollToPosition(px);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
protected handlerRecalculate(): void {
|
|
276
|
+
this.scrollControl?.reCalculatorViewPort();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
77
279
|
```
|
|
78
280
|
|
|
79
|
-
|
|
281
|
+
```html
|
|
282
|
+
<!-- programmatic-scroll.component.html -->
|
|
283
|
+
<div class="flex gap-2 mb-3">
|
|
284
|
+
<button (click)="handlerScrollToIndex(0)">Đầu (0)</button>
|
|
285
|
+
<button (click)="handlerScrollToIndex(249)">Giữa (249)</button>
|
|
286
|
+
<button (click)="handlerScrollToIndex(499)">Cuối (499)</button>
|
|
287
|
+
<button (click)="handlerScrollToPosition(1000)">Vị trí 1000px</button>
|
|
288
|
+
<button (click)="handlerRecalculate()">Tính lại viewport</button>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div
|
|
292
|
+
#photoScroll
|
|
293
|
+
class="w-full h-[100px] overflow-x-auto relative border border-gray-200 rounded">
|
|
294
|
+
|
|
295
|
+
<div
|
|
296
|
+
class="flex items-center gap-2 h-full"
|
|
297
|
+
LibsUiScrollMeasureItemDirectionHorizontalDirective
|
|
298
|
+
[elementScroll]="photoScroll"
|
|
299
|
+
[items]="photos()"
|
|
300
|
+
[functionGetWidthItem]="measurePhotoWidth"
|
|
301
|
+
(outViewPortItem)="handlerViewPortItem($event)"
|
|
302
|
+
(outFunctionControl)="handlerFunctionControl($event)">
|
|
303
|
+
|
|
304
|
+
@for (photo of viewPortPhotos(); track photo.id) {
|
|
305
|
+
<img
|
|
306
|
+
class="shrink-0 h-[80px] rounded object-cover"
|
|
307
|
+
[style.width.px]="photo.width"
|
|
308
|
+
[src]="photo.src"
|
|
309
|
+
[alt]="'Photo ' + photo.id" />
|
|
310
|
+
}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## @Input()
|
|
80
316
|
|
|
81
|
-
|
|
317
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
318
|
+
|---|---|---|---|---|
|
|
319
|
+
| `[elementScroll]` | `HTMLElement` | **required** | Element cha có `overflow-x: auto` chứa thanh cuộn ngang. Thường dùng template variable `#scrollContainer`. | `[elementScroll]="scrollContainer"` |
|
|
320
|
+
| `[items]` | `Array<any>` | **required** | Toàn bộ mảng dữ liệu nguồn. Directive tự tính toán item nào cần render dựa trên viewport. | `[items]="columns()"` |
|
|
321
|
+
| `[functionGetWidthItem]` | `(item: any) => Promise<number>` | **required** | Hàm bất đồng bộ trả về chiều rộng (px) của một item, bao gồm cả padding và margin/gap. Được gọi song song cho toàn bộ mảng khi `items` thay đổi. | `[functionGetWidthItem]="measureWidth"` |
|
|
82
322
|
|
|
83
|
-
|
|
323
|
+
## @Output()
|
|
324
|
+
|
|
325
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
326
|
+
|---|---|---|---|---|
|
|
327
|
+
| `(outViewPortItem)` | `Array<any>` | Emit danh sách item cần render trong viewport (kèm buffer 3 item). Consumer dùng mảng này cho `@for`. | `handlerViewPortItem(items: T_Item[]): void { items.stopPropagation?.(); this.viewPortItems.set(items); }` | `(outViewPortItem)="handlerViewPortItem($event)"` |
|
|
328
|
+
| `(outFunctionControl)` | `IScrollMeasureItemDirectionHorizontalFunctionsControl` | Emit object chứa các hàm điều khiển scroll. Lưu lại để gọi `scrollToIndex`, `scrollInto`,... theo chương trình. | `handlerFunctionControl(ctrl: IScrollMeasureItemDirectionHorizontalFunctionsControl): void { this.scrollControl = ctrl; }` | `(outFunctionControl)="handlerFunctionControl($event)"` |
|
|
329
|
+
| `(outDivVirtual)` | `HTMLDivElement` | Emit element ảo (invisible) được chèn vào container để duy trì tổng chiều rộng scroll. Ít khi cần dùng trực tiếp. | `handlerDivVirtual(el: HTMLDivElement): void { this.virtualEl = el; }` | `(outDivVirtual)="handlerDivVirtual($event)"` |
|
|
330
|
+
| `(outPaddingLeft)` | `number` | Emit giá trị `paddingLeft` (px) hiện tại của inner container — bù cho các item đã scroll qua khỏi viewport bên trái. | `handlerPaddingLeft(value: number): void { this.currentPaddingLeft = value; }` | `(outPaddingLeft)="handlerPaddingLeft($event)"` |
|
|
331
|
+
|
|
332
|
+
## Types & Interfaces
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import {
|
|
336
|
+
IScrollMeasureItemDirectionHorizontalFunctionsControl,
|
|
337
|
+
IScrollMeasureStoreItemConvert,
|
|
338
|
+
} from '@libs-ui/components-scroll-measure-items-direction-horizontal';
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Object điều khiển scroll được emit qua (outFunctionControl).
|
|
342
|
+
* Lưu lại reference sau khi nhận để gọi các phương thức theo chương trình.
|
|
343
|
+
*/
|
|
344
|
+
interface IScrollMeasureItemDirectionHorizontalFunctionsControl {
|
|
345
|
+
/** Scroll container đến vị trí bắt đầu của item được truyền vào. */
|
|
346
|
+
scrollInto: (item: any) => Promise<void>;
|
|
347
|
+
|
|
348
|
+
/** Scroll container đến vị trí pixel chỉ định (scrollLeft). */
|
|
349
|
+
scrollToPosition: (position: number) => Promise<void>;
|
|
350
|
+
|
|
351
|
+
/** Scroll container đến vị trí bắt đầu của item tại index chỉ định. */
|
|
352
|
+
scrollToIndex: (index: number) => Promise<void>;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Gọi lại hàm đo width toàn bộ item và tính lại vùng hiển thị.
|
|
356
|
+
* Dùng khi kích thước item thay đổi sau khi render (ví dụ: font load xong, dữ liệu cập nhật).
|
|
357
|
+
*/
|
|
358
|
+
reCalculatorViewPort: () => Promise<void>;
|
|
359
|
+
|
|
360
|
+
/** Trả về mảng item đang hiển thị trong viewport tại thời điểm gọi. */
|
|
361
|
+
getViewPortItems: () => Array<any>;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Cấu trúc nội bộ lưu trữ thông tin vị trí từng item.
|
|
366
|
+
* Được emit qua (outDivVirtual) nếu consumer cần debug layout.
|
|
367
|
+
*/
|
|
368
|
+
interface IScrollMeasureStoreItemConvert {
|
|
369
|
+
/** Tham chiếu đến item gốc trong mảng [items]. */
|
|
370
|
+
ref: any;
|
|
371
|
+
/** Chiều rộng tính toán (px) của item này. */
|
|
372
|
+
itemWidth: number;
|
|
373
|
+
/** Vị trí bắt đầu (scrollLeft) của item, tính từ đầu container. */
|
|
374
|
+
start: number;
|
|
375
|
+
/** Vị trí kết thúc (scrollLeft + itemWidth) của item. */
|
|
376
|
+
end: number;
|
|
377
|
+
}
|
|
378
|
+
```
|
|
84
379
|
|
|
85
|
-
|
|
86
|
-
| ------------------------ | -------------------------------- | ---------- | ----------------------------------------------- |
|
|
87
|
-
| `[items]` | `Array<any>` | `required` | Danh sách dữ liệu gốc cần hiển thị. |
|
|
88
|
-
| `[elementScroll]` | `HTMLElement` | `required` | Element chứa thanh cuộn (container chính). |
|
|
89
|
-
| `[functionGetWidthItem]` | `(item: any) => Promise<number>` | `required` | Hàm async trả về chiều rộng (px) của từng item. |
|
|
380
|
+
## Lưu ý quan trọng
|
|
90
381
|
|
|
91
|
-
|
|
382
|
+
⚠️ **Kiến trúc 2 lớp bắt buộc**: Directive phải được gắn vào **inner container** (thẻ flex bên trong), KHÔNG phải outer container có `overflow-x: auto`. Input `[elementScroll]` phải trỏ đến outer container — đây là element có thanh cuộn thực sự.
|
|
92
383
|
|
|
93
|
-
|
|
94
|
-
| ---------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
95
|
-
| `(outViewPortItem)` | `Array<Record<string, any>>` | Emit danh sách item cần render trong viewport. Item gốc nằm trong property `ref`. |
|
|
96
|
-
| `(outFunctionControl)` | `IScrollMeasureItemDirectionHorizontalFunctionsControl` | Emit controller chứa các hàm điều khiển scroll. |
|
|
97
|
-
| `(outDivVirtual)` | `HTMLDivElement` | Emit element ảo được tạo ra để giữ chiều rộng scroll của container. |
|
|
384
|
+
⚠️ **Hàm `functionGetWidthItem` phải tính cả margin/gap**: Chiều rộng trả về phải bao gồm toàn bộ không gian mà item chiếm (bao gồm margin, gap). Nếu thiếu, tổng chiều rộng ảo sẽ sai và scroll sẽ bị lệch vị trí.
|
|
98
385
|
|
|
99
|
-
|
|
386
|
+
⚠️ **Directive thêm `position: relative` và điều chỉnh `paddingLeft` vào inner container**: Không đặt các style này thủ công trên element gắn directive vì sẽ bị override. Thay vào đó, dùng output `(outPaddingLeft)` nếu cần biết giá trị hiện tại.
|
|
100
387
|
|
|
101
|
-
**
|
|
388
|
+
⚠️ **Element ảo `divVirtual` được chèn vào outer container**: Directive tự tạo và quản lý một `<div>` ẩn bên trong `elementScroll` để giữ đúng tổng chiều rộng scroll. Không cần tạo thủ công. Element này tự xóa khi directive destroy.
|
|
102
389
|
|
|
103
|
-
|
|
104
|
-
| --------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
105
|
-
| `scrollInto(item: any): Promise<void>` | Scroll đến item cụ thể. |
|
|
106
|
-
| `scrollToPosition(position: number): Promise<void>` | Scroll đến vị trí pixel cụ thể. |
|
|
107
|
-
| `scrollToIndex(index: number): Promise<void>` | Scroll đến item tại index cụ thể. |
|
|
108
|
-
| `reCalculatorViewPort(): Promise<void>` | Tính toán lại viewport (dùng khi kích thước container hoặc item thay đổi). |
|
|
390
|
+
⚠️ **`items` thay đổi reference mới kích hoạt đo lại**: Mỗi khi `items` signal thay đổi, toàn bộ width được đo lại song song qua `Promise.all`. Với list rất lớn (> 5000 item) và hàm đo nặng, cân nhắc debounce ở phía consumer trước khi cập nhật `items`.
|
|
109
391
|
|
|
110
392
|
## Demo
|
|
111
393
|
|
|
@@ -113,4 +395,4 @@ measureWidth = async (item: any): Promise<number> => {
|
|
|
113
395
|
npx nx serve core-ui
|
|
114
396
|
```
|
|
115
397
|
|
|
116
|
-
Truy cập
|
|
398
|
+
Truy cập: http://localhost:4500/components/scroll-measure-items/direction-horizontal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libs-ui-components-scroll-measure-items-direction-horizontal.mjs","sources":["../../../../../../libs-ui/components/scroll-measure-items/direction-horizontal/src/direction-horizontal.directive.ts","../../../../../../libs-ui/components/scroll-measure-items/direction-horizontal/src/libs-ui-components-scroll-measure-items-direction-horizontal.ts"],"sourcesContent":["// CONVENTION-EXCEPT: any — Generic directive works with unknown item types from consumer\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { AfterViewInit, DestroyRef, Directive, ElementRef, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { fromEvent } from 'rxjs';\nimport { IScrollMeasureItemDirectionHorizontalFunctionsControl, IScrollMeasureStoreItemConvert } from './interfaces/direction-horizontal.interface';\n@Directive({\n // eslint-disable-next-line @angular-eslint/directive-selector\n selector: '[LibsUiScrollMeasureItemDirectionHorizontalDirective]',\n standalone: true,\n})\nexport class LibsUiScrollMeasureItemDirectionHorizontalDirective implements AfterViewInit {\n // #region PROPERTY\n /** Số item buffer render thêm sau viewport để tránh trắng vùng khi scroll. */\n private readonly bufferAhead = 3;\n\n private readonly viewPortItems = signal<Array<any>>([]);\n private readonly start = signal<number>(0);\n private readonly end = signal<number>(0);\n private readonly storeItems = signal<Array<IScrollMeasureStoreItemConvert>>([]);\n private readonly divVirtual = document.createElement('div');\n private resizeObserver?: ResizeObserver;\n /** Chặn ResizeObserver / subscribe được tạo trong setTimeout sau khi directive đã destroy. */\n private timerAfterViewInit?: number;\n /** Token nhận dạng lần effect chạy hiện tại. Async đo width xong sẽ bỏ kết quả nếu token đổi. */\n private executionToken = 0;\n /** Style + DOM hook của divVirtual chỉ cần khởi tạo một lần cho cả vòng đời directive. */\n private divVirtualInitialized = false;\n\n // #region INPUT\n readonly elementScroll = input.required<HTMLElement>();\n readonly functionGetWidthItem = input.required<(item: any) => Promise<number>>();\n readonly items = input.required<Array<any>>();\n\n // #region OUTPUT\n readonly outViewPortItem = output<Array<any>>();\n readonly outFunctionControl = output<IScrollMeasureItemDirectionHorizontalFunctionsControl>();\n readonly outDivVirtual = output<HTMLDivElement>();\n readonly outPaddingLeft = output<number>();\n\n // #region INJECT\n private readonly elementRef = inject(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n effect(() => {\n const items = this.items();\n const myToken = ++this.executionToken;\n untracked(async () => {\n const scrollEl = this.ScrollContainer;\n const functionGetWidthItem = this.functionGetWidthItem();\n if (!scrollEl || !functionGetWidthItem) {\n return;\n }\n\n // Đo width tất cả item song song — rút ngắn thời gian cho list lớn (vd 3000 cột).\n const widths = await Promise.all(items.map((it) => functionGetWidthItem(it)));\n // Bị huỷ vì có lượt items() mới chạy sau — bỏ kết quả cũ tránh đè state.\n if (myToken !== this.executionToken) {\n return;\n }\n\n this.start.set(0);\n const scrollLeft = scrollEl.scrollLeft;\n const containerWidth = scrollEl.getBoundingClientRect().width;\n let width = 0;\n let endLocal = 0;\n const newItems: Array<IScrollMeasureStoreItemConvert> = [];\n\n for (let i = 0; i < items.length; i++) {\n const itemWidth = widths[i];\n const newItem: IScrollMeasureStoreItemConvert = { ref: items[i], itemWidth, start: width, end: 0 };\n width += itemWidth;\n newItem.end = width;\n if (newItem.end <= containerWidth + scrollLeft) {\n endLocal = i;\n }\n newItems.push(newItem);\n }\n // Set end một lần thay vì ghi mỗi vòng lặp — tránh notify consumer N lần.\n this.end.set(endLocal);\n this.storeItems.set(newItems);\n\n // Khởi tạo style + DOM hook của divVirtual đúng một lần.\n if (!this.divVirtualInitialized) {\n this.divVirtual.style.position = 'absolute';\n this.divVirtual.style.top = '0px';\n this.divVirtual.style.left = '0px';\n this.divVirtual.style.height = '1px';\n this.divVirtual.style.visibility = 'hidden';\n this.elementRef.nativeElement.classList.add('relative');\n this.elementRef.nativeElement.classList.remove('w-full');\n this.elementRef.nativeElement.style.paddingLeft = '0px';\n scrollEl.append(this.divVirtual);\n this.divVirtualInitialized = true;\n this.outDivVirtual.emit(this.divVirtual);\n }\n // Width tổng thể đổi theo dataset → cần update mỗi lượt.\n this.divVirtual.style.width = `${width}px`;\n\n // containerWidth lúc đầu có thể = 0 (DOM chưa layout), khiến end giữ\n // nguyên 0 và chỉ render 3 cột đầu. Re-đo bằng handlerScroll sau khi vòng\n // lặp await xong (layout thường đã hoàn tất) để end ra đúng giá trị.\n this.handlerScroll();\n });\n });\n\n this.destroyRef.onDestroy(() => {\n clearTimeout(this.timerAfterViewInit);\n this.divVirtual.remove();\n this.resizeObserver?.disconnect();\n });\n }\n\n ngAfterViewInit() {\n this.timerAfterViewInit = setTimeout(() => {\n fromEvent<Event>(this.ScrollContainer, 'scroll')\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.handlerScroll());\n this.outFunctionControl.emit(this.FunctionsControl);\n\n // Re-tính viewport sau khi container có kích thước thật.\n // Lần đầu render, getBoundingClientRect().width có thể = 0 nên end giữ giá trị 0,\n // chỉ hiển thị 3 cột đầu (do slice(start, end + bufferAhead)). ResizeObserver fire ngay khi\n // observe và mỗi lần container đổi size sẽ kéo end về đúng giá trị.\n this.resizeObserver = new ResizeObserver(() => this.handlerScroll());\n this.resizeObserver.observe(this.ScrollContainer);\n });\n }\n\n // #region PUBLIC API\n public get FunctionsControl(): IScrollMeasureItemDirectionHorizontalFunctionsControl {\n return {\n scrollInto: this.scrollInto.bind(this),\n scrollToPosition: this.scrollToPosition.bind(this),\n scrollToIndex: this.scrollToIndex.bind(this),\n reCalculatorViewPort: this.reCalculatorViewPort.bind(this),\n getViewPortItems: () => this.viewPortItems(),\n };\n }\n\n // #region PRIVATE METHODS\n private get ScrollContainer(): HTMLElement {\n return this.elementScroll() || this.elementRef.nativeElement;\n }\n\n private handlerScroll() {\n const store = this.storeItems();\n if (!store?.length) {\n return;\n }\n const scrollEl = this.ScrollContainer;\n const scrollLeft = scrollEl.scrollLeft;\n const containerWidth = scrollEl.getBoundingClientRect().width;\n let start = this.start();\n let endLocal = this.end();\n let isSetStartPositioned = false;\n\n for (let i = 0; i < store.length; i++) {\n const item = store[i];\n if (item.end > containerWidth + scrollLeft) {\n break;\n }\n endLocal = i;\n if (item.end >= scrollLeft && !isSetStartPositioned) {\n start = i;\n isSetStartPositioned = true;\n }\n }\n if (endLocal !== this.end()) {\n this.end.set(endLocal);\n }\n\n if (start !== this.start()) {\n this.start.set(start);\n const paddingLeft = store[start].start;\n this.elementRef.nativeElement.style.paddingLeft = `${paddingLeft}px`;\n this.outPaddingLeft.emit(paddingLeft);\n }\n this.viewPortItems.set(store.slice(this.start(), this.end() + this.bufferAhead).map((item) => item.ref));\n this.outViewPortItem.emit(this.viewPortItems());\n }\n\n private async scrollInto(itemScroll: any) {\n const itemFound = this.storeItems().find((item) => item.ref === itemScroll);\n if (!itemFound) {\n return;\n }\n this.ScrollContainer.scrollLeft = itemFound.start;\n }\n\n private async scrollToPosition(position: number) {\n this.ScrollContainer.scrollLeft = position;\n }\n\n private async scrollToIndex(index: number) {\n const itemOfIndex = this.storeItems()[index];\n if (!itemOfIndex) {\n return;\n }\n this.ScrollContainer.scrollLeft = itemOfIndex.start;\n }\n\n private async reCalculatorViewPort() {\n const items = this.storeItems();\n const fn = this.functionGetWidthItem();\n // Đo song song để rút ngắn thời gian thay vì await tuần tự.\n const widths = await Promise.all(items.map((it) => fn(it.ref)));\n\n let width = 0;\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const itemWidth = widths[i];\n item.itemWidth = itemWidth;\n item.start = width;\n width += itemWidth;\n item.end = width;\n }\n\n this.storeItems.set([...items]);\n this.divVirtual.style.width = `${width}px`;\n this.handlerScroll();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"libs-ui-components-scroll-measure-items-direction-horizontal.mjs","sources":["../../../../../../libs-ui/components/scroll-measure-items/direction-horizontal/src/direction-horizontal.directive.ts","../../../../../../libs-ui/components/scroll-measure-items/direction-horizontal/src/libs-ui-components-scroll-measure-items-direction-horizontal.ts"],"sourcesContent":["// CONVENTION-EXCEPT: any — Generic directive works with unknown item types from consumer\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { AfterViewInit, DestroyRef, Directive, ElementRef, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { fromEvent } from 'rxjs';\nimport { IScrollMeasureItemDirectionHorizontalFunctionsControl, IScrollMeasureStoreItemConvert } from './interfaces/direction-horizontal.interface';\n@Directive({\n // eslint-disable-next-line @angular-eslint/directive-selector\n selector: '[LibsUiScrollMeasureItemDirectionHorizontalDirective]',\n standalone: true,\n})\nexport class LibsUiScrollMeasureItemDirectionHorizontalDirective implements AfterViewInit {\n // #region PROPERTY\n /** Số item buffer render thêm sau viewport để tránh trắng vùng khi scroll. */\n private readonly bufferAhead = 3;\n\n private readonly viewPortItems = signal<Array<any>>([]);\n private readonly start = signal<number>(0);\n private readonly end = signal<number>(0);\n private readonly storeItems = signal<Array<IScrollMeasureStoreItemConvert>>([]);\n private readonly divVirtual = document.createElement('div');\n private resizeObserver?: ResizeObserver;\n /** Chặn ResizeObserver / subscribe được tạo trong setTimeout sau khi directive đã destroy. */\n private timerAfterViewInit?: number;\n /** Token nhận dạng lần effect chạy hiện tại. Async đo width xong sẽ bỏ kết quả nếu token đổi. */\n private executionToken = 0;\n /** Style + DOM hook của divVirtual chỉ cần khởi tạo một lần cho cả vòng đời directive. */\n private divVirtualInitialized = false;\n\n // #region INPUT\n readonly elementScroll = input.required<HTMLElement>();\n readonly functionGetWidthItem = input.required<(item: any) => Promise<number>>();\n readonly items = input.required<Array<any>>();\n\n // #region OUTPUT\n readonly outViewPortItem = output<Array<any>>();\n readonly outFunctionControl = output<IScrollMeasureItemDirectionHorizontalFunctionsControl>();\n readonly outDivVirtual = output<HTMLDivElement>();\n readonly outPaddingLeft = output<number>();\n\n // #region INJECT\n private readonly elementRef = inject(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n effect(() => {\n const items = this.items();\n const myToken = ++this.executionToken;\n untracked(async () => {\n const scrollEl = this.ScrollContainer;\n const functionGetWidthItem = this.functionGetWidthItem();\n if (!scrollEl || !functionGetWidthItem) {\n return;\n }\n\n // Đo width tất cả item song song — rút ngắn thời gian cho list lớn (vd 3000 cột).\n const widths = await Promise.all(items.map((it) => functionGetWidthItem(it)));\n // Bị huỷ vì có lượt items() mới chạy sau — bỏ kết quả cũ tránh đè state.\n if (myToken !== this.executionToken) {\n return;\n }\n\n this.start.set(0);\n const scrollLeft = scrollEl.scrollLeft;\n const containerWidth = scrollEl.getBoundingClientRect().width;\n let width = 0;\n let endLocal = 0;\n const newItems: Array<IScrollMeasureStoreItemConvert> = [];\n\n for (let i = 0; i < items.length; i++) {\n const itemWidth = widths[i];\n const newItem: IScrollMeasureStoreItemConvert = { ref: items[i], itemWidth, start: width, end: 0 };\n width += itemWidth;\n newItem.end = width;\n if (newItem.end <= containerWidth + scrollLeft) {\n endLocal = i;\n }\n newItems.push(newItem);\n }\n // Set end một lần thay vì ghi mỗi vòng lặp — tránh notify consumer N lần.\n this.end.set(endLocal);\n this.storeItems.set(newItems);\n\n // Khởi tạo style + DOM hook của divVirtual đúng một lần.\n if (!this.divVirtualInitialized) {\n this.divVirtual.style.position = 'absolute';\n this.divVirtual.style.top = '0px';\n this.divVirtual.style.left = '0px';\n this.divVirtual.style.height = '1px';\n this.divVirtual.style.visibility = 'hidden';\n this.elementRef.nativeElement.classList.add('relative');\n this.elementRef.nativeElement.classList.remove('w-full');\n this.elementRef.nativeElement.style.paddingLeft = '0px';\n scrollEl.append(this.divVirtual);\n this.divVirtualInitialized = true;\n this.outDivVirtual.emit(this.divVirtual);\n }\n // Width tổng thể đổi theo dataset → cần update mỗi lượt.\n this.divVirtual.style.width = `${width}px`;\n\n // containerWidth lúc đầu có thể = 0 (DOM chưa layout), khiến end giữ\n // nguyên 0 và chỉ render 3 cột đầu. Re-đo bằng handlerScroll sau khi vòng\n // lặp await xong (layout thường đã hoàn tất) để end ra đúng giá trị.\n this.handlerScroll();\n });\n });\n\n this.destroyRef.onDestroy(() => {\n clearTimeout(this.timerAfterViewInit);\n this.divVirtual.remove();\n this.resizeObserver?.disconnect();\n });\n }\n\n ngAfterViewInit() {\n this.timerAfterViewInit = setTimeout(() => {\n fromEvent<Event>(this.ScrollContainer, 'scroll')\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.handlerScroll());\n this.outFunctionControl.emit(this.FunctionsControl);\n\n // Re-tính viewport sau khi container có kích thước thật.\n // Lần đầu render, getBoundingClientRect().width có thể = 0 nên end giữ giá trị 0,\n // chỉ hiển thị 3 cột đầu (do slice(start, end + bufferAhead)). ResizeObserver fire ngay khi\n // observe và mỗi lần container đổi size sẽ kéo end về đúng giá trị.\n this.resizeObserver = new ResizeObserver(() => this.handlerScroll());\n this.resizeObserver.observe(this.ScrollContainer);\n });\n }\n\n // #region PUBLIC API\n public get FunctionsControl(): IScrollMeasureItemDirectionHorizontalFunctionsControl {\n return {\n scrollInto: this.scrollInto.bind(this),\n scrollToPosition: this.scrollToPosition.bind(this),\n scrollToIndex: this.scrollToIndex.bind(this),\n reCalculatorViewPort: this.reCalculatorViewPort.bind(this),\n getViewPortItems: () => this.viewPortItems(),\n };\n }\n\n // #region PRIVATE METHODS\n private get ScrollContainer(): HTMLElement {\n return this.elementScroll() || this.elementRef.nativeElement;\n }\n\n private handlerScroll() {\n const store = this.storeItems();\n if (!store?.length) {\n return;\n }\n const scrollEl = this.ScrollContainer;\n const scrollLeft = scrollEl.scrollLeft;\n const containerWidth = scrollEl.getBoundingClientRect().width;\n let start = this.start();\n let endLocal = this.end();\n let isSetStartPositioned = false;\n\n for (let i = 0; i < store.length; i++) {\n const item = store[i];\n if (item.end > containerWidth + scrollLeft) {\n break;\n }\n endLocal = i;\n if (item.end >= scrollLeft && !isSetStartPositioned) {\n start = i;\n isSetStartPositioned = true;\n }\n }\n if (endLocal !== this.end()) {\n this.end.set(endLocal);\n }\n\n if (start !== this.start()) {\n this.start.set(start);\n const paddingLeft = store[start].start;\n this.elementRef.nativeElement.style.paddingLeft = `${paddingLeft}px`;\n this.outPaddingLeft.emit(paddingLeft);\n }\n this.viewPortItems.set(store.slice(this.start(), this.end() + this.bufferAhead).map((item) => item.ref));\n this.outViewPortItem.emit(this.viewPortItems());\n }\n\n private async scrollInto(itemScroll: any) {\n const itemFound = this.storeItems().find((item) => item.ref === itemScroll);\n if (!itemFound) {\n return;\n }\n this.ScrollContainer.scrollLeft = itemFound.start;\n }\n\n private async scrollToPosition(position: number) {\n this.ScrollContainer.scrollLeft = position;\n }\n\n private async scrollToIndex(index: number) {\n const itemOfIndex = this.storeItems()[index];\n if (!itemOfIndex) {\n return;\n }\n this.ScrollContainer.scrollLeft = itemOfIndex.start;\n }\n\n private async reCalculatorViewPort() {\n const items = this.storeItems();\n const fn = this.functionGetWidthItem();\n // Đo song song để rút ngắn thời gian thay vì await tuần tự.\n const widths = await Promise.all(items.map((it) => fn(it.ref)));\n\n let width = 0;\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const itemWidth = widths[i];\n item.itemWidth = itemWidth;\n item.start = width;\n width += itemWidth;\n item.end = width;\n }\n\n this.storeItems.set([...items]);\n this.divVirtual.style.width = `${width}px`;\n this.handlerScroll();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|