@libs-ui/components-list 0.2.356-42 → 0.2.356-43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,58 +1,61 @@
1
1
  # @libs-ui/components-list
2
2
 
3
- > Component mạnh mẽ hỗ trợ hiển thị danh sách với nhiều định dạng: Text, Radio, Checkbox, Tag Group/Tree. Kèm sub-component hiển thị giao diện No-Data linh hoạt.
4
-
5
- **Version:** `0.2.356-3` | **Package:** `@libs-ui/components-list`
3
+ > Component danh sách đa năng hỗ trợ 5 kiểu template (text, radio, checkbox, tag, group/tree) với tìm kiếm, lazy-loading HTTP Angular Signals.
6
4
 
7
5
  ## Giới thiệu
8
6
 
9
- `LibsUiComponentsListComponent` là một standalone Angular component được thiết kế để xử lý các bài toán hiển thị danh sách phức tạp. Component này không chỉ hiển thị dữ liệu còn tích hợp sâu với các dịch vụ lấy dữ liệu (HTTP), tìm kiếm quản trạng thái chọn item.
7
+ `LibsUiComponentsListComponent` là một standalone Angular component được thiết kế để xử lý các bài toán hiển thị danh sách phức tạp. Component tích hợp sẵn tìm kiếm online/offline, lazy-loading qua HTTP, virtual scroll quản trạng thái chọn item (single/multi). Mỗi kiểu hiển thị (text, radio, checkbox, tag, group/tree) được khởi tạo động thông qua `LibsUiDynamicComponentService`, giúp bundle size tối ưu và cấu hình linh hoạt.
10
8
 
11
- ### Tính năng
9
+ ## Tính năng
12
10
 
13
- - ✅ **Đa dạng Template**: Hỗ trợ 5 loại hiển thị: `text`, `radio`, `checkbox`, `tag`, `group`.
14
- - ✅ **Cấu trúc Cây (Group/Tree)**: Hiển thị phân cấp không giới hạn level với logic quan hệ cha-con tinh tế.
15
- - ✅ **Tìm kiếm Thông minh**: Hỗ trợ cả tìm kiếm Online (server-side) và Offline (client-side).
11
+ - ✅ **Đa dạng template**: Hỗ trợ 5 loại hiển thị: `text`, `radio`, `checkbox`, `tag`, `group/tree`.
12
+ - ✅ **Cấu trúc cây (Group/Tree)**: Phân cấp không giới hạn với logic quan hệ cha-con tích hợp sẵn qua `buildListGroupConfig`.
13
+ - ✅ **Tìm kiếm thông minh**: Hỗ trợ cả tìm kiếm online (server-side) và offline (client-side).
16
14
  - ✅ **Tích hợp HTTP**: Tự động load dữ liệu thông qua cấu hình `httpRequestData`.
17
15
  - ✅ **Virtual Scroll**: Tối ưu render danh sách lớn.
18
- - ✅ **OnPush Change Detection**: Tối ưu hiệu năng render.
19
- - ✅ **Standalone Component**: Dễ dàng import và sử dụng.
20
- - ✅ **Angular Signals**: State management hiện đại và reactive.
21
16
  - ✅ **Custom Row/Column**: Tùy biến layout từng item qua cấu hình `rows`/`cols`.
17
+ - ✅ **FunctionControl API**: Điều khiển component từ bên ngoài (refresh, resetKeySelected, removeItems, updateData).
18
+ - ✅ **Validation**: Hỗ trợ bắt buộc chọn item qua `validRequired`.
19
+ - ✅ **OnPush + Standalone**: Hiệu năng cao, dễ import.
22
20
 
23
21
  ## Khi nào sử dụng
24
22
 
25
- - Khi cần hiển thị danh sách các tùy chọn cho người dùng chọn một hoặc nhiều.
26
- - Khi xây dựng các bộ lọc (filters) cấu trúc phân cấp phức tạp.
27
- - Khi danh sách có kích thước lớn cần tối ưu hiệu năng render (Virtual Scroll).
28
- - Khi muốn đồng bộ hóa việc tìm kiếm hiển thị dữ liệu từ API một cách tự động.
29
-
30
- ## ⚠️ Important Notes
31
-
32
- - **Config Type**: Phải luôn cung cấp `type` và signal config tương ứng (ví dụ: `configTemplateText` cho type `text`).
33
- - **Performance**: Virtual scroll được bật mặc định cho danh sách lớn. Tắt bằng cách set `notUseVirtualScroll: true` trong config.
34
- - **Signals**: Các cấu hình bên trong `config` (như `httpRequestData`) phải là `WritableSignal` để component có thể reactive.
35
- - **keysDisableItem + configCheckboxCheckAll**: Không dùng `keysDisableItem` kết hợp với template checkbox có `configCheckboxCheckAll`.
23
+ - Khi cần danh sách tùy chọn đơn (single-select) hoặc đa (multi-select).
24
+ - Khi xây dựng bộ lọc (filter) phân cấp dạng cây (tree structure).
25
+ - Khi danh sách có kích thước lớn, cần lazy-loading hoặc tìm kiếm online từ API.
26
+ - Khi cần hiển thị dữ liệu danh sách với layout item phức tạp (nhiều dòng, nhiều cột).
27
+ - Khi xây dựng dropdown, menu chọn nhanh với icon và hình ảnh.
36
28
 
37
29
  ## Cài đặt
38
30
 
39
31
  ```bash
40
- # npm
41
32
  npm install @libs-ui/components-list
42
-
43
- # yarn
44
- yarn add @libs-ui/components-list
45
33
  ```
46
34
 
47
35
  ## Import
48
36
 
49
37
  ```typescript
50
- // Component chính
51
38
  import { LibsUiComponentsListComponent } from '@libs-ui/components-list';
52
-
53
- // Sub-component No-Data (khi cần dùng riêng)
54
39
  import { LibsUiComponentsListTemplatesNoDataComponent } from '@libs-ui/components-list';
55
40
 
41
+ // Types & Interfaces
42
+ import {
43
+ IListConfigItem,
44
+ IListConfigItemText,
45
+ IListConfigItemRadio,
46
+ IListConfigItemCheckbox,
47
+ IListConfigItemGroup,
48
+ IListConfigItemTag,
49
+ IListFunctionControlEvent,
50
+ IListDataEmitKey,
51
+ IListDataEmitMultiKey,
52
+ IDataUpdateToStore,
53
+ TYPE_TEMPLATE,
54
+ } from '@libs-ui/components-list';
55
+
56
+ // Helper function để tạo cấu hình group/tree
57
+ import { buildListGroupConfig } from '@libs-ui/components-list';
58
+
56
59
  @Component({
57
60
  standalone: true,
58
61
  imports: [LibsUiComponentsListComponent],
@@ -61,234 +64,600 @@ import { LibsUiComponentsListTemplatesNoDataComponent } from '@libs-ui/component
61
64
  export class YourComponent {}
62
65
  ```
63
66
 
64
- ## Ví dụ
67
+ ## Ví dụ sử dụng
65
68
 
66
- ### Basic (Text Single Select)
69
+ ### dụ 1 — Text (Single Select)
67
70
 
68
71
  ```html
69
72
  <libs_ui-components-list
70
73
  [config]="configText()"
71
74
  [keySelected]="keySelected()"
72
- (outSelectKey)="onSelect($event)"
73
- (outFunctionsControl)="onFunctionsControl($event)"></libs_ui-components-list>
75
+ (outSelectKey)="handlerSelectKey($event)"
76
+ (outFunctionsControl)="handlerFunctionsControl($event)">
77
+ </libs_ui-components-list>
74
78
  ```
75
79
 
76
80
  ```typescript
77
- configText = signal<IListConfigItem>({
78
- type: 'text',
79
- httpRequestData: signal<IHttpRequestConfig>({
80
- objectInstance: returnListObject(myData),
81
- argumentsValue: [],
82
- functionName: 'list',
83
- }),
84
- configTemplateText: signal({
85
- fieldKey: 'id',
86
- getValue: (item) => item.name || item.label,
87
- }),
88
- });
81
+ import { signal } from '@angular/core';
82
+ import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey, IListFunctionControlEvent } from '@libs-ui/components-list';
83
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
84
+
85
+ // Dữ liệu mẫu
86
+ const sampleData = [
87
+ { id: '1', name: 'Nguyễn Văn An', avatar: '' },
88
+ { id: '2', name: 'Trần Thị Bình', avatar: '' },
89
+ { id: '3', name: 'Lê Quốc Cường', avatar: '' },
90
+ ];
91
+
92
+ export class YourComponent {
93
+ protected keySelected = signal<string | undefined>('1');
94
+ private listFunctionControl: IListFunctionControlEvent | undefined;
95
+
96
+ protected configText = signal<IListConfigItem>({
97
+ type: 'text',
98
+ httpRequestData: signal<IHttpRequestConfig>({
99
+ objectInstance: returnListObject(sampleData),
100
+ argumentsValue: [],
101
+ functionName: 'list',
102
+ }),
103
+ configTemplateText: signal({
104
+ fieldKey: 'id',
105
+ getValue: (item) => item.name,
106
+ }),
107
+ });
108
+
109
+ protected handlerSelectKey(event?: IListDataEmitKey) {
110
+ event?.event?.stopPropagation?.();
111
+ this.keySelected.set(event?.key as string);
112
+ }
113
+
114
+ protected handlerFunctionsControl(event: IListFunctionControlEvent) {
115
+ this.listFunctionControl = event;
116
+ }
117
+ }
89
118
  ```
90
119
 
91
- ### Checkbox (Multi-select)
120
+ ### Ví dụ 2 — Checkbox (Multi Select)
92
121
 
93
122
  ```html
94
123
  <libs_ui-components-list
95
124
  [config]="configCheckbox()"
96
125
  [multiKeySelected]="multiKeySelected()"
97
- (outSelectMultiKey)="onMultiSelect($event)"></libs_ui-components-list>
126
+ [searchConfig]="{ placeholder: 'Tìm kiếm...' }"
127
+ (outSelectMultiKey)="handlerSelectMultiKey($event)"
128
+ (outFunctionsControl)="handlerFunctionsControl($event)">
129
+ </libs_ui-components-list>
98
130
  ```
99
131
 
100
132
  ```typescript
101
- configCheckbox = signal<IListConfigItem>({
102
- type: 'checkbox',
103
- configTemplateCheckbox: signal({
104
- fieldKey: 'id',
105
- getValue: (item) => item.name,
106
- }),
107
- });
133
+ import { signal } from '@angular/core';
134
+ import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey, IListFunctionControlEvent } from '@libs-ui/components-list';
135
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
136
+
137
+ const sampleData = [
138
+ { id: '1', name: 'Báo cáo doanh thu' },
139
+ { id: '2', name: 'Báo cáo chi phí' },
140
+ { id: '3', name: 'Báo cáo lợi nhuận' },
141
+ ];
142
+
143
+ export class YourComponent {
144
+ protected multiKeySelected = signal<string[]>(['1', '2']);
145
+ private listFunctionControl: IListFunctionControlEvent | undefined;
146
+
147
+ protected configCheckbox = signal<IListConfigItem>({
148
+ type: 'checkbox',
149
+ httpRequestData: signal<IHttpRequestConfig>({
150
+ objectInstance: returnListObject(sampleData),
151
+ argumentsValue: [],
152
+ functionName: 'list',
153
+ }),
154
+ configTemplateCheckbox: signal({
155
+ fieldKey: 'id',
156
+ getValue: (item) => item.name,
157
+ }),
158
+ });
159
+
160
+ protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
161
+ this.multiKeySelected.set(event?.keys as string[]);
162
+ }
163
+
164
+ protected handlerFunctionsControl(event: IListFunctionControlEvent) {
165
+ this.listFunctionControl = event;
166
+ }
167
+ }
168
+ ```
169
+
170
+ ### Ví dụ 3 — Group/Tree với `buildListGroupConfig`
171
+
172
+ ```html
173
+ <libs_ui-components-list
174
+ [config]="configGroup()"
175
+ [multiKeySelected]="multiKeySelected()"
176
+ (outSelectMultiKey)="handlerSelectMultiKey($event)">
177
+ </libs_ui-components-list>
178
+ ```
179
+
180
+ ```typescript
181
+ import { signal } from '@angular/core';
182
+ import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey, buildListGroupConfig } from '@libs-ui/components-list';
183
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
184
+
185
+ const groupData = [
186
+ {
187
+ id: 'g1',
188
+ name: 'Nhóm A',
189
+ items: [
190
+ { id: 'a1', name: 'Item A1' },
191
+ { id: 'a2', name: 'Item A2' },
192
+ ],
193
+ },
194
+ {
195
+ id: 'g2',
196
+ name: 'Nhóm B',
197
+ items: [
198
+ { id: 'b1', name: 'Item B1' },
199
+ { id: 'b2', name: 'Item B2' },
200
+ ],
201
+ },
202
+ ];
203
+
204
+ export class YourComponent {
205
+ protected multiKeySelected = signal<string[]>([]);
206
+
207
+ // buildListGroupConfig tạo sẵn cấu hình cho các pattern phổ biến
208
+ protected configGroup = signal<IListConfigItem>({
209
+ type: 'group',
210
+ httpRequestData: signal<IHttpRequestConfig>({
211
+ objectInstance: returnListObject(groupData),
212
+ argumentsValue: [],
213
+ functionName: 'list',
214
+ }),
215
+ configTemplateGroup: buildListGroupConfig('group_tree_checkbox_1'),
216
+ });
217
+
218
+ protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
219
+ this.multiKeySelected.set(event?.keys as string[]);
220
+ }
221
+ }
108
222
  ```
109
223
 
110
- ### Tag + Search
224
+ ### dụ 4 — Radio
225
+
226
+ ```html
227
+ <libs_ui-components-list
228
+ [config]="configRadio()"
229
+ [keySelected]="keySelected()"
230
+ (outSelectKey)="handlerSelectKey($event)">
231
+ </libs_ui-components-list>
232
+ ```
233
+
234
+ ```typescript
235
+ import { signal } from '@angular/core';
236
+ import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey } from '@libs-ui/components-list';
237
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
238
+
239
+ const sampleData = [
240
+ { id: 'all', name: 'Tất cả' },
241
+ { id: 'active', name: 'Đang hoạt động' },
242
+ { id: 'inactive', name: 'Ngừng hoạt động' },
243
+ ];
244
+
245
+ export class YourComponent {
246
+ protected keySelected = signal<string | undefined>('all');
247
+
248
+ protected configRadio = signal<IListConfigItem>({
249
+ type: 'radio',
250
+ httpRequestData: signal<IHttpRequestConfig>({
251
+ objectInstance: returnListObject(sampleData),
252
+ argumentsValue: [],
253
+ functionName: 'list',
254
+ }),
255
+ configTemplateRadio: signal({
256
+ fieldKey: 'id',
257
+ getValue: (item) => item.name,
258
+ }),
259
+ });
260
+
261
+ protected handlerSelectKey(event?: IListDataEmitKey) {
262
+ this.keySelected.set(event?.key as string);
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### Ví dụ 5 — Tag
111
268
 
112
269
  ```html
113
270
  <libs_ui-components-list
114
271
  [config]="configTag()"
115
- [searchConfig]="{ placeholder: 'Tìm theo tên...' }"></libs_ui-components-list>
272
+ [searchConfig]="{ placeholder: 'Tìm theo tên...' }"
273
+ [multiKeySelected]="multiKeySelected()"
274
+ (outSelectMultiKey)="handlerSelectMultiKey($event)">
275
+ </libs_ui-components-list>
116
276
  ```
117
277
 
118
278
  ```typescript
119
- configTag = signal<IListConfigItem>({
120
- type: 'tag',
121
- configTemplateTag: signal({ fieldKey: 'id', getValue: (item) => item.name }),
122
- });
279
+ import { signal } from '@angular/core';
280
+ import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey } from '@libs-ui/components-list';
281
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
282
+
283
+ const tagData = [
284
+ { id: '1', name: 'Angular' },
285
+ { id: '2', name: 'TypeScript' },
286
+ { id: '3', name: 'RxJS' },
287
+ { id: '4', name: 'Signals' },
288
+ ];
289
+
290
+ export class YourComponent {
291
+ protected multiKeySelected = signal<string[]>([]);
292
+
293
+ protected configTag = signal<IListConfigItem>({
294
+ type: 'tag',
295
+ httpRequestData: signal<IHttpRequestConfig>({
296
+ objectInstance: returnListObject(tagData),
297
+ argumentsValue: [],
298
+ functionName: 'list',
299
+ }),
300
+ configTemplateTag: signal({
301
+ fieldKey: 'id',
302
+ getValue: (item) => item.name,
303
+ }),
304
+ });
305
+
306
+ protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
307
+ this.multiKeySelected.set(event?.keys as string[]);
308
+ }
309
+ }
123
310
  ```
124
311
 
125
- ### Group / Tree
312
+ ### dụ 6 — Custom Row & Column
313
+
314
+ ```html
315
+ <libs_ui-components-list [config]="configCustomRow()">
316
+ </libs_ui-components-list>
317
+ ```
126
318
 
127
319
  ```typescript
128
- import { buildListGroupConfig } from '@libs-ui/components-list';
320
+ import { signal } from '@angular/core';
321
+ import { of } from 'rxjs';
322
+ import { LibsUiComponentsListComponent, IListConfigItem } from '@libs-ui/components-list';
323
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
324
+ import { get } from '@libs-ui/utils';
325
+
326
+ const richData = [
327
+ { id: '1', name: 'Nguyễn Văn An', subTitle: 'Trưởng phòng', desc: 'Kinh nghiệm 5 năm' },
328
+ { id: '2', name: 'Trần Thị Bình', subTitle: 'Nhân viên', desc: 'Kinh nghiệm 2 năm' },
329
+ ];
330
+
331
+ export class YourComponent {
332
+ protected configCustomRow = signal<IListConfigItem>({
333
+ type: 'text',
334
+ httpRequestData: signal<IHttpRequestConfig>({
335
+ objectInstance: returnListObject(richData),
336
+ argumentsValue: [],
337
+ functionName: 'list',
338
+ }),
339
+ configTemplateText: signal({
340
+ fieldKey: 'id',
341
+ rows: signal([
342
+ signal({
343
+ classRow: 'flex flex-col mb-1',
344
+ cols: signal([
345
+ signal({ getValue: (data) => of(get(data, 'item.name') as string) }),
346
+ signal({ getValue: (data) => of(get(data, 'item.subTitle') as string) }),
347
+ ]),
348
+ }),
349
+ signal({
350
+ classRow: 'text-sm text-gray-500 mt-1',
351
+ getValue: (data) => of(get(data, 'item.desc') as string),
352
+ }),
353
+ ]),
354
+ }),
355
+ });
356
+ }
357
+ ```
129
358
 
130
- configGroup = signal<IListConfigItem>({
131
- type: 'group',
132
- configTemplateGroup: buildListGroupConfig('group_tree_checkbox_1'),
133
- });
359
+ ### dụ 7 — FunctionControl: refresh, resetKeySelected, removeItems
360
+
361
+ ```typescript
362
+ import { signal } from '@angular/core';
363
+ import { LibsUiComponentsListComponent, IListFunctionControlEvent } from '@libs-ui/components-list';
364
+
365
+ export class YourComponent {
366
+ private listFunctionControl: IListFunctionControlEvent | undefined;
367
+
368
+ protected handlerFunctionsControl(event: IListFunctionControlEvent) {
369
+ this.listFunctionControl = event;
370
+ }
371
+
372
+ protected handlerRefreshList() {
373
+ this.listFunctionControl?.refresh();
374
+ }
375
+
376
+ protected handlerResetSelection() {
377
+ this.listFunctionControl?.resetKeySelected();
378
+ }
379
+
380
+ protected handlerRemoveItems(keys: string[]) {
381
+ this.listFunctionControl?.removeItems(keys);
382
+ }
383
+
384
+ protected async handlerValidate(): Promise<boolean> {
385
+ return this.listFunctionControl?.checkIsValid() ?? true;
386
+ }
387
+ }
134
388
  ```
135
389
 
136
- ### Custom Row & Column
390
+ ### dụ 8 — Saved Views (Group flat với single-select item)
391
+
392
+ ```html
393
+ <libs_ui-components-list
394
+ [config]="configSavedViews()"
395
+ [keySelected]="savedViewKeySelected()"
396
+ (outSelectKey)="handlerSelectedSavedView($event)">
397
+ </libs_ui-components-list>
398
+ ```
137
399
 
138
400
  ```typescript
139
- configCustomRow = signal<IListConfigItem>({
140
- type: 'text',
141
- configTemplateText: signal({
142
- fieldKey: 'id',
143
- rows: signal([
144
- signal({
145
- classRow: 'flex flex-col',
146
- cols: signal([signal({ getValue: (data) => of(`<b>${data.item.name}</b>`) }), signal({ getValue: (data) => of(data.item.subTitle) })]),
147
- }),
148
- signal({ getValue: (data) => of(data.item.desc) }),
149
- ]),
150
- }),
151
- });
401
+ import { signal } from '@angular/core';
402
+ import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey } from '@libs-ui/components-list';
403
+ import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
404
+
405
+ const savedViewGroups = [
406
+ {
407
+ key: 'my_views',
408
+ label: 'Views của tôi',
409
+ views: [
410
+ { key: 'view_high_value', label: 'Khách hàng giá trị cao' },
411
+ { key: 'view_new', label: 'Khách hàng mới' },
412
+ ],
413
+ },
414
+ {
415
+ key: 'shared_views',
416
+ label: 'Views chia sẻ',
417
+ views: [
418
+ { key: 'view_vip', label: 'VIP' },
419
+ ],
420
+ },
421
+ ];
422
+
423
+ export class YourComponent {
424
+ protected savedViewKeySelected = signal<string[] | undefined>(['view_high_value']);
425
+
426
+ protected configSavedViews = signal<IListConfigItem>({
427
+ type: 'group',
428
+ httpRequestData: signal<IHttpRequestConfig>({
429
+ objectInstance: returnListObject(savedViewGroups),
430
+ argumentsValue: [],
431
+ functionName: 'list',
432
+ }),
433
+ configTemplateGroup: signal({
434
+ fieldKey: 'key',
435
+ fieldGetItems: 'views',
436
+ getLabelGroup: (group) => `${group.label} (${group.views.length})`,
437
+ getMaxLevelGroup: () => 2,
438
+ getLabelItem: (item) => item.label,
439
+ singleSelectItem: true,
440
+ hasIconCheckSingleSelectItem: true,
441
+ hasBackgroundColorWhenItemSelected: true,
442
+ colorBlueWhenItemSelected: true,
443
+ iconExpand: 'right',
444
+ ignoreCheckboxGroup: true,
445
+ ignoreCheckboxItem: true,
446
+ getExpandGroup: () => true,
447
+ }),
448
+ });
449
+
450
+ protected handlerSelectedSavedView(event?: IListDataEmitKey) {
451
+ this.savedViewKeySelected.set(event ? [event.key as string] : undefined);
452
+ }
453
+ }
152
454
  ```
153
455
 
154
- ### No-Data (Default)
456
+ ## @Input()
457
+
458
+ ### `libs_ui-components-list`
459
+
460
+ | Input | Type | Default | Mô tả | Ví dụ |
461
+ |---|---|---|---|---|
462
+ | `autoSelectedFirstItemCallOutsideBefore` | `boolean` | `false` | Tự động chọn item đầu tiên nếu chưa có item nào được chọn khi component khởi tạo. | `[autoSelectedFirstItemCallOutsideBefore]="true"` |
463
+ | `backgroundListCustom` | `string` | `'bg-[#ffffff]'` | Class màu nền tùy chỉnh cho container danh sách. | `[backgroundListCustom]="'bg-gray-50'"` |
464
+ | `buttonsOther` | `IButton[] \| undefined` | `undefined` | Danh sách các nút bấm bổ sung hiển thị phía trên danh sách. | `[buttonsOther]="extraButtons"` |
465
+ | `clickExactly` | `boolean` | `undefined` | Chỉ kích hoạt sự kiện chọn khi click trực tiếp vào vùng item, không kích hoạt khi click vào popover hoặc vùng ngoài. | `[clickExactly]="true"` |
466
+ | `config` | `IListConfigItem` | **BẮT BUỘC** | Cấu hình chính cho component: loại template, nguồn dữ liệu, config hiển thị item. | `[config]="configText()"` |
467
+ | `disable` | `boolean` | `undefined` | Vô hiệu hóa toàn bộ component, không cho phép chọn item. | `[disable]="isDisabled()"` |
468
+ | `disableLabel` | `boolean` | `undefined` | Vô hiệu hóa phần label tiêu đề của danh sách. | `[disableLabel]="true"` |
469
+ | `dividerClassInclude` | `string` | `undefined` | Class CSS bổ sung cho đường kẻ phân cách giữa các phần. | `[dividerClassInclude]="'border-blue-200'"` |
470
+ | `dropdownTabKeyActive` | `string` | `undefined` | Key của tab đang active trong dropdown. Thay đổi giá trị này sẽ trigger refresh danh sách. | `[dropdownTabKeyActive]="activeTab()"` |
471
+ | `focusInputSearch` | `boolean` | `undefined` | Tự động focus vào ô tìm kiếm sau khi dữ liệu load xong. | `[focusInputSearch]="true"` |
472
+ | `functionGetItemsAutoAddList` | `(() => Array<any>) \| undefined` | `undefined` | Hàm trả về danh sách item được thêm tự động vào đầu danh sách (ví dụ: option "Tất cả"). | `[functionGetItemsAutoAddList]="getExtraItems"` |
473
+ | `hasButtonUnSelectOption` | `boolean` | `undefined` | Hiển thị nút "Bỏ chọn" để xóa toàn bộ lựa chọn hiện tại. | `[hasButtonUnSelectOption]="true"` |
474
+ | `hasDivider` | `boolean` | `true` | Hiển thị đường kẻ phân cách giữa các phần (search bar và danh sách). | `[hasDivider]="false"` |
475
+ | `hiddenInputSearch` | `boolean` | `false` | Ẩn thanh tìm kiếm. Mặc định hiển thị. | `[hiddenInputSearch]="true"` |
476
+ | `ignoreClassDisableDefaultWhenUseKeysDisableItem` | `boolean` | `undefined` | Bỏ class CSS disable mặc định khi dùng `keysDisableItem`, cho phép tự xử lý UI disabled từng phần trong rows. | `[ignoreClassDisableDefaultWhenUseKeysDisableItem]="true"` |
477
+ | `isSearchOnline` | `boolean` | `undefined` | Bật chế độ tìm kiếm từ server (call API với keyword). Mặc định là tìm kiếm client-side. | `[isSearchOnline]="true"` |
478
+ | `keySearch` | `string` | `undefined` | Giá trị keyword tìm kiếm được truyền vào từ bên ngoài. | `[keySearch]="searchKeyword()"` |
479
+ | `keySelected` | `unknown` | `undefined` | Key của item đang được chọn (single select). Dùng cho type `text` và `radio`. | `[keySelected]="selectedId()"` |
480
+ | `keysDisableItem` | `Array<unknown> \| undefined` | `undefined` | Danh sách các key bị vô hiệu hóa, không thể chọn. Không dùng kết hợp với `configCheckboxCheckAll`. | `[keysDisableItem]="disabledIds()"` |
481
+ | `keysHiddenItem` | `Array<unknown> \| undefined` | `undefined` | Danh sách các key bị ẩn khỏi danh sách. Không dùng kết hợp với `configCheckboxCheckAll`. | `[keysHiddenItem]="hiddenIds()"` |
482
+ | `labelConfig` | `ILabel` | `undefined` | Cấu hình label tiêu đề hiển thị phía trên danh sách. | `[labelConfig]="{ label: 'Chọn danh mục' }"` |
483
+ | `loadingIconSize` | `'large' \| 'medium' \| 'small' \| 'smaller' \| undefined` | `undefined` | Kích thước icon loading khi đang tải dữ liệu. | `[loadingIconSize]="'small'"` |
484
+ | `maxItemShow` | `number` | `undefined` | Số lượng item tối đa hiển thị trong viewport trước khi bắt đầu scroll. | `[maxItemShow]="5"` |
485
+ | `multiKeySelected` | `Array<unknown> \| undefined` | `undefined` | Danh sách các key đang được chọn (multi select). Dùng cho type `checkbox` và `group`. | `[multiKeySelected]="selectedIds()"` |
486
+ | `paddingLeftItem` | `boolean` | `undefined` | Thêm padding bên trái cho các item trong danh sách. | `[paddingLeftItem]="true"` |
487
+ | `resetKeyWhenSelectAllKeyDropdown` | `boolean` | `undefined` | Reset key đã chọn khi người dùng chọn option "Tất cả" trong dropdown. | `[resetKeyWhenSelectAllKeyDropdown]="true"` |
488
+ | `searchConfig` | `IInputSearchConfig` | `{}` | Cấu hình chi tiết cho ô tìm kiếm (placeholder, debounce, v.v.). | `[searchConfig]="{ placeholder: 'Tìm kiếm...' }"` |
489
+ | `searchPadding` | `boolean` | `undefined` | Thêm padding cho container thanh tìm kiếm. | `[searchPadding]="true"` |
490
+ | `showValidateBottom` | `boolean` | `undefined` | Hiển thị thông báo lỗi validation ở phía dưới danh sách thay vì phía trên. | `[showValidateBottom]="true"` |
491
+ | `skipFocusInputWhenKeySearchStoreUndefined` | `boolean` | `undefined` | Bỏ qua auto-focus vào ô tìm kiếm khi `keySearchStore` đang là `undefined`. | `[skipFocusInputWhenKeySearchStoreUndefined]="true"` |
492
+ | `templateRefNotSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh hiển thị khi chưa có từ khóa tìm kiếm và danh sách rỗng. | `[templateRefNotSearchNoData]="tplNoData"` |
493
+ | `templateRefSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh hiển thị khi tìm kiếm không có kết quả. | `[templateRefSearchNoData]="tplNoResult"` |
494
+ | `validRequired` | `IValidRequired \| undefined` | `undefined` | Cấu hình bắt buộc phải chọn ít nhất một item. Tích hợp với form validation. | `[validRequired]="{ message: 'Vui lòng chọn ít nhất 1 mục' }"` |
495
+ | `zIndex` | `number` | `undefined` | Z-index CSS cho container danh sách, dùng khi cần quản lý lớp hiển thị. | `[zIndex]="100"` |
496
+
497
+ ## @Output()
498
+
499
+ ### `libs_ui-components-list`
500
+
501
+ | Output | Type | Mô tả | Handler TS | Binding HTML |
502
+ |---|---|---|---|---|
503
+ | `(outChangeView)` | `Array<any>` | Phát ra mảng item đang hiển thị sau mỗi lần danh sách thay đổi (load xong, filter, search). | `handlerChangeView(items: any[]): void { items.stopPropagation?.(); ... }` | `(outChangeView)="handlerChangeView($event)"` |
504
+ | `(outChangStageFlagMousePopover)` | `IFlagMouse` | Phát ra trạng thái hover chuột vào/ra khỏi popover trong danh sách. | `handlerFlagMousePopover(flag: IFlagMouse): void { flag.stopPropagation?.(); ... }` | `(outChangStageFlagMousePopover)="handlerFlagMousePopover($event)"` |
505
+ | `(outClickButtonOther)` | `IButton` | Phát ra khi người dùng click vào một trong các nút bổ sung (`buttonsOther`). | `handlerClickButtonOther(btn: IButton): void { btn.stopPropagation?.(); ... }` | `(outClickButtonOther)="handlerClickButtonOther($event)"` |
506
+ | `(outFieldKey)` | `string \| undefined` | Phát ra tên trường `fieldKey` đang sử dụng trong template hiện tại. | `handlerFieldKey(key: string \| undefined): void { ... }` | `(outFieldKey)="handlerFieldKey($event)"` |
507
+ | `(outFunctionsControl)` | `IListFunctionControlEvent` | Phát ra object chứa các method điều khiển component từ bên ngoài. Nên lưu lại để gọi sau. | `handlerFunctionsControl(event: IListFunctionControlEvent): void { this.listControl = event; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
508
+ | `(outKeySearch)` | `string` | Phát ra keyword mỗi khi người dùng thay đổi nội dung ô tìm kiếm. | `handlerKeySearch(keyword: string): void { this.keyword.set(keyword); }` | `(outKeySearch)="handlerKeySearch($event)"` |
509
+ | `(outLoadItemsComplete)` | `{ items: Array<any>; paging?: IPaging }` | Phát ra sau khi HTTP request tải dữ liệu hoàn tất. | `handlerLoadComplete(event: { items: any[]; paging?: IPaging }): void { this.total.set(event.paging?.total ?? 0); }` | `(outLoadItemsComplete)="handlerLoadComplete($event)"` |
510
+ | `(outLoading)` | `boolean` | Phát ra trạng thái loading: `true` khi đang tải, `false` khi xong. | `handlerLoading(loading: boolean): void { this.isLoading.set(loading); }` | `(outLoading)="handlerLoading($event)"` |
511
+ | `(outSelectKey)` | `IListDataEmitKey \| undefined` | Phát ra khi người dùng chọn 1 item (single select, type `text`/`radio`). `undefined` khi bỏ chọn. | `handlerSelectKey(event?: IListDataEmitKey): void { event?.event?.stopPropagation?.(); this.selected.set(event?.key as string); }` | `(outSelectKey)="handlerSelectKey($event)"` |
512
+ | `(outSelectMultiKey)` | `IListDataEmitMultiKey \| undefined` | Phát ra khi người dùng chọn/bỏ chọn item (multi select, type `checkbox`/`group`). | `handlerSelectMultiKey(event?: IListDataEmitMultiKey): void { this.selectedIds.set(event?.keys as string[]); }` | `(outSelectMultiKey)="handlerSelectMultiKey($event)"` |
513
+ | `(outUnSelectMultiKey)` | `Array<unknown>` | Phát ra mảng keys bị bỏ chọn trong multi select. | `handlerUnSelectMultiKey(keys: unknown[]): void { ... }` | `(outUnSelectMultiKey)="handlerUnSelectMultiKey($event)"` |
514
+
515
+ ## FunctionControl (via `outFunctionsControl`)
516
+
517
+ Nhận `IListFunctionControlEvent` qua `(outFunctionsControl)` để điều khiển component từ bên ngoài:
518
+
519
+ | Method | Signature | Mô tả |
520
+ |---|---|---|
521
+ | `checkIsValid` | `() => Promise<boolean>` | Kiểm tra tính hợp lệ của lựa chọn khi có cấu hình `validRequired`. Trả về `true` nếu hợp lệ. |
522
+ | `getRectListView` | `() => Promise<IBoundingClientRect>` | Lấy kích thước và vị trí DOM của container danh sách. |
523
+ | `refresh` | `() => Promise<void>` | Làm mới danh sách: clone lại config và tải lại dữ liệu từ đầu. |
524
+ | `removeItems` | `(keys: string[]) => Promise<void>` | Xóa các item khỏi store hiển thị theo danh sách keys. |
525
+ | `resetKeySelected` | `() => Promise<void>` | Xóa toàn bộ item đang được chọn (cả single lẫn multi select). |
526
+ | `updateData` | `(data: IDataUpdateToStore) => Promise<void>` | Cập nhật/merge dữ liệu vào store hiện tại mà không cần reload từ API. |
527
+
528
+ ```typescript
529
+ // Sử dụng FunctionControl
530
+ export class YourComponent {
531
+ private listControl: IListFunctionControlEvent | undefined;
532
+
533
+ protected handlerFunctionsControl(event: IListFunctionControlEvent) {
534
+ this.listControl = event;
535
+ }
536
+
537
+ protected handlerRefresh() {
538
+ this.listControl?.refresh();
539
+ }
540
+
541
+ protected async handlerSubmit(): Promise<void> {
542
+ const isValid = await this.listControl?.checkIsValid();
543
+ if (!isValid) {
544
+ return;
545
+ }
546
+ // proceed...
547
+ }
548
+ }
549
+ ```
550
+
551
+ ## Sub-component: `libs_ui-components-list-templates-no_data`
552
+
553
+ Component hiển thị trạng thái no-data, có thể dùng độc lập hoặc truyền qua `[templateRefSearchNoData]` / `[templateRefNotSearchNoData]`.
155
554
 
156
555
  ```html
556
+ <!-- No data cơ bản -->
157
557
  <libs_ui-components-list-templates-no_data
158
- [config]="configEmpty()"
558
+ [config]="configText()"
159
559
  [keySearch]="''"
160
560
  [loading]="false"
161
- [enableNoData]="true"></libs_ui-components-list-templates-no_data>
162
- ```
163
-
164
- ### No-Data (Custom trước khi search)
561
+ [enableNoData]="true">
562
+ </libs_ui-components-list-templates-no_data>
165
563
 
166
- ```html
167
- <ng-template #tplNoData>
564
+ <!-- Custom template khi chưa search -->
565
+ <ng-template #tplNotSearch>
168
566
  <div class="flex flex-col items-center p-4">
169
567
  <span>Vui lòng nhập thông tin để tìm kiếm.</span>
170
568
  </div>
171
569
  </ng-template>
172
570
 
173
571
  <libs_ui-components-list-templates-no_data
174
- [config]="configEmpty()"
572
+ [config]="configText()"
175
573
  [keySearch]="''"
176
574
  [loading]="false"
177
575
  [enableNoData]="true"
178
- [templateRefNotSearchNoData]="tplNoData"></libs_ui-components-list-templates-no_data>
179
- ```
576
+ [templateRefNotSearchNoData]="tplNotSearch">
577
+ </libs_ui-components-list-templates-no_data>
180
578
 
181
- ### No-Result (Custom sau khi search)
182
-
183
- ```html
184
- <ng-template
185
- #tplNoResult
186
- let-keySearch="keySearch">
187
- <div>
188
- Không có kết quả cho:
189
- <b>{{ keySearch }}</b>
579
+ <!-- Custom template khi search không có kết quả -->
580
+ <ng-template #tplNoResult let-keySearch="keySearch">
581
+ <div class="p-4 text-center text-gray-500">
582
+ Không tìm thấy kết quả cho: <b>{{ keySearch }}</b>
190
583
  </div>
191
584
  </ng-template>
192
585
 
193
586
  <libs_ui-components-list-templates-no_data
194
- [config]="configEmpty()"
195
- [keySearch]="searchKeyword"
587
+ [config]="configText()"
588
+ [keySearch]="'từ khóa tìm kiếm'"
196
589
  [loading]="false"
197
590
  [enableNoData]="true"
198
- [templateRefSearchNoData]="tplNoResult"></libs_ui-components-list-templates-no_data>
591
+ [templateRefSearchNoData]="tplNoResult">
592
+ </libs_ui-components-list-templates-no_data>
199
593
  ```
200
594
 
201
- ## API
202
-
203
- ### libs_ui-components-list
204
-
205
- #### Inputs
206
-
207
- | Property | Type | Default | Description |
208
- | ------------------------------------------------- | -------------------------------- | -------------- | -------------------------------------------------------------------------- |
209
- | `autoSelectedFirstItemCallOutsideBefore` | `boolean` | `false` | Tự động chọn item đầu tiên nếu chưa có item nào được chọn. |
210
- | `backgroundListCustom` | `string` | `bg-[#ffffff]` | Màu nền tùy chỉnh cho danh sách. |
211
- | `buttonsOther` | `IButton[]` | `undefined` | Danh sách các nút bấm khác hiển thị cuối danh sách. |
212
- | `clickExactly` | `boolean` | `undefined` | Chỉ kích hoạt chọn khi click chính xác vào item. |
213
- | `config` | `IListConfigItem` | **REQUIRED** | Cấu hình chính (loại template, logic lấy dữ liệu...). |
214
- | `disable` | `boolean` | `false` | Vô hiệu hóa toàn bộ component. |
215
- | `disableLabel` | `boolean` | `false` | Vô hiệu hóa nhãn (label). |
216
- | `dividerClassInclude` | `string` | `undefined` | Class CSS bổ sung cho đường kẻ phân cách. |
217
- | `dropdownTabKeyActive` | `string` | `undefined` | Key của tab đang active trong dropdown. |
218
- | `focusInputSearch` | `boolean` | `undefined` | Tự động focus vào ô tìm kiếm khi dữ liệu load xong. |
219
- | `functionGetItemsAutoAddList` | `() => Array<any>` | `undefined` | Hàm lấy danh sách item tự động thêm vào list. |
220
- | `hasButtonUnSelectOption` | `boolean` | `undefined` | Hiển thị nút bỏ chọn tất cả. |
221
- | `hasDivider` | `boolean` | `true` | Hiển thị gạch phân cách giữa các phần. |
222
- | `hiddenInputSearch` | `boolean` | `false` | Ẩn/hiện thanh tìm kiếm. |
223
- | `ignoreClassDisableDefaultWhenUseKeysDisableItem` | `boolean` | `undefined` | Bỏ class disable mặc định khi dùng `keysDisableItem`. |
224
- | `isSearchOnline` | `boolean` | `false` | Bật tìm kiếm từ Server (call API). |
225
- | `keySearch` | `string` | `undefined` | Giá trị tìm kiếm khởi tạo. |
226
- | `keySelected` | `any` | `undefined` | Key đang chọn (single select). |
227
- | `keysDisableItem` | `Array<any>` | `undefined` | Danh sách các keys bị vô hiệu hóa. |
228
- | `keysHiddenItem` | `Array<any>` | `undefined` | Danh sách các keys bị ẩn đi. |
229
- | `labelConfig` | `ILabel` | `undefined` | Cấu hình label tiêu đề cho danh sách. |
230
- | `loadingIconSize` | `'large' \| 'medium' \| 'small'` | `undefined` | Kích thước icon loading. |
231
- | `maxItemShow` | `number` | `undefined` | Số lượng item tối đa hiển thị trước khi scroll. |
232
- | `multiKeySelected` | `Array<any>` | `[]` | Danh sách keys đang chọn (multi select). |
233
- | `paddingLeftItem` | `boolean` | `undefined` | Thêm padding bên trái cho item. |
234
- | `resetKeyWhenSelectAllKeyDropdown` | `boolean` | `undefined` | Reset key khi chọn tất cả trong dropdown. |
235
- | `searchConfig` | `IInputSearchConfig` | `{}` | Cấu hình chi tiết cho ô tìm kiếm. |
236
- | `searchPadding` | `boolean` | `undefined` | Thêm padding cho thanh tìm kiếm. |
237
- | `showValidateBottom` | `boolean` | `undefined` | Hiển thị thông báo validate ở phía dưới. |
238
- | `skipFocusInputWhenKeySearchStoreUndefined` | `boolean` | `undefined` | Bỏ qua focus input nếu `keySearchStore` là undefined. |
239
- | `templateRefSearchNoData` | `TemplateRef` | `undefined` | Template hiển thị khi tìm kiếm không có kết quả. |
240
- | `templateRefNotSearchNoData` | `TemplateRef` | `undefined` | Template hiển thị khi không trong trạng thái tìm kiếm và không có dữ liệu. |
241
- | `validRequired` | `IValidRequired` | `undefined` | Cấu hình bắt buộc phải chọn item. |
242
- | `zIndex` | `number` | `undefined` | Z-index cho danh sách. |
243
-
244
- #### Outputs
245
-
246
- | Property | Type | Description |
247
- | --------------------------------- | ----------------------------------------- | -------------------------------------------------------- |
248
- | `(outChangeView)` | `Array<any>` | Phát ra khi danh sách hiển thị thay đổi (ví dụ sau lọc). |
249
- | `(outChangStageFlagMousePopover)` | `IFlagMouse` | Phát ra trạng thái hover chuột cho popover. |
250
- | `(outClickButtonOther)` | `IButton` | Phát ra khi click vào các nút bấm bổ sung. |
251
- | `(outFieldKey)` | `string` | Phát ra key của trường dữ liệu được chọn. |
252
- | `(outFunctionsControl)` | `IListFunctionControlEvent` | Cung cấp các method điều khiển từ bên ngoài. |
253
- | `(outKeySearch)` | `string` | Phát ra khi keyword tìm kiếm thay đổi. |
254
- | `(outLoadItemsComplete)` | `{ items: Array<any>, paging?: IPaging }` | Phát ra khi việc tải dữ liệu hoàn tất. |
255
- | `(outLoading)` | `boolean` | Trạng thái đang tải dữ liệu. |
256
- | `(outSelectKey)` | `IListDataEmitKey` | Phát ra khi chọn 1 item (Single Select). |
257
- | `(outSelectMultiKey)` | `IListDataEmitMultiKey` | Phát ra khi chọn nhiều item (Multi Select). |
258
- | `(outUnSelectMultiKey)` | `Array<unknown>` | Phát ra danh sách các keys bị bỏ chọn. |
259
-
260
- #### Methods (via `outFunctionsControl`)
261
-
262
- | Method | Parameters | Description |
263
- | -------------------- | -------------------------- | ----------------------------------------------------------- |
264
- | `checkIsValid()` | `-` | Kiểm tra tính hợp lệ của lựa chọn (nếu có `validRequired`). |
265
- | `getRectListView()` | `-` | Lấy kích thước và vị trí của list view container. |
266
- | `refresh()` | `-` | Làm mới danh sách và tải lại dữ liệu từ đầu. |
267
- | `removeItems()` | `keys: Array<any>` | Xóa các item cụ thể khỏi danh sách theo keys. |
268
- | `resetKeySelected()` | `-` | Xóa sạch toàn bộ các item đang được chọn. |
269
- | `updateData()` | `data: IDataUpdateToStore` | Cập nhật dữ liệu cụ thể vào store của list. |
270
-
271
- ### libs_ui-components-list-templates-no_data
272
-
273
- Sub-component hiển thị giao diện trạng thái No-Data, có thể dùng độc lập hoặc tích hợp qua `[templateRefSearchNoData]` / `[templateRefNotSearchNoData]`.
274
-
275
- #### Inputs
276
-
277
- | Property | Type | Default | Description |
278
- | ---------------------------- | ----------------- | ------------ | ------------------------------------------------------------------------------ |
279
- | `config` | `IListConfigItem` | **REQUIRED** | Cấu hình list (dùng để lấy `textNoData`). |
280
- | `keySearch` | `string` | `''` | Keyword đang search. Nếu rỗng → hiển thị no-data; nếu có → hiển thị no-result. |
281
- | `loading` | `boolean` | `false` | Đang tải dữ liệu — khi `true` sẽ không hiển thị no-data. |
282
- | `enableNoData` | `boolean` | `false` | Bật/tắt hiển thị trạng thái no-data. |
283
- | `templateRefNotSearchNoData` | `TemplateRef` | `undefined` | Template tùy chỉnh khi chưa tìm kiếm (keySearch rỗng). |
284
- | `templateRefSearchNoData` | `TemplateRef` | `undefined` | Template tùy chỉnh khi tìm kiếm không có kết quả. |
595
+ ### @Input() của `libs_ui-components-list-templates-no_data`
285
596
 
286
- ## Types & Interfaces
597
+ | Input | Type | Default | Mô tả | Ví dụ |
598
+ |---|---|---|---|---|
599
+ | `config` | `IListConfigItem \| undefined` | `undefined` | Cấu hình list để lấy text no-data tùy chỉnh (`textNoData`). | `[config]="configText()"` |
600
+ | `enableNoData` | `boolean` | **BẮT BUỘC** | Bật/tắt hiển thị trạng thái no-data. Phải là `true` để component hiển thị. | `[enableNoData]="true"` |
601
+ | `keySearch` | `string` | **BẮT BUỘC** | Keyword tìm kiếm hiện tại. Rỗng → hiển thị no-data; có giá trị → hiển thị no-result. | `[keySearch]="keyword()"` |
602
+ | `loading` | `boolean` | **BẮT BUỘC** | Trạng thái đang tải. Khi `true` không hiển thị no-data. | `[loading]="isLoading()"` |
603
+ | `templateRefNotSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh khi chưa có từ khóa tìm kiếm (keySearch rỗng). | `[templateRefNotSearchNoData]="tplNotSearch"` |
604
+ | `templateRefSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh khi tìm kiếm không có kết quả. Context: `{ keySearch: string }`. | `[templateRefSearchNoData]="tplNoResult"` |
605
+
606
+ ## Helper: `buildListGroupConfig`
607
+
608
+ Hàm tiện ích tạo sẵn cấu hình `IListConfigItemGroup` theo các pattern phổ biến:
287
609
 
288
610
  ```typescript
289
- export type TYPE_TEMPLATE = 'checkbox' | 'group' | 'radio' | 'text' | 'tag';
611
+ import { buildListGroupConfig } from '@libs-ui/components-list';
612
+
613
+ // Tham số 1: template name
614
+ // Tham số 2: overrides (tùy chọn) — ghi đè fieldKey, fieldGetItems, getLabelGroup, getLabelItem, getMaxLevelGroup
615
+ const groupConfig = buildListGroupConfig('group_tree_checkbox_1', {
616
+ fieldKey: 'code',
617
+ fieldGetItems: 'children',
618
+ getLabelGroup: (g) => g.categoryName,
619
+ getLabelItem: (item) => item.productName,
620
+ });
621
+ ```
622
+
623
+ | Template | Mô tả |
624
+ |---|---|
625
+ | `group_tree_checkbox_1` | Tree nhiều cấp, checkbox, logic Bank: chọn con → chọn cha; bỏ cha → bỏ hết con. |
626
+ | `group_tree_checkbox_2` | Tree nhiều cấp, checkbox, logic Retail: chọn cha → chọn hết con; bỏ cha không bỏ con. |
627
+ | `group_tree_radio_1` | Tree nhiều cấp, radio, single-select theo logic child triggers parent. |
628
+ | `group_tree_radio_2` | Tree nhiều cấp, radio, single-select (biến thể emit all item checked). |
629
+ | `group_tree_text_1` | Tree nhiều cấp, text, single-select theo logic Bank. |
630
+ | `group_tree_text_2` | Tree nhiều cấp, text, single-select theo logic Retail. |
631
+ | `group_label_checkbox_1` | Group dạng label phân nhóm, item checkbox multi-select. |
632
+ | `group_label_text_1` | Group dạng label phân nhóm, item text single-select với icon check. |
290
633
 
291
- export interface IListConfigItem {
634
+ ## Types & Interfaces
635
+
636
+ ```typescript
637
+ import {
638
+ TYPE_TEMPLATE,
639
+ IListConfigItem,
640
+ IListConfigItemText,
641
+ IListConfigItemRadio,
642
+ IListConfigItemCheckbox,
643
+ IListConfigItemGroup,
644
+ IListConfigItemTag,
645
+ IListConfigItemTextRow,
646
+ IListConfigItemTextCol,
647
+ IListDataEmitKey,
648
+ IListDataEmitMultiKey,
649
+ IListFunctionControlEvent,
650
+ IDataUpdateToStore,
651
+ IDataFunctionCallInConfig,
652
+ IConfigDescriptionGroup,
653
+ CONFIG_LIST_GROUP_TYPE,
654
+ } from '@libs-ui/components-list';
655
+
656
+ // Kiểu template
657
+ type TYPE_TEMPLATE = 'checkbox' | 'group' | 'radio' | 'text' | 'tag';
658
+
659
+ // Cấu hình chính
660
+ interface IListConfigItem {
292
661
  type: TYPE_TEMPLATE;
293
662
  httpRequestData?: WritableSignal<IHttpRequestConfig>;
294
663
  configTemplateText?: WritableSignal<IListConfigItemText>;
@@ -296,53 +665,73 @@ export interface IListConfigItem {
296
665
  configTemplateCheckbox?: WritableSignal<IListConfigItemCheckbox>;
297
666
  configTemplateGroup?: WritableSignal<IListConfigItemGroup>;
298
667
  configTemplateTag?: WritableSignal<IListConfigItemTag>;
299
- textNoData?: string;
300
- autoSelectFirstItem?: boolean;
301
- sort?: (items: Array<any>) => void;
302
- // ...
668
+ textNoData?: string; // Text hiển thị khi không có dữ liệu
669
+ textNoDataClassInclude?: string; // Class CSS cho text no-data
670
+ textSearchNoData?: string; // Text khi tìm kiếm không có kết quả
671
+ autoSelectFirstItem?: boolean; // Tự động chọn item đầu tiên khi có dữ liệu
672
+ sort?: (items: Array<any>) => void; // Hàm sort tùy chỉnh
673
+ hasIconNoData?: boolean;
674
+ ignoreShowDataWhenNotSearch?: boolean; // Ẩn danh sách khi chưa search
675
+ highlightTextSearchInResult?: boolean; // Highlight keyword trong kết quả
676
+ heightItem?: number;
677
+ minHeightItem?: number;
303
678
  }
304
679
 
305
- export interface IListDataEmitKey {
306
- key: unknown;
307
- item: any;
308
- isClickManual: boolean;
680
+ // Event khi chọn single item
681
+ interface IListDataEmitKey {
682
+ key: unknown; // Key của item được chọn
683
+ item: any; // Toàn bộ object item
684
+ isClickManual: boolean; // true nếu do user click, false nếu programmatic
309
685
  }
310
686
 
311
- export interface IListDataEmitMultiKey {
312
- keys: Array<unknown>;
313
- mapKeys: Array<IListDataEmitKey>;
687
+ // Event khi chọn nhiều item
688
+ interface IListDataEmitMultiKey {
689
+ keys: Array<unknown>; // Mảng keys được chọn
690
+ mapKeys: Array<IListDataEmitKey>; // Chi tiết từng item được chọn
314
691
  isClickManual: boolean;
315
692
  }
316
693
 
317
- export interface IListFunctionControlEvent {
694
+ // FunctionControl API
695
+ interface IListFunctionControlEvent {
318
696
  checkIsValid: () => Promise<boolean>;
319
697
  refresh: () => Promise<void>;
320
698
  resetKeySelected: () => Promise<void>;
321
699
  getRectListView: () => Promise<IBoundingClientRect>;
322
700
  removeItems: (keys: Array<string>) => Promise<void>;
323
701
  updateData: (data: IDataUpdateToStore) => Promise<void>;
702
+ setMessageError?: (message: string) => Promise<void>;
324
703
  }
325
704
 
326
- export interface IDataUpdateToStore {
705
+ // Dùng cho updateData
706
+ interface IDataUpdateToStore {
327
707
  newData: WritableSignal<Array<WritableSignal<any>>>;
328
- functionCustomAddDataToStore: (newData: WritableSignal<Array<WritableSignal<any>>>, store: WritableSignal<Array<WritableSignal<any>>>) => void;
708
+ functionCustomAddDataToStore: (
709
+ newData: WritableSignal<Array<WritableSignal<any>>>,
710
+ store: WritableSignal<Array<WritableSignal<any>>>
711
+ ) => void;
329
712
  }
330
713
  ```
331
714
 
332
- ## Demo
715
+ ## Lưu ý quan trọng
716
+
717
+ ⚠️ **Config bắt buộc**: Phải luôn cung cấp `type` và signal config tương ứng. Ví dụ: type `text` cần có `configTemplateText: signal({...})`, type `group` cần có `configTemplateGroup: signal({...})` hoặc dùng `buildListGroupConfig(...)`.
718
+
719
+ ⚠️ **WritableSignal cho config con**: Tất cả các config con (`configTemplateText`, `configTemplateCheckbox`, `httpRequestData`, v.v.) phải là `WritableSignal` để component có thể reactive và refresh đúng cách khi gọi `FunctionControl.refresh()`.
720
+
721
+ ⚠️ **keysDisableItem không tương thích với configCheckboxCheckAll**: Không dùng `[keysDisableItem]` kết hợp với template checkbox có cấu hình `configCheckboxCheckAll` vì sẽ gây hành vi không xác định.
333
722
 
334
- - **Local**: [http://localhost:4500/components/list](http://localhost:4500/components/list)
723
+ ⚠️ **keysHiddenItem không tương thích với configCheckboxCheckAll**: Tương tự như trên, không dùng `[keysHiddenItem]` kết hợp với `configCheckboxCheckAll`.
335
724
 
336
- ## Công nghệ
725
+ ⚠️ **Virtual Scroll mặc định**: Virtual scroll được bật mặc định cho danh sách lớn. Nếu cần tắt (ví dụ: dùng trong config badge của table), set `notUseVirtualScroll: true` trong `configTemplateText`.
337
726
 
338
- | Technology | Purpose |
339
- | ----------------- | ----------------------------- |
340
- | Angular 18+ | Framework chính |
341
- | Angular Signals | Quản lý state reactive |
342
- | TailwindCSS | Styling hệ thống |
343
- | Dynamic Component | Khởi tạo template linh hoạt |
344
- | Virtual Scroll | Render danh sách lớn hiệu quả |
727
+ ⚠️ **outFunctionsControl**: Luôn lưu lại `IListFunctionControlEvent` khi nhận từ `(outFunctionsControl)` để dùng sau. Chỉ gọi các method sau khi component đã mount (ngOnInit xong).
345
728
 
346
- ## License
729
+ ⚠️ **dropdownTabKeyActive và refresh**: Thay đổi giá trị `dropdownTabKeyActive` sẽ tự động trigger `refresh()` bên trong component. Sử dụng cơ chế này khi cần reload danh sách khi đổi tab trong dropdown.
730
+
731
+ ## Demo
732
+
733
+ ```bash
734
+ npx nx serve core-ui
735
+ ```
347
736
 
348
- MIT
737
+ Truy cập: http://localhost:4500/components/list