@libs-ui/components-scroll-measure-items-direction-horizontal 0.2.356-9 → 0.2.357-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 CHANGED
@@ -1,111 +1,393 @@
1
1
  # @libs-ui/components-scroll-measure-items-direction-horizontal
2
2
 
3
- > Directive hỗ trợ ảo hóa danh sách (virtual scrolling) theo chiều ngang với kích thước item động (dynamic width).
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à một directive giúp tối ưu hóa hiệu năng khi render danh sách lớn các item theo chiều ngang. chỉ render các item đang nằm trong vùng nhìn thấy (viewport) sử dụng các element ảo để duy trì kích thước scroll, đồng thời hỗ trợ các hàm điều khiển scroll mạnh mẽ.
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 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
- ### Tính năng
9
+ ## Tính năng
10
10
 
11
- - ✅ **Virtual Scrolling**: Chỉ render các item trong viewport, giúp giảm số lượng DOM node.
12
- - ✅ **Dynamic Width**: Hỗ trợ item có kích thước thay đổi (không cố định) thông qua hàm đo kích thước bất đồng bộ.
13
- - ✅ **Scroll Control API**: Cung cấp các phương thức `scrollToIndex`, `scrollToPosition`, `scrollInto` item cụ thể.
14
- - ✅ **Performance**: Tối ưu hóa cho danh sách lớn (hàng nghìn item).
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ẻ, hoặc item theo chiều ngang với số lượng lớn.
19
- - Kích thước các item không đều nhau (ví dụ: text dài ngắn khác nhau).
20
- - Cần tính năng scroll đến item cụ thể hoặc index cụ thể.
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 { LibsUiScrollMeasureItemDirectionHorizontalDirective } from '@libs-ui/components-scroll-measure-items-direction-horizontal';
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 YourComponent {}
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
- ### Basic Usage
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="scroll-container flex overflow-x-auto"
53
- LibsUiScrollMeasureItemDirectionHorizontalDirective
54
- [elementScroll]="scrollContainer"
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="item"
61
- [style.width.px]="item.width">
62
- {{ item.text }}
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
- items = [...]; // Data source
70
- viewPortItems = []; // Items processed for display
71
-
72
- // Function to measure item width (async)
73
- measureWidth = async (item: any): Promise<number> => {
74
- // Logic to calculate width, e.g., based on content length or fixed rules
75
- return item.text.length * 10 + 20;
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
- ## API
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
- ### LibsUiScrollMeasureItemDirectionHorizontalDirective
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
- #### Inputs
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
- | Property | Type | Default | Description |
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
- #### Outputs
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
- | Property | Type | Description |
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
- #### Interfaces
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
- **IScrollMeasureItemDirectionHorizontalFunctionsControl**
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
- | Method | Description |
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 path: `/components/scroll-measure-items/direction-horizontal`
398
+ Truy cập: http://localhost:4500/components/scroll-measure-items/direction-horizontal
@@ -1,30 +1,39 @@
1
- import { AfterViewInit, OnDestroy } from '@angular/core';
1
+ import { AfterViewInit } from '@angular/core';
2
2
  import { IScrollMeasureItemDirectionHorizontalFunctionsControl } from './interfaces/direction-horizontal.interface';
3
3
  import * as i0 from "@angular/core";
4
- export declare class LibsUiScrollMeasureItemDirectionHorizontalDirective implements AfterViewInit, OnDestroy {
5
- viewPortItems: import("@angular/core").WritableSignal<Record<string, any>[]>;
6
- private start;
7
- private end;
8
- private storeItems;
9
- private divVirtual;
10
- private onDestroy;
4
+ export declare class LibsUiScrollMeasureItemDirectionHorizontalDirective implements AfterViewInit {
5
+ /** Số item buffer render thêm sau viewport để tránh trắng vùng khi scroll. */
6
+ private readonly bufferAhead;
7
+ private readonly viewPortItems;
8
+ private readonly start;
9
+ private readonly end;
10
+ private readonly storeItems;
11
+ private readonly divVirtual;
12
+ private resizeObserver?;
13
+ /** Chặn ResizeObserver / subscribe được tạo trong setTimeout sau khi directive đã destroy. */
14
+ private timerAfterViewInit?;
15
+ /** 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. */
16
+ private executionToken;
17
+ /** Style + DOM hook của divVirtual chỉ cần khởi tạo một lần cho cả vòng đời directive. */
18
+ private divVirtualInitialized;
11
19
  readonly elementScroll: import("@angular/core").InputSignal<HTMLElement>;
12
20
  readonly functionGetWidthItem: import("@angular/core").InputSignal<(item: any) => Promise<number>>;
13
21
  readonly items: import("@angular/core").InputSignal<any[]>;
14
- readonly outViewPortItem: import("@angular/core").OutputEmitterRef<Record<string, any>[]>;
22
+ readonly outViewPortItem: import("@angular/core").OutputEmitterRef<any[]>;
15
23
  readonly outFunctionControl: import("@angular/core").OutputEmitterRef<IScrollMeasureItemDirectionHorizontalFunctionsControl>;
16
24
  readonly outDivVirtual: import("@angular/core").OutputEmitterRef<HTMLDivElement>;
17
- private elementRef;
25
+ readonly outPaddingLeft: import("@angular/core").OutputEmitterRef<number>;
26
+ private readonly elementRef;
27
+ private readonly destroyRef;
18
28
  constructor();
19
29
  ngAfterViewInit(): void;
20
- private get ElementScroll();
21
30
  get FunctionsControl(): IScrollMeasureItemDirectionHorizontalFunctionsControl;
31
+ private get ScrollContainer();
22
32
  private handlerScroll;
23
33
  private scrollInto;
24
34
  private scrollToPosition;
25
35
  private scrollToIndex;
26
36
  private reCalculatorViewPort;
27
- ngOnDestroy(): void;
28
37
  static ɵfac: i0.ɵɵFactoryDeclaration<LibsUiScrollMeasureItemDirectionHorizontalDirective, never>;
29
- static ɵdir: i0.ɵɵDirectiveDeclaration<LibsUiScrollMeasureItemDirectionHorizontalDirective, "[LibsUiScrollMeasureItemDirectionHorizontalDirective]", never, { "elementScroll": { "alias": "elementScroll"; "required": true; "isSignal": true; }; "functionGetWidthItem": { "alias": "functionGetWidthItem"; "required": true; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; }, { "outViewPortItem": "outViewPortItem"; "outFunctionControl": "outFunctionControl"; "outDivVirtual": "outDivVirtual"; }, never, never, true, never>;
38
+ static ɵdir: i0.ɵɵDirectiveDeclaration<LibsUiScrollMeasureItemDirectionHorizontalDirective, "[LibsUiScrollMeasureItemDirectionHorizontalDirective]", never, { "elementScroll": { "alias": "elementScroll"; "required": true; "isSignal": true; }; "functionGetWidthItem": { "alias": "functionGetWidthItem"; "required": true; "isSignal": true; }; "items": { "alias": "items"; "required": true; "isSignal": true; }; }, { "outViewPortItem": "outViewPortItem"; "outFunctionControl": "outFunctionControl"; "outDivVirtual": "outDivVirtual"; "outPaddingLeft": "outPaddingLeft"; }, never, never, true, never>;
30
39
  }
@@ -1,16 +1,25 @@
1
+ // CONVENTION-EXCEPT: any — Generic directive works with unknown item types from consumer
1
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Directive, ElementRef, effect, inject, input, output, signal, untracked } from '@angular/core';
3
- import { cloneDeep, set } from '@libs-ui/utils';
4
- import { Subject, fromEvent, takeUntil } from 'rxjs';
3
+ import { DestroyRef, Directive, ElementRef, effect, inject, input, output, signal, untracked } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { fromEvent } from 'rxjs';
5
6
  import * as i0 from "@angular/core";
6
7
  export class LibsUiScrollMeasureItemDirectionHorizontalDirective {
7
8
  // #region PROPERTY
9
+ /** Số item buffer render thêm sau viewport để tránh trắng vùng khi scroll. */
10
+ bufferAhead = 3;
8
11
  viewPortItems = signal([]);
9
12
  start = signal(0);
10
13
  end = signal(0);
11
14
  storeItems = signal([]);
12
15
  divVirtual = document.createElement('div');
13
- onDestroy = new Subject();
16
+ resizeObserver;
17
+ /** Chặn ResizeObserver / subscribe được tạo trong setTimeout sau khi directive đã destroy. */
18
+ timerAfterViewInit;
19
+ /** 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. */
20
+ executionToken = 0;
21
+ /** Style + DOM hook của divVirtual chỉ cần khởi tạo một lần cho cả vòng đời directive. */
22
+ divVirtualInitialized = false;
14
23
  // #region INPUT
15
24
  elementScroll = input.required();
16
25
  functionGetWidthItem = input.required();
@@ -19,134 +28,172 @@ export class LibsUiScrollMeasureItemDirectionHorizontalDirective {
19
28
  outViewPortItem = output();
20
29
  outFunctionControl = output();
21
30
  outDivVirtual = output();
31
+ outPaddingLeft = output();
22
32
  // #region INJECT
23
33
  elementRef = inject(ElementRef);
34
+ destroyRef = inject(DestroyRef);
24
35
  constructor() {
25
36
  effect(() => {
26
- if (!untracked(() => this.ElementScroll) || !untracked(() => this.functionGetWidthItem())) {
27
- return;
28
- }
29
37
  const items = this.items();
38
+ const myToken = ++this.executionToken;
30
39
  untracked(async () => {
40
+ const scrollEl = this.ScrollContainer;
41
+ const functionGetWidthItem = this.functionGetWidthItem();
42
+ if (!scrollEl || !functionGetWidthItem) {
43
+ return;
44
+ }
45
+ // Đo width tất cả item song song — rút ngắn thời gian cho list lớn (vd 3000 cột).
46
+ const widths = await Promise.all(items.map((it) => functionGetWidthItem(it)));
47
+ // Bị huỷ vì có lượt items() mới chạy sau — bỏ kết quả cũ tránh đè state.
48
+ if (myToken !== this.executionToken) {
49
+ return;
50
+ }
31
51
  this.start.set(0);
32
- const rect = this.ElementScroll.getBoundingClientRect();
52
+ const scrollLeft = scrollEl.scrollLeft;
53
+ const containerWidth = scrollEl.getBoundingClientRect().width;
33
54
  let width = 0;
55
+ let endLocal = 0;
34
56
  const newItems = [];
35
- for (const index in items) {
36
- const item = items[index];
37
- const newItem = cloneDeep(item);
38
- newItem.ref = item;
39
- const itemWidth = await this.functionGetWidthItem()(item);
40
- newItem.itemWidth = itemWidth;
41
- newItem.start = width;
57
+ for (let i = 0; i < items.length; i++) {
58
+ const itemWidth = widths[i];
59
+ const newItem = { ref: items[i], itemWidth, start: width, end: 0 };
42
60
  width += itemWidth;
43
61
  newItem.end = width;
44
- if (newItem.end <= rect.width + this.ElementScroll.scrollLeft) {
45
- this.end.set(+index);
62
+ if (newItem.end <= containerWidth + scrollLeft) {
63
+ endLocal = i;
46
64
  }
47
65
  newItems.push(newItem);
48
66
  }
67
+ // Set end một lần thay vì ghi mỗi vòng lặp — tránh notify consumer N lần.
68
+ this.end.set(endLocal);
49
69
  this.storeItems.set(newItems);
50
- this.viewPortItems.set(this.storeItems()
51
- .slice(this.start(), this.end() + 3)
52
- .map((item) => item['ref']));
53
- this.outViewPortItem.emit(this.viewPortItems());
54
- this.divVirtual.style.position = 'absolute';
70
+ // Khởi tạo style + DOM hook của divVirtual đúng một lần.
71
+ if (!this.divVirtualInitialized) {
72
+ this.divVirtual.style.position = 'absolute';
73
+ this.divVirtual.style.top = '0px';
74
+ this.divVirtual.style.left = '0px';
75
+ this.divVirtual.style.height = '1px';
76
+ this.divVirtual.style.visibility = 'hidden';
77
+ this.elementRef.nativeElement.classList.add('relative');
78
+ this.elementRef.nativeElement.classList.remove('w-full');
79
+ this.elementRef.nativeElement.style.paddingLeft = '0px';
80
+ scrollEl.append(this.divVirtual);
81
+ this.divVirtualInitialized = true;
82
+ this.outDivVirtual.emit(this.divVirtual);
83
+ }
84
+ // Width tổng thể đổi theo dataset → cần update mỗi lượt.
55
85
  this.divVirtual.style.width = `${width}px`;
56
- this.divVirtual.style.height = '1px';
57
- this.elementRef.nativeElement.classList.add('relative');
58
- this.elementRef.nativeElement.classList.remove('w-full');
59
- this.elementRef.nativeElement.style.paddingLeft = `0px`;
60
- this.ElementScroll.append(this.divVirtual);
61
- this.outDivVirtual.emit(this.divVirtual);
86
+ // containerWidth lúc đầu có thể = 0 (DOM chưa layout), khiến end giữ
87
+ // nguyên 0 và chỉ render 3 cột đầu. Re-đo bằng handlerScroll sau khi vòng
88
+ // lặp await xong (layout thường đã hoàn tất) để end ra đúng giá trị.
89
+ this.handlerScroll();
62
90
  });
63
91
  });
92
+ this.destroyRef.onDestroy(() => {
93
+ clearTimeout(this.timerAfterViewInit);
94
+ this.divVirtual.remove();
95
+ this.resizeObserver?.disconnect();
96
+ });
64
97
  }
65
98
  ngAfterViewInit() {
66
- setTimeout(() => {
67
- fromEvent(this.ElementScroll, 'scroll').pipe(takeUntil(this.onDestroy)).subscribe(this.handlerScroll.bind(this));
99
+ this.timerAfterViewInit = setTimeout(() => {
100
+ fromEvent(this.ScrollContainer, 'scroll')
101
+ .pipe(takeUntilDestroyed(this.destroyRef))
102
+ .subscribe(() => this.handlerScroll());
68
103
  this.outFunctionControl.emit(this.FunctionsControl);
104
+ // Re-tính viewport sau khi container có kích thước thật.
105
+ // Lần đầu render, getBoundingClientRect().width có thể = 0 nên end giữ giá trị 0,
106
+ // chỉ hiển thị 3 cột đầu (do slice(start, end + bufferAhead)). ResizeObserver fire ngay khi
107
+ // observe và mỗi lần container đổi size sẽ kéo end về đúng giá trị.
108
+ this.resizeObserver = new ResizeObserver(() => this.handlerScroll());
109
+ this.resizeObserver.observe(this.ScrollContainer);
69
110
  });
70
111
  }
71
- get ElementScroll() {
72
- return this.elementScroll() || this.elementRef.nativeElement;
73
- }
74
- /* FUNCTIONS */
112
+ // #region PUBLIC API
75
113
  get FunctionsControl() {
76
114
  return {
77
115
  scrollInto: this.scrollInto.bind(this),
78
116
  scrollToPosition: this.scrollToPosition.bind(this),
79
117
  scrollToIndex: this.scrollToIndex.bind(this),
80
118
  reCalculatorViewPort: this.reCalculatorViewPort.bind(this),
119
+ getViewPortItems: () => this.viewPortItems(),
81
120
  };
82
121
  }
122
+ // #region PRIVATE METHODS
123
+ get ScrollContainer() {
124
+ return this.elementScroll() || this.elementRef.nativeElement;
125
+ }
83
126
  handlerScroll() {
84
- if (!this.storeItems()?.length) {
127
+ const store = this.storeItems();
128
+ if (!store?.length) {
85
129
  return;
86
130
  }
87
- const rect = this.ElementScroll.getBoundingClientRect();
131
+ const scrollEl = this.ScrollContainer;
132
+ const scrollLeft = scrollEl.scrollLeft;
133
+ const containerWidth = scrollEl.getBoundingClientRect().width;
88
134
  let start = this.start();
135
+ let endLocal = this.end();
89
136
  let isSetStartPositioned = false;
90
- for (const index in this.storeItems()) {
91
- const item = this.storeItems()[index];
92
- if (item['end'] > rect.width + this.ElementScroll.scrollLeft) {
137
+ for (let i = 0; i < store.length; i++) {
138
+ const item = store[i];
139
+ if (item.end > containerWidth + scrollLeft) {
93
140
  break;
94
141
  }
95
- this.end.set(+index);
96
- if (item['end'] >= this.ElementScroll.scrollLeft && !isSetStartPositioned) {
97
- start = +index;
142
+ endLocal = i;
143
+ if (item.end >= scrollLeft && !isSetStartPositioned) {
144
+ start = i;
98
145
  isSetStartPositioned = true;
99
146
  }
100
147
  }
148
+ if (endLocal !== this.end()) {
149
+ this.end.set(endLocal);
150
+ }
101
151
  if (start !== this.start()) {
102
152
  this.start.set(start);
103
- this.elementRef.nativeElement.style.paddingLeft = `${this.storeItems()[start]['start']}px`;
104
- }
105
- if (this.start() < 0) {
106
- this.start.set(0);
153
+ const paddingLeft = store[start].start;
154
+ this.elementRef.nativeElement.style.paddingLeft = `${paddingLeft}px`;
155
+ this.outPaddingLeft.emit(paddingLeft);
107
156
  }
108
- this.viewPortItems.set(this.storeItems()
109
- .slice(this.start(), this.end() + 3)
110
- .map((item) => item['ref']));
157
+ this.viewPortItems.set(store.slice(this.start(), this.end() + this.bufferAhead).map((item) => item.ref));
111
158
  this.outViewPortItem.emit(this.viewPortItems());
112
159
  }
113
160
  async scrollInto(itemScroll) {
114
- const itemFound = this.storeItems().find((item) => item['ref'] === itemScroll);
115
- if (itemFound) {
116
- this.ElementScroll.scrollLeft = itemFound['start'];
161
+ const itemFound = this.storeItems().find((item) => item.ref === itemScroll);
162
+ if (!itemFound) {
163
+ return;
117
164
  }
165
+ this.ScrollContainer.scrollLeft = itemFound.start;
118
166
  }
119
167
  async scrollToPosition(position) {
120
- this.ElementScroll.scrollLeft = position;
168
+ this.ScrollContainer.scrollLeft = position;
121
169
  }
122
170
  async scrollToIndex(index) {
123
171
  const itemOfIndex = this.storeItems()[index];
124
- if (itemOfIndex) {
125
- this.ElementScroll.scrollLeft = itemOfIndex['start'];
172
+ if (!itemOfIndex) {
173
+ return;
126
174
  }
175
+ this.ScrollContainer.scrollLeft = itemOfIndex.start;
127
176
  }
128
177
  async reCalculatorViewPort() {
178
+ const items = this.storeItems();
179
+ const fn = this.functionGetWidthItem();
180
+ // Đo song song để rút ngắn thời gian thay vì await tuần tự.
181
+ const widths = await Promise.all(items.map((it) => fn(it.ref)));
129
182
  let width = 0;
130
- this.storeItems.update((items) => {
131
- items.forEach(async (item) => {
132
- const itemWidth = await this.functionGetWidthItem()(item['ref']);
133
- set(item, 'itemWidth', itemWidth);
134
- set(item, 'start', width);
135
- width += itemWidth;
136
- set(item, 'end', width);
137
- });
138
- return [...items];
139
- });
183
+ for (let i = 0; i < items.length; i++) {
184
+ const item = items[i];
185
+ const itemWidth = widths[i];
186
+ item.itemWidth = itemWidth;
187
+ item.start = width;
188
+ width += itemWidth;
189
+ item.end = width;
190
+ }
191
+ this.storeItems.set([...items]);
140
192
  this.divVirtual.style.width = `${width}px`;
141
193
  this.handlerScroll();
142
194
  }
143
- ngOnDestroy() {
144
- this.divVirtual.remove();
145
- this.onDestroy.next();
146
- this.onDestroy.complete();
147
- }
148
195
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiScrollMeasureItemDirectionHorizontalDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
149
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiScrollMeasureItemDirectionHorizontalDirective, isStandalone: true, selector: "[LibsUiScrollMeasureItemDirectionHorizontalDirective]", inputs: { elementScroll: { classPropertyName: "elementScroll", publicName: "elementScroll", isSignal: true, isRequired: true, transformFunction: null }, functionGetWidthItem: { classPropertyName: "functionGetWidthItem", publicName: "functionGetWidthItem", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { outViewPortItem: "outViewPortItem", outFunctionControl: "outFunctionControl", outDivVirtual: "outDivVirtual" }, ngImport: i0 });
196
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiScrollMeasureItemDirectionHorizontalDirective, isStandalone: true, selector: "[LibsUiScrollMeasureItemDirectionHorizontalDirective]", inputs: { elementScroll: { classPropertyName: "elementScroll", publicName: "elementScroll", isSignal: true, isRequired: true, transformFunction: null }, functionGetWidthItem: { classPropertyName: "functionGetWidthItem", publicName: "functionGetWidthItem", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { outViewPortItem: "outViewPortItem", outFunctionControl: "outFunctionControl", outDivVirtual: "outDivVirtual", outPaddingLeft: "outPaddingLeft" }, ngImport: i0 });
150
197
  }
151
198
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiScrollMeasureItemDirectionHorizontalDirective, decorators: [{
152
199
  type: Directive,
@@ -156,4 +203,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
156
203
  standalone: true,
157
204
  }]
158
205
  }], ctorParameters: () => [] });
159
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlyZWN0aW9uLWhvcml6b250YWwuZGlyZWN0aXZlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3Njcm9sbC1tZWFzdXJlLWl0ZW1zL2RpcmVjdGlvbi1ob3Jpem9udGFsL3NyYy9kaXJlY3Rpb24taG9yaXpvbnRhbC5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsdURBQXVEO0FBQ3ZELE9BQU8sRUFBaUIsU0FBUyxFQUFFLFVBQVUsRUFBYSxNQUFNLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNsSSxPQUFPLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ2hELE9BQU8sRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxNQUFNLE1BQU0sQ0FBQzs7QUFPckQsTUFBTSxPQUFPLG1EQUFtRDtJQUM5RCxtQkFBbUI7SUFDWixhQUFhLEdBQUcsTUFBTSxDQUE2QixFQUFFLENBQUMsQ0FBQztJQUN0RCxLQUFLLEdBQUcsTUFBTSxDQUFTLENBQUMsQ0FBQyxDQUFDO0lBQzFCLEdBQUcsR0FBRyxNQUFNLENBQVMsQ0FBQyxDQUFDLENBQUM7SUFDeEIsVUFBVSxHQUFHLE1BQU0sQ0FBNkIsRUFBRSxDQUFDLENBQUM7SUFDcEQsVUFBVSxHQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0MsU0FBUyxHQUFHLElBQUksT0FBTyxFQUFRLENBQUM7SUFFeEMsZ0JBQWdCO0lBQ1AsYUFBYSxHQUFHLEtBQUssQ0FBQyxRQUFRLEVBQWUsQ0FBQztJQUM5QyxvQkFBb0IsR0FBRyxLQUFLLENBQUMsUUFBUSxFQUFrQyxDQUFDO0lBQ3hFLEtBQUssR0FBRyxLQUFLLENBQUMsUUFBUSxFQUFjLENBQUM7SUFFOUMsaUJB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L0QsU0FBUzttQkFBQztvQkFDVCw4REFBOEQ7b0JBQzlELFFBQVEsRUFBRSx1REFBdUQ7b0JBQ2pFLFVBQVUsRUFBRSxJQUFJO2lCQUNqQiIsInNvdXJjZXNDb250ZW50IjpbIi8qIGVzbGludC1kaXNhYmxlIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnkgKi9cbmltcG9ydCB7IEFmdGVyVmlld0luaXQsIERpcmVjdGl2ZSwgRWxlbWVudFJlZiwgT25EZXN0cm95LCBlZmZlY3QsIGluamVjdCwgaW5wdXQsIG91dHB1dCwgc2lnbmFsLCB1bnRyYWNrZWQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IGNsb25lRGVlcCwgc2V0IH0gZnJvbSAnQGxpYnMtdWkvdXRpbHMnO1xuaW1wb3J0IHsgU3ViamVjdCwgZnJvbUV2ZW50LCB0YWtlVW50aWwgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IElTY3JvbGxNZWFzdXJlSXRlbURpcmVjdGlvbkhvcml6b250YWxGdW5jdGlvbnNDb250cm9sIH0gZnJvbSAnLi9pbnRlcmZhY2VzL2RpcmVjdGlvbi1ob3Jpem9udGFsLmludGVyZmFjZSc7XG5ARGlyZWN0aXZlKHtcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEBhbmd1bGFyLWVzbGludC9kaXJlY3RpdmUtc2VsZWN0b3JcbiAgc2VsZWN0b3I6ICdbTGlic1VpU2Nyb2xsTWVhc3VyZUl0ZW1EaXJlY3Rpb25Ib3Jpem9udGFsRGlyZWN0aXZlXScsXG4gIHN0YW5kYWxvbmU6IHRydWUsXG59KVxuZXhwb3J0IGNsYXNzIExpYnNVaVNjcm9sbE1lYXN1cmVJdGVtRGlyZWN0aW9uSG9yaXpvbnRhbERpcmVjdGl2ZSBpbXBsZW1lbnRzIEFmdGVyVmlld0luaXQsIE9uRGVzdHJveSB7XG4gIC8vICNyZWdpb24gUFJPUEVSVFlcbiAgcHVibGljIHZpZXdQb3J0SXRlbXMgPSBzaWduYWw8QXJyYXk8UmVjb3JkPHN0cmluZywgYW55Pj4+KFtdKTtcbiAgcHJpdmF0ZSBzdGFydCA9IHNpZ25hbDxudW1iZXI+KDApO1xuICBwcml2YXRlIGVuZCA9IHNpZ25hbDxudW1iZXI+KDApO1xuICBwcml2YXRlIHN0b3JlSXRlbXMgPSBzaWduYWw8QXJyYXk8UmVjb3JkPHN0cmluZywgYW55Pj4+KFtdKTtcbiAgcHJpdmF0ZSBkaXZWaXJ0dWFsID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gIHByaXZhdGUgb25EZXN0cm95ID0gbmV3IFN1YmplY3Q8dm9pZD4oKTtcblxuICAvLyAjcmVnaW9uIElOUFVUXG4gIHJlYWRvbmx5IGVsZW1lbnRTY3JvbGwgPSBpbnB1dC5yZXF1aXJlZDxIVE1MRWxlbWVudD4oKTtcbiAgcmVhZG9ubHkgZnVuY3Rpb25HZXRXaWR0aEl0ZW0gPSBpbnB1dC5yZXF1aXJlZDwoaXRlbTogYW55KSA9PiBQcm9taXNlPG51bWJlcj4+KCk7XG4gIHJlYWRvbmx5IGl0ZW1zID0gaW5wdXQucmVxdWlyZWQ8QXJyYXk8YW55Pj4oKTtcblxuICAvLyAjcmVnaW9uIE9VVFBVVFxuICByZWFkb25seSBvdXRWaWV3UG9ydEl0ZW0gPSBvdXRwdXQ8QXJyYXk8UmVjb3JkPHN0cmluZywgYW55Pj4+KCk7XG4gIHJlYWRvbmx5IG91dEZ1bmN0aW9uQ29udHJvbCA9IG91dHB1dDxJU2Nyb2xsTWVhc3VyZUl0ZW1EaXJlY3Rpb25Ib3Jpem9udGFsRnVuY3Rpb25zQ29udHJvbD4oKTtcbiAgcmVhZG9ubHkgb3V0RGl2VmlydHVhbCA9IG91dHB1dDxIVE1MRGl2RWxlbWVudD4oKTtcblxuICAvLyAjcmVnaW9uIElOSkVDVFxuICBwcml2YXRlIGVsZW1lbnRSZWYgPSBpbmplY3QoRWxlbWVudFJlZik7XG5cbiAgY29uc3RydWN0b3IoKSB7XG4gICAgZWZmZWN0KCgpID0+IHtcbiAgICAgIGlmICghdW50cmFja2VkKCgpID0+IHRoaXMuRWxlbWVudFNjcm9sbCkgfHwgIXVudHJhY2tlZCgoKSA9PiB0aGlzLmZ1bmN0aW9uR2V0V2lkdGhJdGVtKCkpKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGl0ZW1zID0gdGhpcy5pdGVtcygpO1xuICAgICAgdW50cmFja2VkKGFzeW5jICgpID0+IHtcbiAgICAgICAgdGhpcy5zdGFydC5zZXQoMCk7XG4gICAgICAgIGNvbnN0IHJlY3QgPSB0aGlzLkVsZW1lbnRTY3JvbGwuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgICAgIGxldCB3aWR0aCA9IDA7XG4gICAgICAgIGNvbnN0IG5ld0l0ZW1zID0gW107XG4gICAgICAgIGZvciAoY29uc3QgaW5kZXggaW4gaXRlbXMpIHtcbiAgICAgICAgICBjb25zdCBpdGVtID0gaXRlbXNbaW5kZXhdO1xuICAgICAgICAgIGNvbnN0IG5ld0l0ZW0gPSBjbG9uZURlZXAoaXRlbSk7XG4gICAgICAgICAgbmV3SXRlbS5yZWYgPSBpdGVtO1xuICAgICAgICAgIGNvbnN0IGl0ZW1XaWR0aCA9IGF3YWl0IHRoaXMuZnVuY3Rpb25HZXRXaWR0aEl0ZW0oKShpdGVtKTtcbiAgICAgICAgICBuZXdJdGVtLml0ZW1XaWR0aCA9IGl0ZW1XaWR0aDtcbiAgICAgICAgICBuZXdJdGVtLnN0YXJ0ID0gd2lkdGg7XG4gICAgICAgICAgd2lkdGggKz0gaXRlbVdpZHRoO1xuICAgICAgICAgIG5ld0l0ZW0uZW5kID0gd2lkdGg7XG4gICAgICAgICAgaWYgKG5ld0l0ZW0uZW5kIDw9IHJlY3Qud2lkdGggKyB0aGlzLkVsZW1lbnRTY3JvbGwuc2Nyb2xsTGVmdCkge1xuICAgICAgICAgICAgdGhpcy5lbmQuc2V0KCtpbmRleCk7XG4gICAgICAgICAgfVxuICAgICAgICAgIG5ld0l0ZW1zLnB1c2gobmV3SXRlbSk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5zdG9yZUl0ZW1zLnNldChuZXdJdGVtcyk7XG4gICAgICAgIHRoaXMudmlld1BvcnRJdGVtcy5zZXQoXG4gICAgICAgICAgdGhpcy5zdG9yZUl0ZW1zKClcbiAgICAgICAgICAgIC5zbGljZSh0aGlzLnN0YXJ0KCksIHRoaXMuZW5kKCkgKyAzKVxuICAgICAgICAgICAgLm1hcCgoaXRlbSkgPT4gaXRlbVsncmVmJ10pXG4gICAgICAgICk7XG4gICAgICAgIHRoaXMub3V0Vmlld1BvcnRJdGVtLmVtaXQodGhpcy52aWV3UG9ydEl0ZW1zKCkpO1xuXG4gICAgICAgIHRoaXMuZGl2VmlydHVhbC5zdHlsZS5wb3NpdGlvbiA9ICdhYnNvbHV0ZSc7XG4gICAgICAgIHRoaXMuZGl2VmlydHVhbC5zdHlsZS53aWR0aCA9IGAke3dpZHRofXB4YDtcbiAgICAgICAgdGhpcy5kaXZWaXJ0dWFsLnN0eWxlLmhlaWdodCA9ICcxcHgnO1xuICAgICAgICB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5jbGFzc0xpc3QuYWRkKCdyZWxhdGl2ZScpO1xuICAgICAgICB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5jbGFzc0xpc3QucmVtb3ZlKCd3LWZ1bGwnKTtcbiAgICAgICAgdGhpcy5lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuc3R5bGUucGFkZGluZ0xlZnQgPSBgMHB4YDtcbiAgICAgICAgdGhpcy5FbGVtZW50U2Nyb2xsLmFwcGVuZCh0aGlzLmRpdlZpcnR1YWwpO1xuICAgICAgICB0aGlzLm91dERpdlZpcnR1YWwuZW1pdCh0aGlzLmRpdlZpcnR1YWwpO1xuICAgICAgfSk7XG4gICAgfSk7XG4gIH1cblxuICBuZ0FmdGVyVmlld0luaXQoKSB7XG4gICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICBmcm9tRXZlbnQ8RXZlbnQ+KHRoaXMuRWxlbWVudFNjcm9sbCwgJ3Njcm9sbCcpLnBpcGUodGFrZVVudGlsKHRoaXMub25EZXN0cm95KSkuc3Vic2NyaWJlKHRoaXMuaGFuZGxlclNjcm9sbC5iaW5kKHRoaXMpKTtcbiAgICAgIHRoaXMub3V0RnVuY3Rpb25Db250cm9sLmVtaXQodGhpcy5GdW5jdGlvbnNDb250cm9sKTtcbiAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgZ2V0IEVsZW1lbnRTY3JvbGwoKSB7XG4gICAgcmV0dXJuIHRoaXMuZWxlbWVudFNjcm9sbCgpIHx8IHRoaXMuZWxlbWVudFJlZi5uYXRpdmVFbGVtZW50O1xuICB9XG5cbiAgLyogRlVOQ1RJT05TICovXG4gIHB1YmxpYyBnZXQgRnVuY3Rpb25zQ29udHJvbCgpOiBJU2Nyb2xsTWVhc3VyZUl0ZW1EaXJlY3Rpb25Ib3Jpem9udGFsRnVuY3Rpb25zQ29udHJvbCB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHNjcm9sbEludG86IHRoaXMuc2Nyb2xsSW50by5iaW5kKHRoaXMpLFxuICAgICAgc2Nyb2xsVG9Qb3NpdGlvbjogdGhpcy5zY3JvbGxUb1Bvc2l0aW9uLmJpbmQodGhpcyksXG4gICAgICBzY3JvbGxUb0luZGV4OiB0aGlzLnNjcm9sbFRvSW5kZXguYmluZCh0aGlzKSxcbiAgICAgIHJlQ2FsY3VsYXRvclZpZXdQb3J0OiB0aGlzLnJlQ2FsY3VsYXRvclZpZXdQb3J0LmJpbmQodGhpcyksXG4gICAgfTtcbiAgfVxuICBwcml2YXRlIGhhbmRsZXJTY3JvbGwoKSB7XG4gICAgaWYgKCF0aGlzLnN0b3JlSXRlbXMoKT8ubGVuZ3RoKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHJlY3QgPSB0aGlzLkVsZW1lbnRTY3JvbGwuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG4gICAgbGV0IHN0YXJ0ID0gdGhpcy5zdGFydCgpO1xuICAgIGxldCBpc1NldFN0YXJ0UG9zaXRpb25lZCA9IGZhbHNlO1xuXG4gICAgZm9yIChjb25zdCBpbmRleCBpbiB0aGlzLnN0b3JlSXRlbXMoKSkge1xuICAgICAgY29uc3QgaXRlbSA9IHRoaXMuc3RvcmVJdGVtcygpW2luZGV4XTtcbiAgICAgIGlmIChpdGVtWydlbmQnXSA+IHJlY3Qud2lkdGggKyB0aGlzLkVsZW1lbnRTY3JvbGwuc2Nyb2xsTGVmdCkge1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIHRoaXMuZW5kLnNldCgraW5kZXgpO1xuICAgICAgaWYgKGl0ZW1bJ2VuZCddID49IHRoaXMuRWxlbWVudFNjcm9sbC5zY3JvbGxMZWZ0ICYmICFpc1NldFN0YXJ0UG9zaXRpb25lZCkge1xuICAgICAgICBzdGFydCA9ICtpbmRleDtcbiAgICAgICAgaXNTZXRTdGFydFBvc2l0aW9uZWQgPSB0cnVlO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChzdGFydCAhPT0gdGhpcy5zdGFydCgpKSB7XG4gICAgICB0aGlzLnN0YXJ0LnNldChzdGFydCk7XG4gICAgICB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zdHlsZS5wYWRkaW5nTGVmdCA9IGAke3RoaXMuc3RvcmVJdGVtcygpW3N0YXJ0XVsnc3RhcnQnXX1weGA7XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuc3RhcnQoKSA8IDApIHtcbiAgICAgIHRoaXMuc3RhcnQuc2V0KDApO1xuICAgIH1cbiAgICB0aGlzLnZpZXdQb3J0SXRlbXMuc2V0KFxuICAgICAgdGhpcy5zdG9yZUl0ZW1zKClcbiAgICAgICAgLnNsaWNlKHRoaXMuc3RhcnQoKSwgdGhpcy5lbmQoKSArIDMpXG4gICAgICAgIC5tYXAoKGl0ZW0pID0+IGl0ZW1bJ3JlZiddKVxuICAgICk7XG4gICAgdGhpcy5vdXRWaWV3UG9ydEl0ZW0uZW1pdCh0aGlzLnZpZXdQb3J0SXRlbXMoKSk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHNjcm9sbEludG8oaXRlbVNjcm9sbDogYW55KSB7XG4gICAgY29uc3QgaXRlbUZvdW5kID0gdGhpcy5zdG9yZUl0ZW1zKCkuZmluZCgoaXRlbSkgPT4gaXRlbVsncmVmJ10gPT09IGl0ZW1TY3JvbGwpO1xuXG4gICAgaWYgKGl0ZW1Gb3VuZCkge1xuICAgICAgdGhpcy5FbGVtZW50U2Nyb2xsLnNjcm9sbExlZnQgPSBpdGVtRm91bmRbJ3N0YXJ0J107XG4gICAgfVxuICB9XG4gIHByaXZhdGUgYXN5bmMgc2Nyb2xsVG9Qb3NpdGlvbihwb3NpdGlvbjogbnVtYmVyKSB7XG4gICAgdGhpcy5FbGVtZW50U2Nyb2xsLnNjcm9sbExlZnQgPSBwb3NpdGlvbjtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgc2Nyb2xsVG9JbmRleChpbmRleDogbnVtYmVyKSB7XG4gICAgY29uc3QgaXRlbU9mSW5kZXggPSB0aGlzLnN0b3JlSXRlbXMoKVtpbmRleF07XG5cbiAgICBpZiAoaXRlbU9mSW5kZXgpIHtcbiAgICAgIHRoaXMuRWxlbWVudFNjcm9sbC5zY3JvbGxMZWZ0ID0gaXRlbU9mSW5kZXhbJ3N0YXJ0J107XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZUNhbGN1bGF0b3JWaWV3UG9ydCgpIHtcbiAgICBsZXQgd2lkdGggPSAwO1xuXG4gICAgdGhpcy5zdG9yZUl0ZW1zLnVwZGF0ZSgoaXRlbXMpID0+IHtcbiAgICAgIGl0ZW1zLmZvckVhY2goYXN5bmMgKGl0ZW0pID0+IHtcbiAgICAgICAgY29uc3QgaXRlbVdpZHRoID0gYXdhaXQgdGhpcy5mdW5jdGlvbkdldFdpZHRoSXRlbSgpKGl0ZW1bJ3JlZiddKTtcblxuICAgICAgICBzZXQoaXRlbSwgJ2l0ZW1XaWR0aCcsIGl0ZW1XaWR0aCk7XG4gICAgICAgIHNldChpdGVtLCAnc3RhcnQnLCB3aWR0aCk7XG4gICAgICAgIHdpZHRoICs9IGl0ZW1XaWR0aDtcbiAgICAgICAgc2V0KGl0ZW0sICdlbmQnLCB3aWR0aCk7XG4gICAgICB9KTtcbiAgICAgIHJldHVybiBbLi4uaXRlbXNdO1xuICAgIH0pO1xuICAgIHRoaXMuZGl2VmlydHVhbC5zdHlsZS53aWR0aCA9IGAke3dpZHRofXB4YDtcbiAgICB0aGlzLmhhbmRsZXJTY3JvbGwoKTtcbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuZGl2VmlydHVhbC5yZW1vdmUoKTtcbiAgICB0aGlzLm9uRGVzdHJveS5uZXh0KCk7XG4gICAgdGhpcy5vbkRlc3Ryb3kuY29tcGxldGUoKTtcbiAgfVxufVxuIl19
206
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlyZWN0aW9uLWhvcml6b250YWwuZGlyZWN0aXZlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3Njcm9sbC1tZWFzdXJlLWl0ZW1zL2RpcmVjdGlvbi1ob3Jpem9udGFsL3NyYy9kaXJlY3Rpb24taG9yaXpvbnRhbC5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEseUZBQXlGO0FBQ3pGLHVEQUF1RDtBQUN2RCxPQUFPLEVBQWlCLFVBQVUsRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25JLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ2hFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxNQUFNLENBQUM7O0FBT2pDLE1BQU0sT0FBTyxtREFBbUQ7SUFDOUQsbUJBQW1CO0lBQ25CLDhFQUE4RTtJQUM3RCxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBRWhCLGFBQWEsR0FBRyxNQUFNLENBQWEsRUFBRSxDQUFDLENBQUM7SUFDdkMsS0FBSyxHQUFHLE1BQU0sQ0FBUyxDQUFDLENBQUMsQ0FBQztJQUMxQixHQUFHLEdBQUcsTUFBTSxDQUFTLENBQUMsQ0FBQyxDQUFDO0lBQ3hCLFVBQVUsR0FBRyxNQUFNLENBQXdDLEVBQUUsQ0FBQyxDQUFDO0lBQy9ELFVBQVUsR0FBRyxRQUFRLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3BELGNBQWMsQ0FBa0I7SUFDeEMsOEZBQThGO0lBQ3RGLGtCQUFrQixDQUFVO0lBQ3BDLGlHQUFpRztJQUN6RixjQUFjLEdBQUcsQ0FBQyxDQUFDO0lBQzNCLDBGQUEwRjtJQUNsRixxQkFBcUIsR0FBRyxLQUFLLENBQUM7SUFFdEMsZ0JBQWdCO0lBQ1AsYUFB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QUFTLENBQUM7WUFDM0IsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7WUFDbkIsS0FBSyxJQUFJLFNBQVMsQ0FBQztZQUNuQixJQUFJLENBQUMsR0FBRyxHQUFHLEtBQUssQ0FBQztRQUNuQixDQUFDO1FBRUQsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDaEMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLEdBQUcsS0FBSyxJQUFJLENBQUM7UUFDM0MsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7d0dBbk5VLG1EQUFtRDs0RkFBbkQsbURBQW1EOzs0RkFBbkQsbURBQW1EO2tCQUwvRCxTQUFTO21CQUFDO29CQUNULDhEQUE4RDtvQkFDOUQsUUFBUSxFQUFFLHVEQUF1RDtvQkFDakUsVUFBVSxFQUFFLElBQUk7aUJBQ2pCIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ09OVkVOVElPTi1FWENFUFQ6IGFueSDigJQgR2VuZXJpYyBkaXJlY3RpdmUgd29ya3Mgd2l0aCB1bmtub3duIGl0ZW0gdHlwZXMgZnJvbSBjb25zdW1lclxuLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuaW1wb3J0IHsgQWZ0ZXJWaWV3SW5pdCwgRGVzdHJveVJlZiwgRGlyZWN0aXZlLCBFbGVtZW50UmVmLCBlZmZlY3QsIGluamVjdCwgaW5wdXQsIG91dHB1dCwgc2lnbmFsLCB1bnRyYWNrZWQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IHRha2VVbnRpbERlc3Ryb3llZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUvcnhqcy1pbnRlcm9wJztcbmltcG9ydCB7IGZyb21FdmVudCB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgSVNjcm9sbE1lYXN1cmVJdGVtRGlyZWN0aW9uSG9yaXpvbnRhbEZ1bmN0aW9uc0NvbnRyb2wsIElTY3JvbGxNZWFzdXJlU3RvcmVJdGVtQ29udmVydCB9IGZyb20gJy4vaW50ZXJmYWNlcy9kaXJlY3Rpb24taG9yaXpvbnRhbC5pbnRlcmZhY2UnO1xuQERpcmVjdGl2ZSh7XG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAYW5ndWxhci1lc2xpbnQvZGlyZWN0aXZlLXNlbGVjdG9yXG4gIHNlbGVjdG9yOiAnW0xpYnNVaVNjcm9sbE1lYXN1cmVJdGVtRGlyZWN0aW9uSG9yaXpvbnRhbERpcmVjdGl2ZV0nLFxuICBzdGFuZGFsb25lOiB0cnVlLFxufSlcbmV4cG9ydCBjbGFzcyBMaWJzVWlTY3JvbGxNZWFzdXJlSXRlbURpcmVjdGlvbkhvcml6b250YWxEaXJlY3RpdmUgaW1wbGVtZW50cyBBZnRlclZpZXdJbml0IHtcbiAgLy8gI3JlZ2lvbiBQUk9QRVJUWVxuICAvKiogU+G7kSBpdGVtIGJ1ZmZlciByZW5kZXIgdGjDqm0gc2F1IHZpZXdwb3J0IMSR4buDIHRyw6FuaCB0cuG6r25nIHbDuW5nIGtoaSBzY3JvbGwuICovXG4gIHByaXZhdGUgcmVhZG9ubHkgYnVmZmVyQWhlYWQgPSAzO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgdmlld1BvcnRJdGVtcyA9IHNpZ25hbDxBcnJheTxhbnk+PihbXSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgc3RhcnQgPSBzaWduYWw8bnVtYmVyPigwKTtcbiAgcHJpdmF0ZSByZWFkb25seSBlbmQgPSBzaWduYWw8bnVtYmVyPigwKTtcbiAgcHJpdmF0ZSByZWFkb25seSBzdG9yZUl0ZW1zID0gc2lnbmFsPEFycmF5PElTY3JvbGxNZWFzdXJlU3RvcmVJdGVtQ29udmVydD4+KFtdKTtcbiAgcHJpdmF0ZSByZWFkb25seSBkaXZWaXJ0dWFsID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gIHByaXZhdGUgcmVzaXplT2JzZXJ2ZXI/OiBSZXNpemVPYnNlcnZlcjtcbiAgLyoqIENo4bq3biBSZXNpemVPYnNlcnZlciAvIHN1YnNjcmliZSDEkcaw4bujYyB04bqhbyB0cm9uZyBzZXRUaW1lb3V0IHNhdSBraGkgZGlyZWN0aXZlIMSRw6MgZGVzdHJveS4gKi9cbiAgcHJpdmF0ZSB0aW1lckFmdGVyVmlld0luaXQ/OiBudW1iZXI7XG4gIC8qKiBUb2tlbiBuaOG6rW4gZOG6oW5nIGzhuqduIGVmZmVjdCBjaOG6oXkgaGnhu4duIHThuqFpLiBBc3luYyDEkW8gd2lkdGggeG9uZyBz4bq9IGLhu48ga+G6v3QgcXXhuqMgbuG6v3UgdG9rZW4gxJHhu5VpLiAqL1xuICBwcml2YXRlIGV4ZWN1dGlvblRva2VuID0gMDtcbiAgLyoqIFN0eWxlICsgRE9NIGhvb2sgY+G7p2EgZGl2VmlydHVhbCBjaOG7iSBj4bqnbiBraOG7n2kgdOG6oW8gbeG7mXQgbOG6p24gY2hvIGPhuqMgdsOybmcgxJHhu51pIGRpcmVjdGl2ZS4gKi9cbiAgcHJpdmF0ZSBkaXZWaXJ0dWFsSW5pdGlhbGl6ZWQgPSBmYWxzZTtcblxuICAvLyAjcmVnaW9uIElOUFVUXG4gIHJlYWRvbmx5IGVsZW1lbnRTY3JvbGwgPSBpbnB1dC5yZXF1aXJlZDxIVE1MRWxlbWVudD4oKTtcbiAgcmVhZG9ubHkgZnVuY3Rpb25HZXRXaWR0aEl0ZW0gPSBpbnB1dC5yZXF1aXJlZDwoaXRlbTogYW55KSA9PiBQcm9taXNlPG51bWJlcj4+KCk7XG4gIHJlYWRvbmx5IGl0ZW1zID0gaW5wdXQucmVxdWlyZWQ8QXJyYXk8YW55Pj4oKTtcblxuICAvLyAjcmVnaW9uIE9VVFBVVFxuICByZWFkb25seSBvdXRWaWV3UG9ydEl0ZW0gPSBvdXRwdXQ8QXJyYXk8YW55Pj4oKTtcbiAgcmVhZG9ubHkgb3V0RnVuY3Rpb25Db250cm9sID0gb3V0cHV0PElTY3JvbGxNZWFzdXJlSXRlbURpcmVjdGlvbkhvcml6b250YWxGdW5jdGlvbnNDb250cm9sPigpO1xuICByZWFkb25seSBvdXREaXZWaXJ0dWFsID0gb3V0cHV0PEhUTUxEaXZFbGVtZW50PigpO1xuICByZWFkb25seSBvdXRQYWRkaW5nTGVmdCA9IG91dHB1dDxudW1iZXI+KCk7XG5cbiAgLy8gI3JlZ2lvbiBJTkpFQ1RcbiAgcHJpdmF0ZSByZWFkb25seSBlbGVtZW50UmVmID0gaW5qZWN0KEVsZW1lbnRSZWYpO1xuICBwcml2YXRlIHJlYWRvbmx5IGRlc3Ryb3lSZWYgPSBpbmplY3QoRGVzdHJveVJlZik7XG5cbiAgY29uc3RydWN0b3IoKSB7XG4gICAgZWZmZWN0KCgpID0+IHtcbiAgICAgIGNvbnN0IGl0ZW1zID0gdGhpcy5pdGVtcygpO1xuICAgICAgY29uc3QgbXlUb2tlbiA9ICsrdGhpcy5leGVjdXRpb25Ub2tlbjtcbiAgICAgIHVudHJhY2tlZChhc3luYyAoKSA9PiB7XG4gICAgICAgIGNvbnN0IHNjcm9sbEVsID0gdGhpcy5TY3JvbGxDb250YWluZXI7XG4gICAgICAgIGNvbnN0IGZ1bmN0aW9uR2V0V2lkdGhJdGVtID0gdGhpcy5mdW5jdGlvbkdldFdpZHRoSXRlbSgpO1xuICAgICAgICBpZiAoIXNjcm9sbEVsIHx8ICFmdW5jdGlvbkdldFdpZHRoSXRlbSkge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIMSQbyB3aWR0aCB04bqldCBj4bqjIGl0ZW0gc29uZyBzb25nIOKAlCByw7p0IG5n4bqvbiB0aOG7nWkgZ2lhbiBjaG8gbGlzdCBs4bubbiAodmQgMzAwMCBj4buZdCkuXG4gICAgICAgIGNvbnN0IHdpZHRocyA9IGF3YWl0IFByb21pc2UuYWxsKGl0ZW1zLm1hcCgoaXQpID0+IGZ1bmN0aW9uR2V0V2lkdGhJdGVtKGl0KSkpO1xuICAgICAgICAvLyBC4buLIGh14bu3IHbDrCBjw7MgbMaw4bujdCBpdGVtcygpIG3hu5tpIGNo4bqheSBzYXUg4oCUIGLhu48ga+G6v3QgcXXhuqMgY8WpIHRyw6FuaCDEkcOoIHN0YXRlLlxuICAgICAgICBpZiAobXlUb2tlbiAhPT0gdGhpcy5leGVjdXRpb25Ub2tlbikge1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc3RhcnQuc2V0KDApO1xuICAgICAgICBjb25zdCBzY3JvbGxMZWZ0ID0gc2Nyb2xsRWwuc2Nyb2xsTGVmdDtcbiAgICAgICAgY29uc3QgY29udGFpbmVyV2lkdGggPSBzY3JvbGxFbC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS53aWR0aDtcbiAgICAgICAgbGV0IHdpZHRoID0gMDtcbiAgICAgICAgbGV0IGVuZExvY2FsID0gMDtcbiAgICAgICAgY29uc3QgbmV3SXRlbXM6IEFycmF5PElTY3JvbGxNZWFzdXJlU3RvcmVJdGVtQ29udmVydD4gPSBbXTtcblxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGl0ZW1zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgY29uc3QgaXRlbVdpZHRoID0gd2lkdGhzW2ldO1xuICAgICAgICAgIGNvbnN0IG5ld0l0ZW06IElTY3JvbGxNZWFzdXJlU3RvcmVJdGVtQ29udmVydCA9IHsgcmVmOiBpdGVtc1tpXSwgaXRlbVdpZHRoLCBzdGFydDogd2lkdGgsIGVuZDogMCB9O1xuICAgICAgICAgIHdpZHRoICs9IGl0ZW1XaWR0aDtcbiAgICAgICAgICBuZXdJdGVtLmVuZCA9IHdpZHRoO1xuICAgICAgICAgIGlmIChuZXdJdGVtLmVuZCA8PSBjb250YWluZXJXaWR0aCArIHNjcm9sbExlZnQpIHtcbiAgICAgICAgICAgIGVuZExvY2FsID0gaTtcbiAgICAgICAgICB9XG4gICAgICAgICAgbmV3SXRlbXMucHVzaChuZXdJdGVtKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBTZXQgZW5kIG3hu5l0IGzhuqduIHRoYXkgdsOsIGdoaSBt4buXaSB2w7JuZyBs4bq3cCDigJQgdHLDoW5oIG5vdGlmeSBjb25zdW1lciBOIGzhuqduLlxuICAgICAgICB0aGlzLmVuZC5zZXQoZW5kTG9jYWwpO1xuICAgICAgICB0aGlzLnN0b3JlSXRlbXMuc2V0KG5ld0l0ZW1zKTtcblxuICAgICAgICAvLyBLaOG7n2kgdOG6oW8gc3R5bGUgKyBET00gaG9vayBj4bunYSBkaXZWaXJ0dWFsIMSRw7puZyBt4buZdCBs4bqnbi5cbiAgICAgICAgaWYgKCF0aGlzLmRpdlZpcnR1YWxJbml0aWFsaXplZCkge1xuICAgICAgICAgIHRoaXMuZGl2VmlydHVhbC5zdHlsZS5wb3NpdGlvbiA9ICdhYnNvbHV0ZSc7XG4gICAgICAgICAgdGhpcy5kaXZWaXJ0dWFsLnN0eWxlLnRvcCA9ICcwcHgnO1xuICAgICAgICAgIHRoaXMuZGl2VmlydHVhbC5zdHlsZS5sZWZ0ID0gJzBweCc7XG4gICAgICAgICAgdGhpcy5kaXZWaXJ0dWFsLnN0eWxlLmhlaWdodCA9ICcxcHgnO1xuICAgICAgICAgIHRoaXMuZGl2VmlydHVhbC5zdHlsZS52aXNpYmlsaXR5ID0gJ2hpZGRlbic7XG4gICAgICAgICAgdGhpcy5lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0LmFkZCgncmVsYXRpdmUnKTtcbiAgICAgICAgICB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5jbGFzc0xpc3QucmVtb3ZlKCd3LWZ1bGwnKTtcbiAgICAgICAgICB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zdHlsZS5wYWRkaW5nTGVmdCA9ICcwcHgnO1xuICAgICAgICAgIHNjcm9sbEVsLmFwcGVuZCh0aGlzLmRpdlZpcnR1YWwpO1xuICAgICAgICAgIHRoaXMuZGl2VmlydHVhbEluaXRpYWxpemVkID0gdHJ1ZTtcbiAgICAgICAgICB0aGlzLm91dERpdlZpcnR1YWwuZW1pdCh0aGlzLmRpdlZpcnR1YWwpO1xuICAgICAgICB9XG4gICAgICAgIC8vIFdpZHRoIHThu5VuZyB0aOG7gyDEkeG7lWkgdGhlbyBkYXRhc2V0IOKGkiBj4bqnbiB1cGRhdGUgbeG7l2kgbMaw4bujdC5cbiAgICAgICAgdGhpcy5kaXZWaXJ0dWFsLnN0eWxlLndpZHRoID0gYCR7d2lkdGh9cHhgO1xuXG4gICAgICAgIC8vIGNvbnRhaW5lcldpZHRoIGzDumMgxJHhuqd1IGPDsyB0aOG7gyA9IDAgKERPTSBjaMawYSBsYXlvdXQpLCBraGnhur9uIGVuZCBnaeG7r1xuICAgICAgICAvLyBuZ3V5w6puIDAgdsOgIGNo4buJIHJlbmRlciAzIGPhu5l0IMSR4bqndS4gUmUtxJFvIGLhurFuZyBoYW5kbGVyU2Nyb2xsIHNhdSBraGkgdsOybmdcbiAgICAgICAgLy8gbOG6t3AgYXdhaXQgeG9uZyAobGF5b3V0IHRoxrDhu51uZyDEkcOjIGhvw6BuIHThuqV0KSDEkeG7gyBlbmQgcmEgxJHDum5nIGdpw6EgdHLhu4suXG4gICAgICAgIHRoaXMuaGFuZGxlclNjcm9sbCgpO1xuICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICB0aGlzLmRlc3Ryb3lSZWYub25EZXN0cm95KCgpID0+IHtcbiAgICAgIGNsZWFyVGltZW91dCh0aGlzLnRpbWVyQWZ0ZXJWaWV3SW5pdCk7XG4gICAgICB0aGlzLmRpdlZpcnR1YWwucmVtb3ZlKCk7XG4gICAgICB0aGlzLnJlc2l6ZU9ic2VydmVyPy5kaXNjb25uZWN0KCk7XG4gICAgfSk7XG4gIH1cblxuICBuZ0FmdGVyVmlld0luaXQoKSB7XG4gICAgdGhpcy50aW1lckFmdGVyVmlld0luaXQgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIGZyb21FdmVudDxFdmVudD4odGhpcy5TY3JvbGxDb250YWluZXIsICdzY3JvbGwnKVxuICAgICAgICAucGlwZSh0YWtlVW50aWxEZXN0cm95ZWQodGhpcy5kZXN0cm95UmVmKSlcbiAgICAgICAgLnN1YnNjcmliZSgoKSA9PiB0aGlzLmhhbmRsZXJTY3JvbGwoKSk7XG4gICAgICB0aGlzLm91dEZ1bmN0aW9uQ29udHJvbC5lbWl0KHRoaXMuRnVuY3Rpb25zQ29udHJvbCk7XG5cbiAgICAgIC8vIFJlLXTDrW5oIHZpZXdwb3J0IHNhdSBraGkgY29udGFpbmVyIGPDsyBrw61jaCB0aMaw4bubYyB0aOG6rXQuXG4gICAgICAvLyBM4bqnbiDEkeG6p3UgcmVuZGVyLCBnZXRCb3VuZGluZ0NsaWVudFJlY3QoKS53aWR0aCBjw7MgdGjhu4MgPSAwIG7Dqm4gZW5kIGdp4buvIGdpw6EgdHLhu4sgMCxcbiAgICAgIC8vIGNo4buJIGhp4buDbiB0aOG7iyAzIGPhu5l0IMSR4bqndSAoZG8gc2xpY2Uoc3RhcnQsIGVuZCArIGJ1ZmZlckFoZWFkKSkuIFJlc2l6ZU9ic2VydmVyIGZpcmUgbmdheSBraGlcbiAgICAgIC8vIG9ic2VydmUgdsOgIG3hu5dpIGzhuqduIGNvbnRhaW5lciDEkeG7lWkgc2l6ZSBz4bq9IGvDqW8gZW5kIHbhu4EgxJHDum5nIGdpw6EgdHLhu4suXG4gICAgICB0aGlzLnJlc2l6ZU9ic2VydmVyID0gbmV3IFJlc2l6ZU9ic2VydmVyKCgpID0+IHRoaXMuaGFuZGxlclNjcm9sbCgpKTtcbiAgICAgIHRoaXMucmVzaXplT2JzZXJ2ZXIub2JzZXJ2ZSh0aGlzLlNjcm9sbENvbnRhaW5lcik7XG4gICAgfSk7XG4gIH1cblxuICAvLyAjcmVnaW9uIFBVQkxJQyBBUElcbiAgcHVibGljIGdldCBGdW5jdGlvbnNDb250cm9sKCk6IElTY3JvbGxNZWFzdXJlSXRlbURpcmVjdGlvbkhvcml6b250YWxGdW5jdGlvbnNDb250cm9sIHtcbiAgICByZXR1cm4ge1xuICAgICAgc2Nyb2xsSW50bzogdGhpcy5zY3JvbGxJbnRvLmJpbmQodGhpcyksXG4gICAgICBzY3JvbGxUb1Bvc2l0aW9uOiB0aGlzLnNjcm9sbFRvUG9zaXRpb24uYmluZCh0aGlzKSxcbiAgICAgIHNjcm9sbFRvSW5kZXg6IHRoaXMuc2Nyb2xsVG9JbmRleC5iaW5kKHRoaXMpLFxuICAgICAgcmVDYWxjdWxhdG9yVmlld1BvcnQ6IHRoaXMucmVDYWxjdWxhdG9yVmlld1BvcnQuYmluZCh0aGlzKSxcbiAgICAgIGdldFZpZXdQb3J0SXRlbXM6ICgpID0+IHRoaXMudmlld1BvcnRJdGVtcygpLFxuICAgIH07XG4gIH1cblxuICAvLyAjcmVnaW9uIFBSSVZBVEUgTUVUSE9EU1xuICBwcml2YXRlIGdldCBTY3JvbGxDb250YWluZXIoKTogSFRNTEVsZW1lbnQge1xuICAgIHJldHVybiB0aGlzLmVsZW1lbnRTY3JvbGwoKSB8fCB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudDtcbiAgfVxuXG4gIHByaXZhdGUgaGFuZGxlclNjcm9sbCgpIHtcbiAgICBjb25zdCBzdG9yZSA9IHRoaXMuc3RvcmVJdGVtcygpO1xuICAgIGlmICghc3RvcmU/Lmxlbmd0aCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBzY3JvbGxFbCA9IHRoaXMuU2Nyb2xsQ29udGFpbmVyO1xuICAgIGNvbnN0IHNjcm9sbExlZnQgPSBzY3JvbGxFbC5zY3JvbGxMZWZ0O1xuICAgIGNvbnN0IGNvbnRhaW5lcldpZHRoID0gc2Nyb2xsRWwuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkud2lkdGg7XG4gICAgbGV0IHN0YXJ0ID0gdGhpcy5zdGFydCgpO1xuICAgIGxldCBlbmRMb2NhbCA9IHRoaXMuZW5kKCk7XG4gICAgbGV0IGlzU2V0U3RhcnRQb3NpdGlvbmVkID0gZmFsc2U7XG5cbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHN0b3JlLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBpdGVtID0gc3RvcmVbaV07XG4gICAgICBpZiAoaXRlbS5lbmQgPiBjb250YWluZXJXaWR0aCArIHNjcm9sbExlZnQpIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBlbmRMb2NhbCA9IGk7XG4gICAgICBpZiAoaXRlbS5lbmQgPj0gc2Nyb2xsTGVmdCAmJiAhaXNTZXRTdGFydFBvc2l0aW9uZWQpIHtcbiAgICAgICAgc3RhcnQgPSBpO1xuICAgICAgICBpc1NldFN0YXJ0UG9zaXRpb25lZCA9IHRydWU7XG4gICAgICB9XG4gICAgfVxuICAgIGlmIChlbmRMb2NhbCAhPT0gdGhpcy5lbmQoKSkge1xuICAgICAgdGhpcy5lbmQuc2V0KGVuZExvY2FsKTtcbiAgICB9XG5cbiAgICBpZiAoc3RhcnQgIT09IHRoaXMuc3RhcnQoKSkge1xuICAgICAgdGhpcy5zdGFydC5zZXQoc3RhcnQpO1xuICAgICAgY29uc3QgcGFkZGluZ0xlZnQgPSBzdG9yZVtzdGFydF0uc3RhcnQ7XG4gICAgICB0aGlzLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5zdHlsZS5wYWRkaW5nTGVmdCA9IGAke3BhZGRpbmdMZWZ0fXB4YDtcbiAgICAgIHRoaXMub3V0UGFkZGluZ0xlZnQuZW1pdChwYWRkaW5nTGVmdCk7XG4gICAgfVxuICAgIHRoaXMudmlld1BvcnRJdGVtcy5zZXQoc3RvcmUuc2xpY2UodGhpcy5zdGFydCgpLCB0aGlzLmVuZCgpICsgdGhpcy5idWZmZXJBaGVhZCkubWFwKChpdGVtKSA9PiBpdGVtLnJlZikpO1xuICAgIHRoaXMub3V0Vmlld1BvcnRJdGVtLmVtaXQodGhpcy52aWV3UG9ydEl0ZW1zKCkpO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBzY3JvbGxJbnRvKGl0ZW1TY3JvbGw6IGFueSkge1xuICAgIGNvbnN0IGl0ZW1Gb3VuZCA9IHRoaXMuc3RvcmVJdGVtcygpLmZpbmQoKGl0ZW0pID0+IGl0ZW0ucmVmID09PSBpdGVtU2Nyb2xsKTtcbiAgICBpZiAoIWl0ZW1Gb3VuZCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLlNjcm9sbENvbnRhaW5lci5zY3JvbGxMZWZ0ID0gaXRlbUZvdW5kLnN0YXJ0O1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBzY3JvbGxUb1Bvc2l0aW9uKHBvc2l0aW9uOiBudW1iZXIpIHtcbiAgICB0aGlzLlNjcm9sbENvbnRhaW5lci5zY3JvbGxMZWZ0ID0gcG9zaXRpb247XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHNjcm9sbFRvSW5kZXgoaW5kZXg6IG51bWJlcikge1xuICAgIGNvbnN0IGl0ZW1PZkluZGV4ID0gdGhpcy5zdG9yZUl0ZW1zKClbaW5kZXhdO1xuICAgIGlmICghaXRlbU9mSW5kZXgpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5TY3JvbGxDb250YWluZXIuc2Nyb2xsTGVmdCA9IGl0ZW1PZkluZGV4LnN0YXJ0O1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZUNhbGN1bGF0b3JWaWV3UG9ydCgpIHtcbiAgICBjb25zdCBpdGVtcyA9IHRoaXMuc3RvcmVJdGVtcygpO1xuICAgIGNvbnN0IGZuID0gdGhpcy5mdW5jdGlvbkdldFdpZHRoSXRlbSgpO1xuICAgIC8vIMSQbyBzb25nIHNvbmcgxJHhu4MgcsO6dCBuZ+G6r24gdGjhu51pIGdpYW4gdGhheSB2w6wgYXdhaXQgdHXhuqduIHThu7EuXG4gICAgY29uc3Qgd2lkdGhzID0gYXdhaXQgUHJvbWlzZS5hbGwoaXRlbXMubWFwKChpdCkgPT4gZm4oaXQucmVmKSkpO1xuXG4gICAgbGV0IHdpZHRoID0gMDtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGl0ZW1zLmxlbmd0aDsgaSsrKSB7XG4gICAgICBjb25zdCBpdGVtID0gaXRlbXNbaV07XG4gICAgICBjb25zdCBpdGVtV2lkdGggPSB3aWR0aHNbaV07XG4gICAgICBpdGVtLml0ZW1XaWR0aCA9IGl0ZW1XaWR0aDtcbiAgICAgIGl0ZW0uc3RhcnQgPSB3aWR0aDtcbiAgICAgIHdpZHRoICs9IGl0ZW1XaWR0aDtcbiAgICAgIGl0ZW0uZW5kID0gd2lkdGg7XG4gICAgfVxuXG4gICAgdGhpcy5zdG9yZUl0ZW1zLnNldChbLi4uaXRlbXNdKTtcbiAgICB0aGlzLmRpdlZpcnR1YWwuc3R5bGUud2lkdGggPSBgJHt3aWR0aH1weGA7XG4gICAgdGhpcy5oYW5kbGVyU2Nyb2xsKCk7XG4gIH1cbn1cbiJdfQ==
@@ -1,2 +1,2 @@
1
1
  export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlyZWN0aW9uLWhvcml6b250YWwuaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3Njcm9sbC1tZWFzdXJlLWl0ZW1zL2RpcmVjdGlvbi1ob3Jpem9udGFsL3NyYy9pbnRlcmZhY2VzL2RpcmVjdGlvbi1ob3Jpem9udGFsLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuZXhwb3J0IGludGVyZmFjZSBJU2Nyb2xsTWVhc3VyZUl0ZW1EaXJlY3Rpb25Ib3Jpem9udGFsRnVuY3Rpb25zQ29udHJvbCB7XG4gIHNjcm9sbEludG86IChpdGVtOiBhbnkpID0+IFByb21pc2U8dm9pZD47XG4gIHNjcm9sbFRvUG9zaXRpb246IChwb3NpdGlvbjogbnVtYmVyKSA9PiBQcm9taXNlPHZvaWQ+O1xuICBzY3JvbGxUb0luZGV4OiAoaW5kZXg6IG51bWJlcikgPT4gUHJvbWlzZTx2b2lkPjtcbiAgcmVDYWxjdWxhdG9yVmlld1BvcnQ6ICgpID0+IFByb21pc2U8dm9pZD47XG59XG4iXX0=
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlyZWN0aW9uLWhvcml6b250YWwuaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL3Njcm9sbC1tZWFzdXJlLWl0ZW1zL2RpcmVjdGlvbi1ob3Jpem9udGFsL3NyYy9pbnRlcmZhY2VzL2RpcmVjdGlvbi1ob3Jpem9udGFsLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueSAqL1xuZXhwb3J0IGludGVyZmFjZSBJU2Nyb2xsTWVhc3VyZUl0ZW1EaXJlY3Rpb25Ib3Jpem9udGFsRnVuY3Rpb25zQ29udHJvbCB7XG4gIHNjcm9sbEludG86IChpdGVtOiBhbnkpID0+IFByb21pc2U8dm9pZD47XG4gIHNjcm9sbFRvUG9zaXRpb246IChwb3NpdGlvbjogbnVtYmVyKSA9PiBQcm9taXNlPHZvaWQ+O1xuICBzY3JvbGxUb0luZGV4OiAoaW5kZXg6IG51bWJlcikgPT4gUHJvbWlzZTx2b2lkPjtcbiAgcmVDYWxjdWxhdG9yVmlld1BvcnQ6ICgpID0+IFByb21pc2U8dm9pZD47XG4gIGdldFZpZXdQb3J0SXRlbXM6ICgpID0+IEFycmF5PGFueT47XG59XG5leHBvcnQgaW50ZXJmYWNlIElTY3JvbGxNZWFzdXJlU3RvcmVJdGVtQ29udmVydCB7XG4gIHJlZjogYW55O1xuICBpdGVtV2lkdGg6IG51bWJlcjtcbiAgc3RhcnQ6IG51bWJlcjtcbiAgZW5kOiBudW1iZXI7XG59XG4iXX0=
@@ -1,17 +1,26 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, input, output, inject, ElementRef, effect, untracked, Directive } from '@angular/core';
3
- import { cloneDeep, set } from '@libs-ui/utils';
4
- import { Subject, fromEvent, takeUntil } from 'rxjs';
2
+ import { signal, input, output, inject, ElementRef, DestroyRef, effect, untracked, Directive } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { fromEvent } from 'rxjs';
5
5
 
6
+ // CONVENTION-EXCEPT: any — Generic directive works with unknown item types from consumer
6
7
  /* eslint-disable @typescript-eslint/no-explicit-any */
7
8
  class LibsUiScrollMeasureItemDirectionHorizontalDirective {
8
9
  // #region PROPERTY
10
+ /** Số item buffer render thêm sau viewport để tránh trắng vùng khi scroll. */
11
+ bufferAhead = 3;
9
12
  viewPortItems = signal([]);
10
13
  start = signal(0);
11
14
  end = signal(0);
12
15
  storeItems = signal([]);
13
16
  divVirtual = document.createElement('div');
14
- onDestroy = new Subject();
17
+ resizeObserver;
18
+ /** Chặn ResizeObserver / subscribe được tạo trong setTimeout sau khi directive đã destroy. */
19
+ timerAfterViewInit;
20
+ /** 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. */
21
+ executionToken = 0;
22
+ /** Style + DOM hook của divVirtual chỉ cần khởi tạo một lần cho cả vòng đời directive. */
23
+ divVirtualInitialized = false;
15
24
  // #region INPUT
16
25
  elementScroll = input.required();
17
26
  functionGetWidthItem = input.required();
@@ -20,134 +29,172 @@ class LibsUiScrollMeasureItemDirectionHorizontalDirective {
20
29
  outViewPortItem = output();
21
30
  outFunctionControl = output();
22
31
  outDivVirtual = output();
32
+ outPaddingLeft = output();
23
33
  // #region INJECT
24
34
  elementRef = inject(ElementRef);
35
+ destroyRef = inject(DestroyRef);
25
36
  constructor() {
26
37
  effect(() => {
27
- if (!untracked(() => this.ElementScroll) || !untracked(() => this.functionGetWidthItem())) {
28
- return;
29
- }
30
38
  const items = this.items();
39
+ const myToken = ++this.executionToken;
31
40
  untracked(async () => {
41
+ const scrollEl = this.ScrollContainer;
42
+ const functionGetWidthItem = this.functionGetWidthItem();
43
+ if (!scrollEl || !functionGetWidthItem) {
44
+ return;
45
+ }
46
+ // Đo width tất cả item song song — rút ngắn thời gian cho list lớn (vd 3000 cột).
47
+ const widths = await Promise.all(items.map((it) => functionGetWidthItem(it)));
48
+ // Bị huỷ vì có lượt items() mới chạy sau — bỏ kết quả cũ tránh đè state.
49
+ if (myToken !== this.executionToken) {
50
+ return;
51
+ }
32
52
  this.start.set(0);
33
- const rect = this.ElementScroll.getBoundingClientRect();
53
+ const scrollLeft = scrollEl.scrollLeft;
54
+ const containerWidth = scrollEl.getBoundingClientRect().width;
34
55
  let width = 0;
56
+ let endLocal = 0;
35
57
  const newItems = [];
36
- for (const index in items) {
37
- const item = items[index];
38
- const newItem = cloneDeep(item);
39
- newItem.ref = item;
40
- const itemWidth = await this.functionGetWidthItem()(item);
41
- newItem.itemWidth = itemWidth;
42
- newItem.start = width;
58
+ for (let i = 0; i < items.length; i++) {
59
+ const itemWidth = widths[i];
60
+ const newItem = { ref: items[i], itemWidth, start: width, end: 0 };
43
61
  width += itemWidth;
44
62
  newItem.end = width;
45
- if (newItem.end <= rect.width + this.ElementScroll.scrollLeft) {
46
- this.end.set(+index);
63
+ if (newItem.end <= containerWidth + scrollLeft) {
64
+ endLocal = i;
47
65
  }
48
66
  newItems.push(newItem);
49
67
  }
68
+ // Set end một lần thay vì ghi mỗi vòng lặp — tránh notify consumer N lần.
69
+ this.end.set(endLocal);
50
70
  this.storeItems.set(newItems);
51
- this.viewPortItems.set(this.storeItems()
52
- .slice(this.start(), this.end() + 3)
53
- .map((item) => item['ref']));
54
- this.outViewPortItem.emit(this.viewPortItems());
55
- this.divVirtual.style.position = 'absolute';
71
+ // Khởi tạo style + DOM hook của divVirtual đúng một lần.
72
+ if (!this.divVirtualInitialized) {
73
+ this.divVirtual.style.position = 'absolute';
74
+ this.divVirtual.style.top = '0px';
75
+ this.divVirtual.style.left = '0px';
76
+ this.divVirtual.style.height = '1px';
77
+ this.divVirtual.style.visibility = 'hidden';
78
+ this.elementRef.nativeElement.classList.add('relative');
79
+ this.elementRef.nativeElement.classList.remove('w-full');
80
+ this.elementRef.nativeElement.style.paddingLeft = '0px';
81
+ scrollEl.append(this.divVirtual);
82
+ this.divVirtualInitialized = true;
83
+ this.outDivVirtual.emit(this.divVirtual);
84
+ }
85
+ // Width tổng thể đổi theo dataset → cần update mỗi lượt.
56
86
  this.divVirtual.style.width = `${width}px`;
57
- this.divVirtual.style.height = '1px';
58
- this.elementRef.nativeElement.classList.add('relative');
59
- this.elementRef.nativeElement.classList.remove('w-full');
60
- this.elementRef.nativeElement.style.paddingLeft = `0px`;
61
- this.ElementScroll.append(this.divVirtual);
62
- this.outDivVirtual.emit(this.divVirtual);
87
+ // containerWidth lúc đầu có thể = 0 (DOM chưa layout), khiến end giữ
88
+ // nguyên 0 và chỉ render 3 cột đầu. Re-đo bằng handlerScroll sau khi vòng
89
+ // lặp await xong (layout thường đã hoàn tất) để end ra đúng giá trị.
90
+ this.handlerScroll();
63
91
  });
64
92
  });
93
+ this.destroyRef.onDestroy(() => {
94
+ clearTimeout(this.timerAfterViewInit);
95
+ this.divVirtual.remove();
96
+ this.resizeObserver?.disconnect();
97
+ });
65
98
  }
66
99
  ngAfterViewInit() {
67
- setTimeout(() => {
68
- fromEvent(this.ElementScroll, 'scroll').pipe(takeUntil(this.onDestroy)).subscribe(this.handlerScroll.bind(this));
100
+ this.timerAfterViewInit = setTimeout(() => {
101
+ fromEvent(this.ScrollContainer, 'scroll')
102
+ .pipe(takeUntilDestroyed(this.destroyRef))
103
+ .subscribe(() => this.handlerScroll());
69
104
  this.outFunctionControl.emit(this.FunctionsControl);
105
+ // Re-tính viewport sau khi container có kích thước thật.
106
+ // Lần đầu render, getBoundingClientRect().width có thể = 0 nên end giữ giá trị 0,
107
+ // chỉ hiển thị 3 cột đầu (do slice(start, end + bufferAhead)). ResizeObserver fire ngay khi
108
+ // observe và mỗi lần container đổi size sẽ kéo end về đúng giá trị.
109
+ this.resizeObserver = new ResizeObserver(() => this.handlerScroll());
110
+ this.resizeObserver.observe(this.ScrollContainer);
70
111
  });
71
112
  }
72
- get ElementScroll() {
73
- return this.elementScroll() || this.elementRef.nativeElement;
74
- }
75
- /* FUNCTIONS */
113
+ // #region PUBLIC API
76
114
  get FunctionsControl() {
77
115
  return {
78
116
  scrollInto: this.scrollInto.bind(this),
79
117
  scrollToPosition: this.scrollToPosition.bind(this),
80
118
  scrollToIndex: this.scrollToIndex.bind(this),
81
119
  reCalculatorViewPort: this.reCalculatorViewPort.bind(this),
120
+ getViewPortItems: () => this.viewPortItems(),
82
121
  };
83
122
  }
123
+ // #region PRIVATE METHODS
124
+ get ScrollContainer() {
125
+ return this.elementScroll() || this.elementRef.nativeElement;
126
+ }
84
127
  handlerScroll() {
85
- if (!this.storeItems()?.length) {
128
+ const store = this.storeItems();
129
+ if (!store?.length) {
86
130
  return;
87
131
  }
88
- const rect = this.ElementScroll.getBoundingClientRect();
132
+ const scrollEl = this.ScrollContainer;
133
+ const scrollLeft = scrollEl.scrollLeft;
134
+ const containerWidth = scrollEl.getBoundingClientRect().width;
89
135
  let start = this.start();
136
+ let endLocal = this.end();
90
137
  let isSetStartPositioned = false;
91
- for (const index in this.storeItems()) {
92
- const item = this.storeItems()[index];
93
- if (item['end'] > rect.width + this.ElementScroll.scrollLeft) {
138
+ for (let i = 0; i < store.length; i++) {
139
+ const item = store[i];
140
+ if (item.end > containerWidth + scrollLeft) {
94
141
  break;
95
142
  }
96
- this.end.set(+index);
97
- if (item['end'] >= this.ElementScroll.scrollLeft && !isSetStartPositioned) {
98
- start = +index;
143
+ endLocal = i;
144
+ if (item.end >= scrollLeft && !isSetStartPositioned) {
145
+ start = i;
99
146
  isSetStartPositioned = true;
100
147
  }
101
148
  }
149
+ if (endLocal !== this.end()) {
150
+ this.end.set(endLocal);
151
+ }
102
152
  if (start !== this.start()) {
103
153
  this.start.set(start);
104
- this.elementRef.nativeElement.style.paddingLeft = `${this.storeItems()[start]['start']}px`;
105
- }
106
- if (this.start() < 0) {
107
- this.start.set(0);
154
+ const paddingLeft = store[start].start;
155
+ this.elementRef.nativeElement.style.paddingLeft = `${paddingLeft}px`;
156
+ this.outPaddingLeft.emit(paddingLeft);
108
157
  }
109
- this.viewPortItems.set(this.storeItems()
110
- .slice(this.start(), this.end() + 3)
111
- .map((item) => item['ref']));
158
+ this.viewPortItems.set(store.slice(this.start(), this.end() + this.bufferAhead).map((item) => item.ref));
112
159
  this.outViewPortItem.emit(this.viewPortItems());
113
160
  }
114
161
  async scrollInto(itemScroll) {
115
- const itemFound = this.storeItems().find((item) => item['ref'] === itemScroll);
116
- if (itemFound) {
117
- this.ElementScroll.scrollLeft = itemFound['start'];
162
+ const itemFound = this.storeItems().find((item) => item.ref === itemScroll);
163
+ if (!itemFound) {
164
+ return;
118
165
  }
166
+ this.ScrollContainer.scrollLeft = itemFound.start;
119
167
  }
120
168
  async scrollToPosition(position) {
121
- this.ElementScroll.scrollLeft = position;
169
+ this.ScrollContainer.scrollLeft = position;
122
170
  }
123
171
  async scrollToIndex(index) {
124
172
  const itemOfIndex = this.storeItems()[index];
125
- if (itemOfIndex) {
126
- this.ElementScroll.scrollLeft = itemOfIndex['start'];
173
+ if (!itemOfIndex) {
174
+ return;
127
175
  }
176
+ this.ScrollContainer.scrollLeft = itemOfIndex.start;
128
177
  }
129
178
  async reCalculatorViewPort() {
179
+ const items = this.storeItems();
180
+ const fn = this.functionGetWidthItem();
181
+ // Đo song song để rút ngắn thời gian thay vì await tuần tự.
182
+ const widths = await Promise.all(items.map((it) => fn(it.ref)));
130
183
  let width = 0;
131
- this.storeItems.update((items) => {
132
- items.forEach(async (item) => {
133
- const itemWidth = await this.functionGetWidthItem()(item['ref']);
134
- set(item, 'itemWidth', itemWidth);
135
- set(item, 'start', width);
136
- width += itemWidth;
137
- set(item, 'end', width);
138
- });
139
- return [...items];
140
- });
184
+ for (let i = 0; i < items.length; i++) {
185
+ const item = items[i];
186
+ const itemWidth = widths[i];
187
+ item.itemWidth = itemWidth;
188
+ item.start = width;
189
+ width += itemWidth;
190
+ item.end = width;
191
+ }
192
+ this.storeItems.set([...items]);
141
193
  this.divVirtual.style.width = `${width}px`;
142
194
  this.handlerScroll();
143
195
  }
144
- ngOnDestroy() {
145
- this.divVirtual.remove();
146
- this.onDestroy.next();
147
- this.onDestroy.complete();
148
- }
149
196
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiScrollMeasureItemDirectionHorizontalDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
150
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiScrollMeasureItemDirectionHorizontalDirective, isStandalone: true, selector: "[LibsUiScrollMeasureItemDirectionHorizontalDirective]", inputs: { elementScroll: { classPropertyName: "elementScroll", publicName: "elementScroll", isSignal: true, isRequired: true, transformFunction: null }, functionGetWidthItem: { classPropertyName: "functionGetWidthItem", publicName: "functionGetWidthItem", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { outViewPortItem: "outViewPortItem", outFunctionControl: "outFunctionControl", outDivVirtual: "outDivVirtual" }, ngImport: i0 });
197
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiScrollMeasureItemDirectionHorizontalDirective, isStandalone: true, selector: "[LibsUiScrollMeasureItemDirectionHorizontalDirective]", inputs: { elementScroll: { classPropertyName: "elementScroll", publicName: "elementScroll", isSignal: true, isRequired: true, transformFunction: null }, functionGetWidthItem: { classPropertyName: "functionGetWidthItem", publicName: "functionGetWidthItem", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { outViewPortItem: "outViewPortItem", outFunctionControl: "outFunctionControl", outDivVirtual: "outDivVirtual", outPaddingLeft: "outPaddingLeft" }, ngImport: i0 });
151
198
  }
152
199
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiScrollMeasureItemDirectionHorizontalDirective, decorators: [{
153
200
  type: Directive,
@@ -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":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { AfterViewInit, Directive, ElementRef, OnDestroy, effect, inject, input, output, signal, untracked } from '@angular/core';\nimport { cloneDeep, set } from '@libs-ui/utils';\nimport { Subject, fromEvent, takeUntil } from 'rxjs';\nimport { IScrollMeasureItemDirectionHorizontalFunctionsControl } 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, OnDestroy {\n // #region PROPERTY\n public viewPortItems = signal<Array<Record<string, any>>>([]);\n private start = signal<number>(0);\n private end = signal<number>(0);\n private storeItems = signal<Array<Record<string, any>>>([]);\n private divVirtual = document.createElement('div');\n private onDestroy = new Subject<void>();\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<Record<string, any>>>();\n readonly outFunctionControl = output<IScrollMeasureItemDirectionHorizontalFunctionsControl>();\n readonly outDivVirtual = output<HTMLDivElement>();\n\n // #region INJECT\n private elementRef = inject(ElementRef);\n\n constructor() {\n effect(() => {\n if (!untracked(() => this.ElementScroll) || !untracked(() => this.functionGetWidthItem())) {\n return;\n }\n const items = this.items();\n untracked(async () => {\n this.start.set(0);\n const rect = this.ElementScroll.getBoundingClientRect();\n let width = 0;\n const newItems = [];\n for (const index in items) {\n const item = items[index];\n const newItem = cloneDeep(item);\n newItem.ref = item;\n const itemWidth = await this.functionGetWidthItem()(item);\n newItem.itemWidth = itemWidth;\n newItem.start = width;\n width += itemWidth;\n newItem.end = width;\n if (newItem.end <= rect.width + this.ElementScroll.scrollLeft) {\n this.end.set(+index);\n }\n newItems.push(newItem);\n }\n this.storeItems.set(newItems);\n this.viewPortItems.set(\n this.storeItems()\n .slice(this.start(), this.end() + 3)\n .map((item) => item['ref'])\n );\n this.outViewPortItem.emit(this.viewPortItems());\n\n this.divVirtual.style.position = 'absolute';\n this.divVirtual.style.width = `${width}px`;\n this.divVirtual.style.height = '1px';\n this.elementRef.nativeElement.classList.add('relative');\n this.elementRef.nativeElement.classList.remove('w-full');\n this.elementRef.nativeElement.style.paddingLeft = `0px`;\n this.ElementScroll.append(this.divVirtual);\n this.outDivVirtual.emit(this.divVirtual);\n });\n });\n }\n\n ngAfterViewInit() {\n setTimeout(() => {\n fromEvent<Event>(this.ElementScroll, 'scroll').pipe(takeUntil(this.onDestroy)).subscribe(this.handlerScroll.bind(this));\n this.outFunctionControl.emit(this.FunctionsControl);\n });\n }\n\n private get ElementScroll() {\n return this.elementScroll() || this.elementRef.nativeElement;\n }\n\n /* FUNCTIONS */\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 };\n }\n private handlerScroll() {\n if (!this.storeItems()?.length) {\n return;\n }\n const rect = this.ElementScroll.getBoundingClientRect();\n let start = this.start();\n let isSetStartPositioned = false;\n\n for (const index in this.storeItems()) {\n const item = this.storeItems()[index];\n if (item['end'] > rect.width + this.ElementScroll.scrollLeft) {\n break;\n }\n this.end.set(+index);\n if (item['end'] >= this.ElementScroll.scrollLeft && !isSetStartPositioned) {\n start = +index;\n isSetStartPositioned = true;\n }\n }\n\n if (start !== this.start()) {\n this.start.set(start);\n this.elementRef.nativeElement.style.paddingLeft = `${this.storeItems()[start]['start']}px`;\n }\n\n if (this.start() < 0) {\n this.start.set(0);\n }\n this.viewPortItems.set(\n this.storeItems()\n .slice(this.start(), this.end() + 3)\n .map((item) => item['ref'])\n );\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\n if (itemFound) {\n this.ElementScroll.scrollLeft = itemFound['start'];\n }\n }\n private async scrollToPosition(position: number) {\n this.ElementScroll.scrollLeft = position;\n }\n\n private async scrollToIndex(index: number) {\n const itemOfIndex = this.storeItems()[index];\n\n if (itemOfIndex) {\n this.ElementScroll.scrollLeft = itemOfIndex['start'];\n }\n }\n\n private async reCalculatorViewPort() {\n let width = 0;\n\n this.storeItems.update((items) => {\n items.forEach(async (item) => {\n const itemWidth = await this.functionGetWidthItem()(item['ref']);\n\n set(item, 'itemWidth', itemWidth);\n set(item, 'start', width);\n width += itemWidth;\n set(item, 'end', width);\n });\n return [...items];\n });\n this.divVirtual.style.width = `${width}px`;\n this.handlerScroll();\n }\n\n ngOnDestroy(): void {\n this.divVirtual.remove();\n this.onDestroy.next();\n this.onDestroy.complete();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;AAAA;MAUa,mDAAmD,CAAA;;AAEvD,IAAA,aAAa,GAAG,MAAM,CAA6B,EAAE,CAAC;AACrD,IAAA,KAAK,GAAG,MAAM,CAAS,CAAC,CAAC;AACzB,IAAA,GAAG,GAAG,MAAM,CAAS,CAAC,CAAC;AACvB,IAAA,UAAU,GAAG,MAAM,CAA6B,EAAE,CAAC;AACnD,IAAA,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;AAC1C,IAAA,SAAS,GAAG,IAAI,OAAO,EAAQ;;AAG9B,IAAA,aAAa,GAAG,KAAK,CAAC,QAAQ,EAAe;AAC7C,IAAA,oBAAoB,GAAG,KAAK,CAAC,QAAQ,EAAkC;AACvE,IAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAc;;IAGpC,eAAe,GAAG,MAAM,EAA8B;IACtD,kBAAkB,GAAG,MAAM,EAAyD;IACpF,aAAa,GAAG,MAAM,EAAkB;;AAGzC,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAEvC,IAAA,WAAA,GAAA;QACE,MAAM,CAAC,MAAK;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC,EAAE;gBACzF;YACF;AACA,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;YAC1B,SAAS,CAAC,YAAW;AACnB,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;gBACjB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,qBAAqB,EAAE;gBACvD,IAAI,KAAK,GAAG,CAAC;gBACb,MAAM,QAAQ,GAAG,EAAE;AACnB,gBAAA,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE;AACzB,oBAAA,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC;AACzB,oBAAA,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC;AAC/B,oBAAA,OAAO,CAAC,GAAG,GAAG,IAAI;oBAClB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC,IAAI,CAAC;AACzD,oBAAA,OAAO,CAAC,SAAS,GAAG,SAAS;AAC7B,oBAAA,OAAO,CAAC,KAAK,GAAG,KAAK;oBACrB,KAAK,IAAI,SAAS;AAClB,oBAAA,OAAO,CAAC,GAAG,GAAG,KAAK;AACnB,oBAAA,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE;wBAC7D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;oBACtB;AACA,oBAAA,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;gBACxB;AACA,gBAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAC7B,IAAI,CAAC,aAAa,CAAC,GAAG,CACpB,IAAI,CAAC,UAAU;AACZ,qBAAA,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC;AAClC,qBAAA,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAC9B;gBACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;gBAE/C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU;gBAC3C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,CAAA,EAAG,KAAK,CAAA,EAAA,CAAI;gBAC1C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK;gBACpC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;gBACvD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC;gBACxD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,GAAG,CAAA,GAAA,CAAK;gBACvD,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;gBAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;AAC1C,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;IACJ;IAEA,eAAe,GAAA;QACb,UAAU,CAAC,MAAK;AACd,YAAA,SAAS,CAAQ,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvH,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC;AACrD,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,IAAY,aAAa,GAAA;QACvB,OAAO,IAAI,CAAC,aAAa,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa;IAC9D;;AAGA,IAAA,IAAW,gBAAgB,GAAA;QACzB,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;YAClD,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;YAC5C,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;SAC3D;IACH;IACQ,aAAa,GAAA;QACnB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE;YAC9B;QACF;QACA,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,qBAAqB,EAAE;AACvD,QAAA,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE;QACxB,IAAI,oBAAoB,GAAG,KAAK;QAEhC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC;AACrC,YAAA,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE;gBAC5D;YACF;YACA,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;AACpB,YAAA,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,UAAU,IAAI,CAAC,oBAAoB,EAAE;gBACzE,KAAK,GAAG,CAAC,KAAK;gBACd,oBAAoB,GAAG,IAAI;YAC7B;QACF;AAEA,QAAA,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE;AAC1B,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;YACrB,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,GAAG,CAAA,EAAG,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAA,EAAA,CAAI;QAC5F;AAEA,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE;AACpB,YAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACnB;QACA,IAAI,CAAC,aAAa,CAAC,GAAG,CACpB,IAAI,CAAC,UAAU;AACZ,aAAA,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC;AAClC,aAAA,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAC9B;QACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;IACjD;IAEQ,MAAM,UAAU,CAAC,UAAe,EAAA;QACtC,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC;QAE9E,IAAI,SAAS,EAAE;YACb,IAAI,CAAC,aAAa,CAAC,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC;QACpD;IACF;IACQ,MAAM,gBAAgB,CAAC,QAAgB,EAAA;AAC7C,QAAA,IAAI,CAAC,aAAa,CAAC,UAAU,GAAG,QAAQ;IAC1C;IAEQ,MAAM,aAAa,CAAC,KAAa,EAAA;QACvC,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC;QAE5C,IAAI,WAAW,EAAE;YACf,IAAI,CAAC,aAAa,CAAC,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC;QACtD;IACF;AAEQ,IAAA,MAAM,oBAAoB,GAAA;QAChC,IAAI,KAAK,GAAG,CAAC;QAEb,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,KAAI;AAC/B,YAAA,KAAK,CAAC,OAAO,CAAC,OAAO,IAAI,KAAI;AAC3B,gBAAA,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAEhE,gBAAA,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC;AACjC,gBAAA,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC;gBACzB,KAAK,IAAI,SAAS;AAClB,gBAAA,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC;AACzB,YAAA,CAAC,CAAC;AACF,YAAA,OAAO,CAAC,GAAG,KAAK,CAAC;AACnB,QAAA,CAAC,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,CAAA,EAAG,KAAK,CAAA,EAAA,CAAI;QAC1C,IAAI,CAAC,aAAa,EAAE;IACtB;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;AACxB,QAAA,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;AACrB,QAAA,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE;IAC3B;wGApKW,mDAAmD,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;4FAAnD,mDAAmD,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,uDAAA,EAAA,MAAA,EAAA,EAAA,aAAA,EAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,UAAA,EAAA,eAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,oBAAA,EAAA,EAAA,iBAAA,EAAA,sBAAA,EAAA,UAAA,EAAA,sBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,eAAA,EAAA,iBAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,aAAA,EAAA,eAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;4FAAnD,mDAAmD,EAAA,UAAA,EAAA,CAAA;kBAL/D,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;;AAET,oBAAA,QAAQ,EAAE,uDAAuD;AACjE,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;ACTD;;AAEG;;;;"}
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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"}
@@ -3,4 +3,11 @@ export interface IScrollMeasureItemDirectionHorizontalFunctionsControl {
3
3
  scrollToPosition: (position: number) => Promise<void>;
4
4
  scrollToIndex: (index: number) => Promise<void>;
5
5
  reCalculatorViewPort: () => Promise<void>;
6
+ getViewPortItems: () => Array<any>;
7
+ }
8
+ export interface IScrollMeasureStoreItemConvert {
9
+ ref: any;
10
+ itemWidth: number;
11
+ start: number;
12
+ end: number;
6
13
  }
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@libs-ui/components-scroll-measure-items-direction-horizontal",
3
- "version": "0.2.356-9",
3
+ "version": "0.2.357-0",
4
4
  "peerDependencies": {
5
5
  "@angular/core": ">=18.0.0",
6
- "@libs-ui/utils": "0.2.356-9",
7
6
  "rxjs": "~7.8.0"
8
7
  },
9
8
  "sideEffects": false,