@libs-ui/components-dropdown 0.2.356-41 → 0.2.356-43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,52 +1,39 @@
|
|
|
1
1
|
# @libs-ui/components-dropdown
|
|
2
2
|
|
|
3
|
-
> Component dropdown đa năng hỗ trợ nhiều chế độ hiển thị: text, radio, checkbox, group. Tích hợp sẵn tìm kiếm, validation, popover, tabs và điều khiển từ bên ngoài.
|
|
4
|
-
|
|
5
|
-
**Version:** `0.2.355-14`
|
|
3
|
+
> Component dropdown đa năng hỗ trợ nhiều chế độ hiển thị: text, radio, checkbox, group, tree, JSON tree. Tích hợp sẵn tìm kiếm, validation, popover overlay, tabs phân loại và điều khiển từ bên ngoài qua FunctionControl.
|
|
6
4
|
|
|
7
5
|
## Giới thiệu
|
|
8
6
|
|
|
9
|
-
`LibsUiComponentsDropdownComponent` là một standalone Angular component cung cấp dropdown selection với nhiều chế độ hiển thị và tính năng nâng cao.
|
|
7
|
+
`LibsUiComponentsDropdownComponent` là một standalone Angular component cung cấp dropdown selection với nhiều chế độ hiển thị và tính năng nâng cao. Component tự động load dữ liệu từ API thông qua `IHttpRequestConfig`, hỗ trợ cả chọn đơn và chọn nhiều, đồng thời cung cấp `FunctionControl` để component cha có thể điều khiển (reset, refresh, validate) từ bên ngoài.
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
## Tính năng
|
|
12
10
|
|
|
13
|
-
- ✅ Nhiều chế độ hiển thị: text
|
|
14
|
-
- ✅ Tìm kiếm online
|
|
15
|
-
- ✅ Validation
|
|
16
|
-
- ✅ Điều khiển từ bên ngoài qua `IDropdownFunctionControlEvent`
|
|
17
|
-
- ✅ Hỗ trợ tabs để phân loại dữ liệu
|
|
18
|
-
- ✅ Tự động load
|
|
19
|
-
- ✅
|
|
20
|
-
- ✅
|
|
21
|
-
- ✅
|
|
22
|
-
- ✅
|
|
23
|
-
- ✅ Angular Signals
|
|
11
|
+
- ✅ Nhiều chế độ hiển thị: `text`, `radio`, `checkbox`, `group` (tree, JSON tree, personalize)
|
|
12
|
+
- ✅ Tìm kiếm online (gọi API) và offline (client-side) tích hợp sẵn
|
|
13
|
+
- ✅ Validation: required, giới hạn số lượng item được chọn tối đa
|
|
14
|
+
- ✅ Điều khiển từ bên ngoài qua `IDropdownFunctionControlEvent` (reset, refresh, setError, setItemSelectedByKey...)
|
|
15
|
+
- ✅ Hỗ trợ tabs để phân loại dữ liệu theo nhóm
|
|
16
|
+
- ✅ Tự động load và auto-select item đầu tiên hoặc toàn bộ (autoSelectFirstItem / autoSelectAllItem)
|
|
17
|
+
- ✅ Hiển thị avatar, icon, image cho từng item trong danh sách
|
|
18
|
+
- ✅ Custom content qua `ng-content` (`[isNgContent]="true"`)
|
|
19
|
+
- ✅ Tùy chỉnh popover overlay (hướng, width, z-index, animation...)
|
|
20
|
+
- ✅ Lazy-load chi tiết item theo key (`httpRequestDetailItemById`)
|
|
21
|
+
- ✅ OnPush Change Detection + Angular Signals
|
|
24
22
|
|
|
25
23
|
## Khi nào sử dụng
|
|
26
24
|
|
|
27
|
-
- Chọn một hoặc nhiều giá trị từ danh sách dữ liệu
|
|
25
|
+
- Chọn một hoặc nhiều giá trị từ danh sách dữ liệu API
|
|
28
26
|
- Dropdown với tìm kiếm online/offline
|
|
29
|
-
- Chọn dữ liệu dạng nhóm (group), cây (tree), JSON tree
|
|
27
|
+
- Chọn dữ liệu dạng nhóm (group), cây (tree), JSON tree nested nhiều cấp
|
|
30
28
|
- Cần validation (required, max items)
|
|
31
|
-
- Cần điều khiển dropdown từ component cha (reset, refresh, check valid)
|
|
32
|
-
- Hiển thị dropdown với tabs để phân loại dữ liệu
|
|
33
|
-
- Dropdown với custom
|
|
34
|
-
|
|
35
|
-
## Lưu ý quan trọng
|
|
36
|
-
|
|
37
|
-
- ⚠️ **Bắt buộc `[listConfig]`**: Phải cung cấp `[listConfig]` để dropdown hiển thị danh sách.
|
|
38
|
-
- ⚠️ **Chế độ chọn**: Sử dụng `[listKeySelected]` cho chọn đơn (text/radio), `[listMultiKeySelected]` cho chọn nhiều (checkbox/group).
|
|
39
|
-
- ⚠️ **Auto-load API**: Khi dùng `httpRequestData` trong `listConfig`, dropdown tự động gọi API để load dữ liệu.
|
|
40
|
-
- ⚠️ **FunctionControl**: Output `outFunctionsControl` emit `IDropdownFunctionControlEvent` để điều khiển dropdown từ bên ngoài.
|
|
29
|
+
- Cần điều khiển dropdown từ component cha (reset, refresh, check valid, set error)
|
|
30
|
+
- Hiển thị dropdown với tabs để phân loại dữ liệu theo nguồn khác nhau
|
|
31
|
+
- Dropdown với custom trigger (ng-content) thay vì giao diện mặc định
|
|
41
32
|
|
|
42
33
|
## Cài đặt
|
|
43
34
|
|
|
44
35
|
```bash
|
|
45
|
-
# npm
|
|
46
36
|
npm install @libs-ui/components-dropdown
|
|
47
|
-
|
|
48
|
-
# yarn
|
|
49
|
-
yarn add @libs-ui/components-dropdown
|
|
50
37
|
```
|
|
51
38
|
|
|
52
39
|
## Import
|
|
@@ -66,266 +53,663 @@ import {
|
|
|
66
53
|
@Component({
|
|
67
54
|
standalone: true,
|
|
68
55
|
imports: [LibsUiComponentsDropdownComponent],
|
|
69
|
-
// ...
|
|
70
56
|
})
|
|
71
|
-
export class
|
|
57
|
+
export class MyComponent {}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Ví dụ sử dụng
|
|
61
|
+
|
|
62
|
+
### 1. Basic — Dropdown chọn đơn (type: text)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Component, signal } from '@angular/core';
|
|
66
|
+
import { LibsUiComponentsDropdownComponent, IEmitSelectKey } from '@libs-ui/components-dropdown';
|
|
67
|
+
import { IListConfigItem } from '@libs-ui/components-list';
|
|
68
|
+
import { IHttpRequestConfig } from '@libs-ui/services-http-request';
|
|
69
|
+
import { escapeHtml, get, set, UtilsHttpParamsRequest } from '@libs-ui/utils';
|
|
70
|
+
|
|
71
|
+
@Component({
|
|
72
|
+
selector: 'app-example-basic',
|
|
73
|
+
standalone: true,
|
|
74
|
+
imports: [LibsUiComponentsDropdownComponent],
|
|
75
|
+
template: `
|
|
76
|
+
<libs_ui-components-dropdown
|
|
77
|
+
[labelConfig]="{ labelLeft: 'Chọn nhân viên', required: true }"
|
|
78
|
+
[listConfig]="listConfig"
|
|
79
|
+
[listSearchConfig]="{ noBorder: true }"
|
|
80
|
+
[listMaxItemShow]="5"
|
|
81
|
+
[convertItemSelected]="convertItemSelected"
|
|
82
|
+
[validRequired]="{}"
|
|
83
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
84
|
+
(outFunctionsControl)="handlerFunctionsControl($event)"
|
|
85
|
+
/>
|
|
86
|
+
`,
|
|
87
|
+
})
|
|
88
|
+
export class ExampleBasicComponent {
|
|
89
|
+
private dropdownControl: IDropdownFunctionControlEvent | undefined;
|
|
90
|
+
|
|
91
|
+
readonly listConfig: IListConfigItem = {
|
|
92
|
+
type: 'text',
|
|
93
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
94
|
+
objectInstance: myApiService,
|
|
95
|
+
functionName: 'getList',
|
|
96
|
+
argumentsValue: [new UtilsHttpParamsRequest({ fromObject: { page: 1, per_page: 20 } })],
|
|
97
|
+
}),
|
|
98
|
+
configTemplateText: signal({
|
|
99
|
+
fieldKey: 'id',
|
|
100
|
+
notUseVirtualScroll: true,
|
|
101
|
+
getValue: (item: { name: string }) => escapeHtml(item.name),
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
readonly convertItemSelected = (item: unknown): void => {
|
|
106
|
+
if (!item) return;
|
|
107
|
+
set(item as Record<string, unknown>, 'labelDisplay', escapeHtml(get(item as Record<string, string>, 'name') || ''));
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
handlerSelectKey(event: IEmitSelectKey | undefined): void {
|
|
111
|
+
event?.key; // id item đã chọn
|
|
112
|
+
event?.item; // object item đầy đủ
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
handlerFunctionsControl(event: IDropdownFunctionControlEvent): void {
|
|
116
|
+
this.dropdownControl = event;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async resetSelection(): Promise<void> {
|
|
120
|
+
await this.dropdownControl?.reset();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 2. Dropdown chọn nhiều (type: checkbox)
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { Component, signal } from '@angular/core';
|
|
129
|
+
import { LibsUiComponentsDropdownComponent, IEmitMultiKey } from '@libs-ui/components-dropdown';
|
|
130
|
+
import { IListConfigItem } from '@libs-ui/components-list';
|
|
131
|
+
import { IHttpRequestConfig } from '@libs-ui/services-http-request';
|
|
132
|
+
import { escapeHtml, get, set, UtilsHttpParamsRequest } from '@libs-ui/utils';
|
|
133
|
+
|
|
134
|
+
@Component({
|
|
135
|
+
selector: 'app-example-checkbox',
|
|
136
|
+
standalone: true,
|
|
137
|
+
imports: [LibsUiComponentsDropdownComponent],
|
|
138
|
+
template: `
|
|
139
|
+
<libs_ui-components-dropdown
|
|
140
|
+
[labelConfig]="{ labelLeft: 'Chọn nhiều nhãn', required: true }"
|
|
141
|
+
[listConfig]="checkboxConfig"
|
|
142
|
+
[listSearchConfig]="{ noBorder: true }"
|
|
143
|
+
[listMaxItemShow]="5"
|
|
144
|
+
[(listMultiKeySelected)]="selectedKeys"
|
|
145
|
+
[convertItemSelected]="convertItemSelected"
|
|
146
|
+
[validRequired]="{}"
|
|
147
|
+
[validMaxItemSelected]="{ value: 3, message: 'Chỉ được chọn tối đa 3 mục' }"
|
|
148
|
+
(outSelectMultiKey)="handlerSelectMultiKey($event)"
|
|
149
|
+
/>
|
|
150
|
+
`,
|
|
151
|
+
})
|
|
152
|
+
export class ExampleCheckboxComponent {
|
|
153
|
+
selectedKeys = signal<string[]>([]);
|
|
154
|
+
|
|
155
|
+
readonly checkboxConfig: IListConfigItem = {
|
|
156
|
+
type: 'checkbox',
|
|
157
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
158
|
+
objectInstance: myApiService,
|
|
159
|
+
functionName: 'getList',
|
|
160
|
+
argumentsValue: [new UtilsHttpParamsRequest({ fromObject: { page: 1, per_page: 20 } })],
|
|
161
|
+
}),
|
|
162
|
+
autoSelectFirstItem: false,
|
|
163
|
+
configTemplateCheckbox: signal({
|
|
164
|
+
fieldKey: 'id',
|
|
165
|
+
configButtonSelectAndUndSelectItem: signal({}),
|
|
166
|
+
getValue: (item: { name: string }) => escapeHtml(item.name),
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
readonly convertItemSelected = (item: unknown): void => {
|
|
171
|
+
if (!item) return;
|
|
172
|
+
set(item as Record<string, unknown>, 'labelDisplay', escapeHtml(get(item as Record<string, string>, 'name') || ''));
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
handlerSelectMultiKey(event: IEmitMultiKey | undefined): void {
|
|
176
|
+
event?.keys; // mảng id đã chọn
|
|
177
|
+
event?.mapKeys; // mảng { key, item } đầy đủ
|
|
178
|
+
}
|
|
179
|
+
}
|
|
72
180
|
```
|
|
73
181
|
|
|
74
|
-
|
|
182
|
+
### 3. Dropdown dạng nhóm — Group Checkbox
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { Component, signal } from '@angular/core';
|
|
186
|
+
import { LibsUiComponentsDropdownComponent, IEmitMultiKey } from '@libs-ui/components-dropdown';
|
|
187
|
+
import { IListConfigItem } from '@libs-ui/components-list';
|
|
188
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
189
|
+
import { escapeHtml, get, set } from '@libs-ui/utils';
|
|
190
|
+
|
|
191
|
+
@Component({
|
|
192
|
+
selector: 'app-example-group',
|
|
193
|
+
standalone: true,
|
|
194
|
+
imports: [LibsUiComponentsDropdownComponent],
|
|
195
|
+
template: `
|
|
196
|
+
<libs_ui-components-dropdown
|
|
197
|
+
[labelConfig]="{ labelLeft: 'Chọn theo nhóm', required: true }"
|
|
198
|
+
[listConfig]="groupConfig"
|
|
199
|
+
[listMaxItemShow]="5"
|
|
200
|
+
[convertItemSelected]="convertItemSelected"
|
|
201
|
+
(outSelectMultiKey)="handlerSelectMultiKey($event)"
|
|
202
|
+
/>
|
|
203
|
+
`,
|
|
204
|
+
})
|
|
205
|
+
export class ExampleGroupComponent {
|
|
206
|
+
private readonly groupDataService = returnListObject([
|
|
207
|
+
{
|
|
208
|
+
id: 'nhom_a',
|
|
209
|
+
name: 'Nhóm A',
|
|
210
|
+
items: [
|
|
211
|
+
{ id: 'a1', name: 'Mục A1' },
|
|
212
|
+
{ id: 'a2', name: 'Mục A2' },
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'nhom_b',
|
|
217
|
+
name: 'Nhóm B',
|
|
218
|
+
items: [{ id: 'b1', name: 'Mục B1' }],
|
|
219
|
+
},
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
readonly groupConfig: IListConfigItem = {
|
|
223
|
+
type: 'group',
|
|
224
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
225
|
+
objectInstance: this.groupDataService,
|
|
226
|
+
functionName: 'list',
|
|
227
|
+
argumentsValue: [],
|
|
228
|
+
}),
|
|
229
|
+
configTemplateGroup: signal({
|
|
230
|
+
fieldKey: 'id',
|
|
231
|
+
fieldGetItems: 'items',
|
|
232
|
+
getLabelGroup: (group: { name: string }) => escapeHtml(group.name),
|
|
233
|
+
getMaxLevelGroup: () => 2,
|
|
234
|
+
getLabelItem: (item: { name: string }) => escapeHtml(item.name),
|
|
235
|
+
iconExpand: 'right',
|
|
236
|
+
isViewRadio: false,
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
readonly convertItemSelected = (item: unknown): void => {
|
|
241
|
+
if (!item) return;
|
|
242
|
+
set(item as Record<string, unknown>, 'labelDisplay', escapeHtml(get(item as Record<string, string>, 'name') || ''));
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
handlerSelectMultiKey(event: IEmitMultiKey | undefined): void {
|
|
246
|
+
event?.keys;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
75
250
|
|
|
76
|
-
###
|
|
251
|
+
### 4. Dropdown Group Radio — chọn đơn trong nhóm
|
|
77
252
|
|
|
78
253
|
```html
|
|
79
254
|
<libs_ui-components-dropdown
|
|
80
|
-
[labelConfig]="{ labelLeft: 'Chọn
|
|
81
|
-
[listConfig]="
|
|
82
|
-
[listSearchConfig]="{ noBorder: true }"
|
|
255
|
+
[labelConfig]="{ labelLeft: 'Chọn một mục trong nhóm', required: true }"
|
|
256
|
+
[listConfig]="groupRadioConfig"
|
|
83
257
|
[listMaxItemShow]="5"
|
|
84
258
|
[convertItemSelected]="convertItemSelected"
|
|
85
|
-
(outSelectKey)="
|
|
259
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
86
260
|
/>
|
|
87
261
|
```
|
|
88
262
|
|
|
89
263
|
```typescript
|
|
90
|
-
|
|
91
|
-
type: '
|
|
92
|
-
httpRequestData: signal<IHttpRequestConfig>(
|
|
93
|
-
|
|
94
|
-
functionName: 'list',
|
|
95
|
-
argumentsValue: [new UtilsHttpParamsRequest({ fromObject: { page: 1, per_page: 20 } })],
|
|
96
|
-
}),
|
|
97
|
-
configTemplateText: signal({
|
|
264
|
+
readonly groupRadioConfig: IListConfigItem = {
|
|
265
|
+
type: 'group',
|
|
266
|
+
httpRequestData: signal<IHttpRequestConfig>(groupHttpConfig),
|
|
267
|
+
configTemplateGroup: signal({
|
|
98
268
|
fieldKey: 'id',
|
|
99
|
-
|
|
100
|
-
|
|
269
|
+
fieldGetItems: 'items',
|
|
270
|
+
getLabelGroup: (group: { name: string }) => escapeHtml(group.name),
|
|
271
|
+
getMaxLevelGroup: () => 2,
|
|
272
|
+
getLabelItem: (item: { name: string }) => escapeHtml(item.name),
|
|
273
|
+
iconExpand: 'right',
|
|
274
|
+
isViewRadio: true, // chỉ khác group checkbox ở dòng này
|
|
101
275
|
}),
|
|
102
276
|
};
|
|
103
277
|
```
|
|
104
278
|
|
|
105
|
-
###
|
|
279
|
+
### 5. Dropdown Radio
|
|
106
280
|
|
|
107
281
|
```html
|
|
108
282
|
<libs_ui-components-dropdown
|
|
109
|
-
[labelConfig]="{ labelLeft: 'Chọn
|
|
110
|
-
[listConfig]="
|
|
283
|
+
[labelConfig]="{ labelLeft: 'Chọn loại khách hàng', required: true }"
|
|
284
|
+
[listConfig]="radioConfig"
|
|
111
285
|
[listMaxItemShow]="5"
|
|
112
286
|
[convertItemSelected]="convertItemSelected"
|
|
113
|
-
(
|
|
287
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
114
288
|
/>
|
|
115
289
|
```
|
|
116
290
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
(
|
|
126
|
-
|
|
291
|
+
```typescript
|
|
292
|
+
readonly radioConfig: IListConfigItem = {
|
|
293
|
+
type: 'radio',
|
|
294
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
295
|
+
objectInstance: myApiService,
|
|
296
|
+
functionName: 'getList',
|
|
297
|
+
argumentsValue: [new UtilsHttpParamsRequest({ fromObject: { page: 1, per_page: 20 } })],
|
|
298
|
+
}),
|
|
299
|
+
configTemplateRadio: signal({
|
|
300
|
+
fieldKey: 'id',
|
|
301
|
+
getValue: (item: { name: string }) => escapeHtml(item.name),
|
|
302
|
+
}),
|
|
303
|
+
};
|
|
127
304
|
```
|
|
128
305
|
|
|
129
|
-
###
|
|
306
|
+
### 6. Dropdown với FunctionControl — điều khiển từ bên ngoài
|
|
130
307
|
|
|
131
308
|
```html
|
|
309
|
+
<div class="flex gap-[8px] mb-[16px]">
|
|
310
|
+
<button (click)="handlerReset()">Reset</button>
|
|
311
|
+
<button (click)="handlerCheckValid()">Check Valid</button>
|
|
312
|
+
<button (click)="handlerRefresh()">Refresh</button>
|
|
313
|
+
<button (click)="handlerSetError()">Set Error</button>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
132
316
|
<libs_ui-components-dropdown
|
|
133
|
-
[labelConfig]="{ labelLeft: '
|
|
317
|
+
[labelConfig]="{ labelLeft: 'Dropdown có điều khiển', required: true }"
|
|
134
318
|
[listConfig]="listConfig"
|
|
135
319
|
[listMaxItemShow]="5"
|
|
136
320
|
[validRequired]="{}"
|
|
137
|
-
(outFunctionsControl)="
|
|
321
|
+
(outFunctionsControl)="handlerFunctionsControl($event)"
|
|
322
|
+
(outValidEvent)="handlerValidEvent($event)"
|
|
138
323
|
/>
|
|
139
324
|
```
|
|
140
325
|
|
|
141
326
|
```typescript
|
|
142
|
-
|
|
327
|
+
import { IDropdownFunctionControlEvent, IEmitSelectKey } from '@libs-ui/components-dropdown';
|
|
328
|
+
|
|
329
|
+
private dropdownControl: IDropdownFunctionControlEvent | undefined;
|
|
143
330
|
|
|
144
|
-
|
|
331
|
+
handlerFunctionsControl(event: IDropdownFunctionControlEvent): void {
|
|
145
332
|
this.dropdownControl = event;
|
|
146
333
|
}
|
|
147
334
|
|
|
148
|
-
|
|
335
|
+
handlerValidEvent(isValid: boolean): void {
|
|
336
|
+
// true khi hợp lệ, false khi có lỗi validation
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async handlerReset(): Promise<void> {
|
|
149
340
|
await this.dropdownControl?.reset();
|
|
150
341
|
}
|
|
151
342
|
|
|
152
|
-
async
|
|
343
|
+
async handlerCheckValid(): Promise<void> {
|
|
153
344
|
const isValid = await this.dropdownControl?.checkIsValid();
|
|
154
|
-
|
|
345
|
+
// isValid: true/false
|
|
155
346
|
}
|
|
156
347
|
|
|
157
|
-
async
|
|
348
|
+
async handlerRefresh(): Promise<void> {
|
|
158
349
|
await this.dropdownControl?.refreshList();
|
|
159
350
|
}
|
|
351
|
+
|
|
352
|
+
async handlerSetError(): Promise<void> {
|
|
353
|
+
await this.dropdownControl?.setError?.('i18n_error_message');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async handlerSetItemByKey(): Promise<void> {
|
|
357
|
+
await this.dropdownControl?.setItemSelectedByKey('abc-123');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async handlerUpdateLabel(): Promise<void> {
|
|
361
|
+
await this.dropdownControl?.updateLabelItemSelected('Tên hiển thị mới');
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 7. Dropdown với Tabs phân loại dữ liệu
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import { IDropdownTabsItem } from '@libs-ui/components-dropdown';
|
|
369
|
+
|
|
370
|
+
readonly tabsConfig: IDropdownTabsItem[] = [
|
|
371
|
+
{
|
|
372
|
+
key: 'all',
|
|
373
|
+
name: 'Tất cả',
|
|
374
|
+
httpRequestData: {
|
|
375
|
+
objectInstance: allApiService,
|
|
376
|
+
functionName: 'getList',
|
|
377
|
+
argumentsValue: [],
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
key: 'active',
|
|
382
|
+
name: 'Hoạt động',
|
|
383
|
+
httpRequestData: {
|
|
384
|
+
objectInstance: activeApiService,
|
|
385
|
+
functionName: 'getList',
|
|
386
|
+
argumentsValue: [],
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
```html
|
|
393
|
+
<libs_ui-components-dropdown
|
|
394
|
+
[labelConfig]="{ labelLeft: 'Chọn với tab phân loại', required: true }"
|
|
395
|
+
[listConfig]="listConfig"
|
|
396
|
+
[tabsConfig]="tabsConfig"
|
|
397
|
+
[(tabKeyActive)]="activeTabKey"
|
|
398
|
+
[listMaxItemShow]="5"
|
|
399
|
+
[convertItemSelected]="convertItemSelected"
|
|
400
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
401
|
+
(outChangeTabKeyActive)="handlerChangeTab($event)"
|
|
402
|
+
/>
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
protected activeTabKey = signal<string>('all');
|
|
407
|
+
|
|
408
|
+
handlerChangeTab(key: string | undefined): void {
|
|
409
|
+
// key: tab vừa được chọn
|
|
410
|
+
}
|
|
160
411
|
```
|
|
161
412
|
|
|
162
|
-
###
|
|
413
|
+
### 8. Dropdown trạng thái: Disable / Readonly / Validation
|
|
163
414
|
|
|
164
415
|
```html
|
|
165
416
|
<!-- Disable -->
|
|
166
417
|
<libs_ui-components-dropdown
|
|
167
|
-
[labelConfig]="{ labelLeft: '
|
|
168
|
-
[disable]="true"
|
|
418
|
+
[labelConfig]="{ labelLeft: 'Trạng thái disable' }"
|
|
169
419
|
[listConfig]="listConfig"
|
|
420
|
+
[disable]="true"
|
|
170
421
|
/>
|
|
171
422
|
|
|
172
423
|
<!-- Readonly -->
|
|
173
424
|
<libs_ui-components-dropdown
|
|
174
|
-
[labelConfig]="{ labelLeft: '
|
|
425
|
+
[labelConfig]="{ labelLeft: 'Trạng thái readonly' }"
|
|
426
|
+
[listConfig]="listConfig"
|
|
175
427
|
[readonly]="true"
|
|
428
|
+
[(listKeySelected)]="selectedKey"
|
|
429
|
+
/>
|
|
430
|
+
|
|
431
|
+
<!-- Validation required -->
|
|
432
|
+
<libs_ui-components-dropdown
|
|
433
|
+
[labelConfig]="{ labelLeft: 'Bắt buộc chọn', required: true }"
|
|
176
434
|
[listConfig]="listConfig"
|
|
435
|
+
[validRequired]="{ message: 'i18n_field_required' }"
|
|
436
|
+
[showError]="true"
|
|
437
|
+
/>
|
|
438
|
+
|
|
439
|
+
<!-- Validation max items -->
|
|
440
|
+
<libs_ui-components-dropdown
|
|
441
|
+
[labelConfig]="{ labelLeft: 'Chọn tối đa 3', required: true }"
|
|
442
|
+
[listConfig]="checkboxConfig"
|
|
443
|
+
[validMaxItemSelected]="{ value: 3, message: 'Chỉ được chọn tối đa {value} mục', interpolateParams: { value: 3 } }"
|
|
444
|
+
/>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### 9. Dropdown với tìm kiếm online (Search Online)
|
|
448
|
+
|
|
449
|
+
```html
|
|
450
|
+
<libs_ui-components-dropdown
|
|
451
|
+
[labelConfig]="{ labelLeft: 'Tìm kiếm online', required: true }"
|
|
452
|
+
[listConfig]="listConfig"
|
|
453
|
+
[isSearchOnline]="true"
|
|
454
|
+
[listSearchConfig]="{ noBorder: true, placeholder: 'Gõ keyword để tìm kiếm...' }"
|
|
455
|
+
[listMaxItemShow]="8"
|
|
456
|
+
[convertItemSelected]="convertItemSelected"
|
|
457
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
458
|
+
/>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### 10. Dropdown lazy-load chi tiết item theo key
|
|
462
|
+
|
|
463
|
+
Dùng khi dropdown chỉ nhận được key (id) từ server, cần gọi API riêng để lấy thông tin hiển thị.
|
|
464
|
+
|
|
465
|
+
```html
|
|
466
|
+
<libs_ui-components-dropdown
|
|
467
|
+
[labelConfig]="{ labelLeft: 'Dropdown lazy-load detail' }"
|
|
468
|
+
[listConfig]="listConfig"
|
|
469
|
+
[(listKeySelected)]="selectedKey"
|
|
470
|
+
[httpRequestDetailItemById]="httpRequestDetailConfig"
|
|
471
|
+
[convertItemSelected]="convertItemSelected"
|
|
472
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
473
|
+
/>
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
readonly httpRequestDetailConfig: IHttpRequestConfig = {
|
|
478
|
+
objectInstance: detailApiService,
|
|
479
|
+
functionName: 'getDetailByKey',
|
|
480
|
+
argumentsValue: [],
|
|
481
|
+
guideAutoUpdateArgumentsValue: {
|
|
482
|
+
paging: {},
|
|
483
|
+
detailById: {
|
|
484
|
+
fieldGetValue: '',
|
|
485
|
+
fieldUpdate: '[0]',
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
selectedKey = signal<string>('item-id-123'); // sẽ tự gọi API để load tên hiển thị
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 11. Dropdown không load list trước khi search
|
|
494
|
+
|
|
495
|
+
```html
|
|
496
|
+
<libs_ui-components-dropdown
|
|
497
|
+
[labelConfig]="{ labelLeft: 'Gõ keyword để bắt đầu load' }"
|
|
498
|
+
[listConfig]="listConfig"
|
|
499
|
+
[isSearchOnline]="true"
|
|
500
|
+
[listHiddenInputSearch]="false"
|
|
501
|
+
[listSearchConfig]="{ noBorder: true, placeholder: 'Nhập để tìm kiếm...' }"
|
|
502
|
+
[dropdownIgnoreNotSearch]="true"
|
|
177
503
|
/>
|
|
178
504
|
```
|
|
179
505
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
|
210
|
-
|
|
211
|
-
| `[
|
|
212
|
-
| `[
|
|
213
|
-
| `[
|
|
214
|
-
| `[
|
|
215
|
-
| `[
|
|
216
|
-
| `[
|
|
217
|
-
| `[
|
|
218
|
-
| `[
|
|
219
|
-
| `[
|
|
220
|
-
| `[
|
|
221
|
-
| `[
|
|
222
|
-
| `[
|
|
223
|
-
| `[
|
|
224
|
-
| `[
|
|
225
|
-
| `[
|
|
226
|
-
| `[
|
|
227
|
-
| `[
|
|
228
|
-
| `[
|
|
229
|
-
| `[
|
|
230
|
-
| `[
|
|
231
|
-
| `[
|
|
232
|
-
| `[
|
|
233
|
-
| `[
|
|
234
|
-
| `[
|
|
235
|
-
| `[
|
|
236
|
-
| `[
|
|
237
|
-
| `[
|
|
238
|
-
| `[
|
|
239
|
-
| `[
|
|
240
|
-
| `[
|
|
241
|
-
| `[
|
|
242
|
-
| `[
|
|
243
|
-
| `[
|
|
244
|
-
| `[
|
|
245
|
-
| `[
|
|
246
|
-
| `[
|
|
247
|
-
| `[
|
|
248
|
-
| `[
|
|
249
|
-
| `[
|
|
250
|
-
| `[
|
|
251
|
-
| `[
|
|
252
|
-
| `[
|
|
253
|
-
| `[
|
|
254
|
-
| `[
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
|
259
|
-
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
|
273
|
-
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
|
506
|
+
### 12. Dropdown với Custom Popover Config
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
import { IPopoverCustomConfig } from '@libs-ui/components-dropdown';
|
|
510
|
+
|
|
511
|
+
readonly popoverConfig: IPopoverCustomConfig = {
|
|
512
|
+
widthByParent: false,
|
|
513
|
+
maxWidth: 500,
|
|
514
|
+
maxHeight: 400,
|
|
515
|
+
direction: 'bottom',
|
|
516
|
+
ignoreArrow: true,
|
|
517
|
+
position: { mode: 'start', distance: 0 },
|
|
518
|
+
animationConfig: { time: 200, distance: 8 },
|
|
519
|
+
};
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
```html
|
|
523
|
+
<libs_ui-components-dropdown
|
|
524
|
+
[labelConfig]="{ labelLeft: 'Dropdown custom popover' }"
|
|
525
|
+
[listConfig]="listConfig"
|
|
526
|
+
[popoverCustomConfig]="popoverConfig"
|
|
527
|
+
[zIndex]="1050"
|
|
528
|
+
[convertItemSelected]="convertItemSelected"
|
|
529
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
530
|
+
/>
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## @Input()
|
|
534
|
+
|
|
535
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
536
|
+
|---|---|---|---|---|
|
|
537
|
+
| `[allowSelectItemMultiple]` | `boolean` | `undefined` | Cho phép chọn lại item đã chọn dù key không đổi | `[allowSelectItemMultiple]="true"` |
|
|
538
|
+
| `[changeValidUndefinedResetError]` | `boolean` | `undefined` | Tự động reset error khi `validRequired` thay đổi về undefined | `[changeValidUndefinedResetError]="true"` |
|
|
539
|
+
| `[classAvatarInclude]` | `string` | `'mr-[8px]'` | Class CSS bổ sung cho avatar hiển thị bên trái label | `[classAvatarInclude]="'mr-[4px]'"` |
|
|
540
|
+
| `[classInclude]` | `string` | `undefined` | Class CSS bổ sung cho wrapper ngoài cùng của dropdown | `[classInclude]="'w-[300px]'"` |
|
|
541
|
+
| `[classIncludeContent]` | `string` | `undefined` | Class CSS bổ sung cho phần trigger content (box hiển thị item đã chọn) | `[classIncludeContent]="'h-[40px]'"` |
|
|
542
|
+
| `[classIncludeIcon]` | `string` | `'ml-[8px]'` | Class CSS bổ sung cho icon mũi tên bên phải | `[classIncludeIcon]="'ml-[4px]'"` |
|
|
543
|
+
| `[classIncludeTextDisplayWhenNoSelect]` | `string` | `'libs-ui-font-h5r'` | Class CSS cho text placeholder khi chưa chọn | `[classIncludeTextDisplayWhenNoSelect]="'libs-ui-font-h5m'"` |
|
|
544
|
+
| `[convertItemSelected]` | `(item: unknown, translate?: TranslateService) => void` | `defaultConvert` | Hàm chuyển đổi item đã chọn để lấy label hiển thị vào field `labelDisplay` | `[convertItemSelected]="convertFn"` |
|
|
545
|
+
| `[disable]` | `boolean` | `undefined` | Vô hiệu hóa dropdown, không cho tương tác | `[disable]="true"` |
|
|
546
|
+
| `[disableLabel]` | `boolean` | `undefined` | Vô hiệu hóa label phía trên dropdown | `[disableLabel]="true"` |
|
|
547
|
+
| `[dropdownTemplateRefNotSearchNoData]` | `TemplateRef<TYPE_TEMPLATE_REF>` | `undefined` | Template custom cho trạng thái "chưa search" hoặc empty state | `[dropdownTemplateRefNotSearchNoData]="myTemplate"` |
|
|
548
|
+
| `[fieldGetColorAvatar]` | `string` | `undefined` | Tên field lấy màu nền cho avatar từ item | `[fieldGetColorAvatar]="'color'"` |
|
|
549
|
+
| `[fieldGetIcon]` | `string` | `undefined` | Tên field lấy class icon từ item để hiển thị bên trái label | `[fieldGetIcon]="'iconClass'"` |
|
|
550
|
+
| `[fieldGetImage]` | `string` | `undefined` | Tên field lấy URL ảnh avatar từ item | `[fieldGetImage]="'avatar_url'"` |
|
|
551
|
+
| `[fieldGetLabel]` | `string` | `undefined` | Tên field lấy label từ item (override mặc định `label`/`name`) | `[fieldGetLabel]="'full_name'"` |
|
|
552
|
+
| `[fieldGetTextAvatar]` | `string` | `'username'` | Tên field lấy text để render avatar chữ | `[fieldGetTextAvatar]="'name'"` |
|
|
553
|
+
| `[fieldLabel]` | `string` | `'labelDisplay'` | Tên field lưu label hiển thị sau khi `convertItemSelected` chạy | `[fieldLabel]="'displayName'"` |
|
|
554
|
+
| `[flagMouse]` | `IFlagMouse` (model) | `{ isMouseEnter: false, isMouseEnterContent: false }` | Two-way binding trạng thái chuột vào/ra dropdown trigger | `[(flagMouse)]="flagMouse"` |
|
|
555
|
+
| `[flagMouseContent]` | `IFlagMouse` (model) | `undefined` | Two-way binding trạng thái chuột vào/ra vùng nội dung popover | `[(flagMouseContent)]="flagMouseContent"` |
|
|
556
|
+
| `[focusInputSearch]` | `boolean` | `true` | Tự động focus vào input tìm kiếm khi mở dropdown | `[focusInputSearch]="false"` |
|
|
557
|
+
| `[getLastTextAfterSpace]` | `boolean` | `undefined` | Lấy phần text sau khoảng trắng cuối cùng để hiển thị lên avatar | `[getLastTextAfterSpace]="true"` |
|
|
558
|
+
| `[getPopoverItemSelected]` | `(item, translate?) => Promise<IPopover \| undefined>` | `undefined` | Hàm async trả về config popover hiển thị khi hover item đã chọn | `[getPopoverItemSelected]="getPopoverFn"` |
|
|
559
|
+
| `[hasContentUnitRight]` | `boolean` | `undefined` | Bỏ border-radius góc phải để ghép với unit bên phải | `[hasContentUnitRight]="true"` |
|
|
560
|
+
| `[httpRequestDetailItemById]` | `IHttpRequestConfig` | `undefined` | Config HTTP request để lazy-load chi tiết item theo key khi chọn | `[httpRequestDetailItemById]="detailConfig"` |
|
|
561
|
+
| `[ignoreBorderBottom]` | `boolean` | `undefined` | Ẩn border bottom của tabs header | `[ignoreBorderBottom]="true"` |
|
|
562
|
+
| `[ignoreStopPropagationEvent]` | `boolean` | `false` | Bỏ qua `stopPropagation` khi click trigger | `[ignoreStopPropagationEvent]="true"` |
|
|
563
|
+
| `[imageSize]` | `TYPE_SIZE_AVATAR_CONFIG` | `16` | Kích thước avatar hiển thị (pixel) | `[imageSize]="24"` |
|
|
564
|
+
| `[isNgContent]` | `boolean` | `undefined` | Dùng `ng-content` làm trigger thay vì giao diện mặc định | `[isNgContent]="true"` |
|
|
565
|
+
| `[isSearchOnline]` | `boolean` | `false` | Gọi API mỗi lần thay đổi từ khóa tìm kiếm (search online) | `[isSearchOnline]="true"` |
|
|
566
|
+
| `[labelConfig]` | `ILabel` | `undefined` | Cấu hình label phía trên dropdown (labelLeft, required, description, buttons...) | `[labelConfig]="{ labelLeft: 'Tên', required: true }"` |
|
|
567
|
+
| `[labelPopoverConfig]` | `IPopoverOverlay` | `undefined` | Config popover tooltip cho label hiển thị item đã chọn | `[labelPopoverConfig]="{ maxWidth: 300 }"` |
|
|
568
|
+
| `[labelPopoverFullWidth]` | `boolean` | `true` | Label popover chiếm full width | `[labelPopoverFullWidth]="false"` |
|
|
569
|
+
| `[lengthKeys]` | `number` (model) | `0` | Two-way binding số lượng key đã chọn | `[(lengthKeys)]="selectedCount"` |
|
|
570
|
+
| `[linkImageError]` | `string` | `undefined` | URL ảnh fallback khi ảnh avatar bị lỗi | `[linkImageError]="'/assets/default.png'"` |
|
|
571
|
+
| `[listBackgroundCustom]` | `string` | `undefined` | Background color custom cho danh sách | `[listBackgroundCustom]="'#f5f5f5'"` |
|
|
572
|
+
| `[listButtonsOther]` | `Array<IButton>` | `undefined` | Các button bổ sung hiển thị ở cuối danh sách | `[listButtonsOther]="extraButtons"` |
|
|
573
|
+
| `[listClickExactly]` | `boolean` | `undefined` | Chỉ phản hồi click chính xác vào item (không phải vùng padding) | `[listClickExactly]="true"` |
|
|
574
|
+
| `[listConfig]` | `IListConfigItem` | `undefined` | **Bắt buộc.** Cấu hình danh sách (type, httpRequestData, template config) | `[listConfig]="myListConfig"` |
|
|
575
|
+
| `[listConfigHasDivider]` | `boolean` | `true` | Hiển thị đường divider trong danh sách | `[listConfigHasDivider]="false"` |
|
|
576
|
+
| `[listDividerClassInclude]` | `string` | `undefined` | Class CSS bổ sung cho đường divider | `[listDividerClassInclude]="'my-[4px]'"` |
|
|
577
|
+
| `[listHasButtonUnSelectOption]` | `boolean` | `auto` | Hiển thị nút "Bỏ chọn" trong danh sách | `[listHasButtonUnSelectOption]="true"` |
|
|
578
|
+
| `[listHiddenInputSearch]` | `boolean` | `undefined` | Ẩn input tìm kiếm | `[listHiddenInputSearch]="true"` |
|
|
579
|
+
| `[listIgnoreClassDisableDefaultWhenUseKeysDisableItem]` | `boolean` | `undefined` | Bỏ class style disable mặc định khi dùng `listKeysDisable` (để tự xử lý styling) | `[listIgnoreClassDisableDefaultWhenUseKeysDisableItem]="true"` |
|
|
580
|
+
| `[listKeysDisable]` | `Array<string>` | `undefined` | Danh sách key của item bị vô hiệu hóa trong list (không cho chọn) | `[listKeysDisable]="['id1', 'id2']"` |
|
|
581
|
+
| `[listKeysHidden]` | `Array<string>` | `undefined` | Danh sách key của item bị ẩn trong list | `[listKeysHidden]="['id3']"` |
|
|
582
|
+
| `[listKeySearch]` | `string` | `undefined` | Tên field dùng để tìm kiếm trong danh sách (mặc định dùng field label) | `[listKeySearch]="'name'"` |
|
|
583
|
+
| `[(listKeySelected)]` | `unknown` (model) | `undefined` | Two-way binding key item đang được chọn (chọn đơn) | `[(listKeySelected)]="selectedId"` |
|
|
584
|
+
| `[listMaxItemShow]` | `number` | `5` | Số item tối đa hiển thị trong danh sách trước khi cuộn (`-1` = không giới hạn) | `[listMaxItemShow]="8"` |
|
|
585
|
+
| `[(listMultiKeySelected)]` | `Array<unknown>` (model) | `undefined` | Two-way binding mảng key đang được chọn (chọn nhiều) | `[(listMultiKeySelected)]="selectedIds"` |
|
|
586
|
+
| `[listSearchConfig]` | `IInputSearchConfig` | `{ noBorder: true }` | Cấu hình input tìm kiếm (placeholder, noBorder...) | `[listSearchConfig]="{ noBorder: true, placeholder: 'Tìm kiếm...' }"` |
|
|
587
|
+
| `[listSearchNoDataTemplateRef]` | `TemplateRef<unknown>` | `undefined` | Template khi tìm kiếm không có kết quả | `[listSearchNoDataTemplateRef]="noDataTpl"` |
|
|
588
|
+
| `[listSearchPadding]` | `boolean` | `undefined` | Thêm padding cho phần search | `[listSearchPadding]="true"` |
|
|
589
|
+
| `[onlyEmitDataWhenReset]` | `boolean` | `undefined` | Chỉ emit `outSelectKey`/`outSelectMultiKey` (với undefined) khi gọi `reset()` | `[onlyEmitDataWhenReset]="true"` |
|
|
590
|
+
| `[popoverCustomConfig]` | `IPopoverCustomConfig` | `undefined` | Cấu hình tùy chỉnh cho popover overlay (width, direction, maxHeight...) | `[popoverCustomConfig]="popoverConfig"` |
|
|
591
|
+
| `[popoverElementRefCustom]` | `HTMLElement` | `undefined` | Element HTML tùy chỉnh làm anchor cho popover | `[popoverElementRefCustom]="myEl"` |
|
|
592
|
+
| `[readonly]` | `boolean` | `undefined` | Chế độ chỉ đọc (hiển thị nhưng không cho tương tác) | `[readonly]="true"` |
|
|
593
|
+
| `[resetKeyWhenSelectAllKey]` | `boolean` | `undefined` | Reset key đã chọn khi checkbox "Chọn tất cả" được nhấn | `[resetKeyWhenSelectAllKey]="true"` |
|
|
594
|
+
| `[(showBorderError)]` | `boolean` (model) | `undefined` | Two-way binding hiển thị border màu đỏ (lỗi) | `[(showBorderError)]="hasError"` |
|
|
595
|
+
| `[showError]` | `boolean` | `true` | Hiển thị thông báo lỗi validation dưới dropdown | `[showError]="false"` |
|
|
596
|
+
| `[(tabKeyActive)]` | `string` (model) | `undefined` | Two-way binding key của tab đang active | `[(tabKeyActive)]="activeTab"` |
|
|
597
|
+
| `[tabsConfig]` | `Array<IDropdownTabsItem>` | `undefined` | Cấu hình tabs hiển thị phía trên danh sách | `[tabsConfig]="tabsConfig"` |
|
|
598
|
+
| `[textDisplayWhenMultiSelect]` | `string` | `'i18n_selecting_options'` | Text hiển thị khi chọn nhiều hơn 1 item | `[textDisplayWhenMultiSelect]="'i18n_selected_count'"` |
|
|
599
|
+
| `[textDisplayWhenNoSelect]` | `string` | `'i18n_select_information'` | Text placeholder hiển thị khi chưa chọn item nào | `[textDisplayWhenNoSelect]="'i18n_choose_option'"` |
|
|
600
|
+
| `[typeShape]` | `TYPE_SHAPE_AVATAR` | `'circle'` | Hình dạng của avatar (`'circle'` hoặc `'square'`) | `[typeShape]="'square'"` |
|
|
601
|
+
| `[useXssFilter]` | `boolean` | `false` | Bật XSS filter cho nội dung label hiển thị | `[useXssFilter]="true"` |
|
|
602
|
+
| `[validMaxItemSelected]` | `IValidMaxItemSelected` | `undefined` | Cấu hình validation giới hạn số item chọn tối đa | `[validMaxItemSelected]="{ value: 5, message: 'Tối đa 5 mục' }"` |
|
|
603
|
+
| `[validRequired]` | `IMessageTranslate` | `undefined` | Bật validation bắt buộc chọn; truyền `{}` để dùng message mặc định | `[validRequired]="{ message: 'i18n_required' }"` |
|
|
604
|
+
| `[zIndex]` | `number` | `undefined` | z-index cho popover overlay (mặc định 1000) | `[zIndex]="1050"` |
|
|
605
|
+
|
|
606
|
+
## @Output()
|
|
607
|
+
|
|
608
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
609
|
+
|---|---|---|---|---|
|
|
610
|
+
| `(outChangStageFlagMouse)` | `IFlagMouse` | Emit khi trạng thái hover chuột vào/ra dropdown thay đổi (merge cả trigger và content) | `handlerChangStageFlagMouse(e: IFlagMouse): void { e; }` | `(outChangStageFlagMouse)="handlerChangStageFlagMouse($event)"` |
|
|
611
|
+
| `(outChangeTabKeyActive)` | `string \| undefined` | Emit key của tab vừa được chọn | `handlerChangeTabKeyActive(e: string \| undefined): void { e; }` | `(outChangeTabKeyActive)="handlerChangeTabKeyActive($event)"` |
|
|
612
|
+
| `(outClickButtonOther)` | `IButton` | Emit khi click vào button bổ sung trong danh sách (`listButtonsOther`) | `handlerClickButtonOther(e: IButton): void { e.stopPropagation(); }` | `(outClickButtonOther)="handlerClickButtonOther($event)"` |
|
|
613
|
+
| `(outDataChange)` | `Array<unknown>` | Emit toàn bộ danh sách hiện tại mỗi khi dữ liệu list thay đổi (sau load/refresh) | `handlerDataChange(e: Array<unknown>): void { e; }` | `(outDataChange)="handlerDataChange($event)"` |
|
|
614
|
+
| `(outFunctionsControl)` | `IDropdownFunctionControlEvent` | Emit object chứa các hàm điều khiển dropdown ngay sau `ngOnInit` | `handlerFunctionsControl(e: IDropdownFunctionControlEvent): void { this.ctrl = e; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
|
|
615
|
+
| `(outSelectKey)` | `IEmitSelectKey \| undefined` | Emit khi chọn 1 item (single select: type text/radio); `undefined` khi bỏ chọn | `handlerSelectKey(e: IEmitSelectKey \| undefined): void { e?.key; }` | `(outSelectKey)="handlerSelectKey($event)"` |
|
|
616
|
+
| `(outSelectMultiKey)` | `IEmitMultiKey \| undefined` | Emit khi chọn/bỏ chọn items (multi select: type checkbox/group); `undefined` khi reset | `handlerSelectMultiKey(e: IEmitMultiKey \| undefined): void { e?.keys; }` | `(outSelectMultiKey)="handlerSelectMultiKey($event)"` |
|
|
617
|
+
| `(outShowList)` | `boolean` | Emit `true` khi mở dropdown, `false` khi đóng | `handlerShowList(e: boolean): void { e; }` | `(outShowList)="handlerShowList($event)"` |
|
|
618
|
+
| `(outValidEvent)` | `boolean` | Emit kết quả validation mỗi khi thay đổi lựa chọn (`true` = hợp lệ) | `handlerValidEvent(e: boolean): void { e; }` | `(outValidEvent)="handlerValidEvent($event)"` |
|
|
619
|
+
|
|
620
|
+
## FunctionControl Methods
|
|
621
|
+
|
|
622
|
+
`IDropdownFunctionControlEvent` được emit qua `(outFunctionsControl)` ngay sau `ngOnInit`. Lưu vào biến và gọi khi cần:
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
private dropdownControl: IDropdownFunctionControlEvent | undefined;
|
|
626
|
+
|
|
627
|
+
handlerFunctionsControl(event: IDropdownFunctionControlEvent): void {
|
|
628
|
+
this.dropdownControl = event;
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
| Method | Signature | Mô tả |
|
|
633
|
+
|---|---|---|
|
|
634
|
+
| `checkIsValid()` | `() => Promise<boolean>` | Chạy validation và trả về `true` nếu hợp lệ, `false` nếu có lỗi |
|
|
635
|
+
| `getDisable()` | `() => Promise<boolean>` | Lấy trạng thái disable hiện tại |
|
|
636
|
+
| `refreshList()` | `() => Promise<void>` | Reload lại danh sách từ API (gọi `httpRequestData` lại từ đầu) |
|
|
637
|
+
| `removeList()` | `() => Promise<void>` | Đóng popover danh sách nếu đang mở |
|
|
638
|
+
| `reset()` | `() => Promise<void>` | Reset về trạng thái ban đầu: xóa lựa chọn, xóa error |
|
|
639
|
+
| `resetError()` | `() => Promise<void>` | Xóa error message đang hiển thị và border đỏ |
|
|
640
|
+
| `setError(message)` | `(message: string) => Promise<void>` | Hiển thị error message tùy chỉnh (có thể truyền i18n key) |
|
|
641
|
+
| `setItemSelectedByKey(id)` | `(id: unknown) => Promise<void>` | Chọn item theo key, tự gọi API `httpRequestDetailItemById` nếu có để lấy chi tiết |
|
|
642
|
+
| `updateLabelItemSelected(label)` | `(label: string) => Promise<void>` | Cập nhật text hiển thị của item đã chọn mà không cần reload |
|
|
283
643
|
|
|
284
644
|
## Types & Interfaces
|
|
285
645
|
|
|
286
646
|
```typescript
|
|
647
|
+
import {
|
|
648
|
+
IEmitSelectKey,
|
|
649
|
+
IEmitMultiKey,
|
|
650
|
+
IPopoverCustomConfig,
|
|
651
|
+
IDropdownTabsItem,
|
|
652
|
+
IValidMaxItemSelected,
|
|
653
|
+
IDropdown,
|
|
654
|
+
IDropdownFunctionControlEvent,
|
|
655
|
+
} from '@libs-ui/components-dropdown';
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
// Dữ liệu emit khi chọn 1 item (single select)
|
|
287
660
|
interface IEmitSelectKey {
|
|
288
|
-
key?: unknown;
|
|
289
|
-
item?: any;
|
|
290
|
-
isClickManual?: boolean;
|
|
291
|
-
tabKeyActive?: string;
|
|
661
|
+
key?: unknown; // id / value của item đã chọn
|
|
662
|
+
item?: any; // object item đầy đủ từ danh sách
|
|
663
|
+
isClickManual?: boolean; // true nếu người dùng click trực tiếp
|
|
664
|
+
tabKeyActive?: string; // key tab đang active khi chọn (nếu có tabs)
|
|
292
665
|
}
|
|
293
666
|
|
|
667
|
+
// Dữ liệu emit khi chọn nhiều items (multi select)
|
|
294
668
|
interface IEmitMultiKey {
|
|
295
|
-
keys?: Array<unknown>;
|
|
296
|
-
mapKeys?: Array<IEmitSelectKey>;
|
|
669
|
+
keys?: Array<unknown>; // mảng id / value đã chọn
|
|
670
|
+
mapKeys?: Array<IEmitSelectKey>; // mảng chi tiết từng item đã chọn
|
|
297
671
|
isClickManual?: boolean;
|
|
298
672
|
tabKeyActive?: string;
|
|
299
673
|
}
|
|
300
674
|
|
|
675
|
+
// Cấu hình tùy chỉnh popover overlay
|
|
301
676
|
interface IPopoverCustomConfig {
|
|
302
|
-
widthByParent?: boolean;
|
|
303
|
-
parentBorderWidth?: number;
|
|
304
|
-
maxHeight?: number;
|
|
305
|
-
maxWidth?: number;
|
|
306
|
-
direction?: TYPE_POPOVER_DIRECTION;
|
|
307
|
-
ignoreArrow?: boolean;
|
|
308
|
-
classInclude?: string;
|
|
309
|
-
disable?: boolean;
|
|
310
|
-
clickExactly?: boolean;
|
|
311
|
-
paddingLeftItem?: boolean;
|
|
312
|
-
timerDestroy?: number;
|
|
313
|
-
position?: {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
677
|
+
widthByParent?: boolean; // width bằng element cha
|
|
678
|
+
parentBorderWidth?: number; // bù trừ border của element cha (px)
|
|
679
|
+
maxHeight?: number; // chiều cao tối đa popup (px)
|
|
680
|
+
maxWidth?: number; // chiều rộng tối đa popup (px)
|
|
681
|
+
direction?: TYPE_POPOVER_DIRECTION; // hướng mở: 'bottom' | 'top' | 'left' | 'right'
|
|
682
|
+
ignoreArrow?: boolean; // ẩn mũi tên
|
|
683
|
+
classInclude?: string; // class CSS bổ sung cho container popup
|
|
684
|
+
disable?: boolean; // vô hiệu hóa popover
|
|
685
|
+
clickExactly?: boolean; // chỉ mở khi click chính xác
|
|
686
|
+
paddingLeftItem?: boolean; // thêm padding trái cho item
|
|
687
|
+
timerDestroy?: number; // thời gian trễ trước khi destroy (ms)
|
|
688
|
+
position?: {
|
|
689
|
+
mode: TYPE_POPOVER_POSITION_MODE; // 'start' | 'center' | 'end'
|
|
690
|
+
distance: number; // khoảng cách từ điểm neo (px)
|
|
691
|
+
};
|
|
692
|
+
animationConfig?: {
|
|
693
|
+
time?: number; // thời gian animation (ms)
|
|
694
|
+
distance?: number; // khoảng cách dịch chuyển animation (px)
|
|
695
|
+
};
|
|
696
|
+
width?: number; // chiều rộng cố định (px)
|
|
697
|
+
classIncludeOverlayBody?: string; // class CSS cho overlay body
|
|
317
698
|
}
|
|
318
699
|
|
|
700
|
+
// Cấu hình từng tab trong dropdown có tabs
|
|
319
701
|
interface IDropdownTabsItem {
|
|
320
|
-
key: string;
|
|
321
|
-
name: string;
|
|
322
|
-
httpRequestData?: IHttpRequestConfig;
|
|
702
|
+
key: string; // định danh duy nhất của tab
|
|
703
|
+
name: string; // tên hiển thị (hỗ trợ i18n key)
|
|
704
|
+
httpRequestData?: IHttpRequestConfig; // config API riêng cho tab này
|
|
323
705
|
}
|
|
324
706
|
|
|
707
|
+
// Validation giới hạn số item được chọn tối đa
|
|
325
708
|
interface IValidMaxItemSelected extends IMessageTranslate {
|
|
326
|
-
value: number;
|
|
709
|
+
value: number; // số lượng tối đa cho phép chọn
|
|
327
710
|
}
|
|
328
711
|
|
|
712
|
+
// Cấu hình dropdown dùng để truyền vào service/helper
|
|
329
713
|
interface IDropdown {
|
|
330
714
|
listConfig: IListConfigItem;
|
|
331
715
|
listBackgroundListCustom?: string;
|
|
@@ -338,6 +722,7 @@ interface IDropdown {
|
|
|
338
722
|
disable?: boolean;
|
|
339
723
|
}
|
|
340
724
|
|
|
725
|
+
// Interface FunctionControl để điều khiển dropdown từ bên ngoài
|
|
341
726
|
interface IDropdownFunctionControlEvent {
|
|
342
727
|
checkIsValid: () => Promise<boolean>;
|
|
343
728
|
resetError: () => Promise<void>;
|
|
@@ -351,62 +736,41 @@ interface IDropdownFunctionControlEvent {
|
|
|
351
736
|
}
|
|
352
737
|
```
|
|
353
738
|
|
|
354
|
-
|
|
355
|
-
|------|--------|
|
|
356
|
-
| `IEmitSelectKey` | Dữ liệu emit khi chọn 1 item (single select) |
|
|
357
|
-
| `IEmitMultiKey` | Dữ liệu emit khi chọn nhiều items (multi select) |
|
|
358
|
-
| `IPopoverCustomConfig` | Cấu hình tùy chỉnh popover overlay |
|
|
359
|
-
| `IDropdownTabsItem` | Cấu hình cho từng tab trong dropdown |
|
|
360
|
-
| `IValidMaxItemSelected` | Cấu hình validation giới hạn số item được chọn |
|
|
361
|
-
| `IDropdown` | Interface cấu hình dropdown |
|
|
362
|
-
| `IDropdownFunctionControlEvent` | Interface chứa các methods để điều khiển dropdown từ component cha |
|
|
363
|
-
|
|
364
|
-
## Dependencies
|
|
365
|
-
|
|
366
|
-
| Package | Version | Mục đích |
|
|
367
|
-
|---------|---------|----------|
|
|
368
|
-
| `@libs-ui/components-avatar` | `0.2.355-14` | Hiển thị avatar trong dropdown |
|
|
369
|
-
| `@libs-ui/components-buttons-button` | `0.2.355-14` | Button interface |
|
|
370
|
-
| `@libs-ui/components-inputs-search` | `0.2.355-14` | Input tìm kiếm |
|
|
371
|
-
| `@libs-ui/components-inputs-valid` | `0.2.355-14` | Validation input |
|
|
372
|
-
| `@libs-ui/components-label` | `0.2.355-14` | Label component |
|
|
373
|
-
| `@libs-ui/components-list` | `0.2.355-14` | Danh sách hiển thị |
|
|
374
|
-
| `@libs-ui/components-popover` | `0.2.355-14` | Popover overlay |
|
|
375
|
-
| `@libs-ui/pipes-security-trust` | `0.2.355-14` | Security trust pipe |
|
|
376
|
-
| `@libs-ui/services-http-request` | `0.2.355-14` | HTTP request service |
|
|
377
|
-
| `@libs-ui/utils` | `0.2.355-14` | Utilities |
|
|
378
|
-
| `@ngx-translate/core` | `^15.0.0` | Đa ngôn ngữ |
|
|
379
|
-
|
|
380
|
-
## Công nghệ
|
|
381
|
-
|
|
382
|
-
| Technology | Version | Purpose |
|
|
383
|
-
|------------|---------|---------|
|
|
384
|
-
| Angular | 18+ | Framework |
|
|
385
|
-
| Angular Signals | - | State management |
|
|
386
|
-
| TailwindCSS | 3.x | Styling |
|
|
387
|
-
| OnPush | - | Change Detection |
|
|
739
|
+
## Sub-Component: libs_ui-components-dropdown-tabs
|
|
388
740
|
|
|
389
|
-
|
|
741
|
+
Component tabs nội bộ, được sử dụng tự động khi truyền `[tabsConfig]` vào dropdown chính. Không cần import riêng.
|
|
390
742
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
743
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
744
|
+
|---|---|---|---|---|
|
|
745
|
+
| `[tabsConfig]` | `Array<IDropdownTabsItem>` | `undefined` | Danh sách cấu hình tab | `[tabsConfig]="tabs"` |
|
|
746
|
+
| `[(tabKeyActive)]` | `string` (model) | `undefined` | Two-way binding key tab đang active | `[(tabKeyActive)]="activeKey"` |
|
|
747
|
+
| `[ignoreBorderBottom]` | `boolean` | `undefined` | Ẩn border bottom của tabs bar | `[ignoreBorderBottom]="true"` |
|
|
748
|
+
| `[disable]` | `boolean` | `undefined` | Vô hiệu hóa toàn bộ tabs | `[disable]="true"` |
|
|
394
749
|
|
|
395
|
-
|
|
750
|
+
| Output | Type | Mô tả |
|
|
751
|
+
|---|---|---|
|
|
752
|
+
| `(outChange)` | `void` | Emit mỗi khi chọn tab mới |
|
|
396
753
|
|
|
397
|
-
##
|
|
754
|
+
## Lưu ý quan trọng
|
|
398
755
|
|
|
399
|
-
|
|
400
|
-
# Chạy tests
|
|
401
|
-
npx nx test components-dropdown
|
|
756
|
+
⚠️ **Bắt buộc `[listConfig]`**: Dropdown không hoạt động nếu thiếu `[listConfig]`. Đây là input quan trọng nhất xác định type hiển thị và nguồn dữ liệu.
|
|
402
757
|
|
|
403
|
-
|
|
404
|
-
npx nx test components-dropdown --coverage
|
|
758
|
+
⚠️ **Chế độ chọn đơn vs nhiều**: Dùng `[(listKeySelected)]` + `(outSelectKey)` cho type `text`/`radio`. Dùng `[(listMultiKeySelected)]` + `(outSelectMultiKey)` cho type `checkbox`/`group`.
|
|
405
759
|
|
|
406
|
-
|
|
407
|
-
npx nx test components-dropdown --watch
|
|
408
|
-
```
|
|
760
|
+
⚠️ **`convertItemSelected` là bắt buộc để hiển thị đúng label**: Hàm này cần gán giá trị vào field `labelDisplay` (hoặc field được chỉ định bởi `[fieldLabel]`) của item. Thiếu hàm này dropdown sẽ hiển thị trắng sau khi chọn.
|
|
409
761
|
|
|
410
|
-
|
|
762
|
+
⚠️ **FunctionControl phát sinh sau ngOnInit**: `(outFunctionsControl)` emit 1 lần duy nhất trong `ngOnInit`. Lưu tham chiếu vào biến class để dùng sau.
|
|
411
763
|
|
|
412
|
-
|
|
764
|
+
⚠️ **`listKeysDisable` không dùng kèm `configCheckboxCheckAll`**: Khi listConfig có cấu hình `configCheckboxCheckAll`, không được dùng `[listKeysDisable]` vì sẽ gây lỗi logic chọn tất cả.
|
|
765
|
+
|
|
766
|
+
⚠️ **`listKeysHidden` không dùng kèm `configCheckboxCheckAll`**: Tương tự `listKeysDisable`, tránh dùng kết hợp với `configCheckboxCheckAll`.
|
|
767
|
+
|
|
768
|
+
⚠️ **XSS Safety**: Khi `getValue`/`getLabelItem` trả về HTML có thể chứa dữ liệu từ người dùng, BẮT BUỘC bọc trong `escapeHtml()` từ `@libs-ui/utils`.
|
|
769
|
+
|
|
770
|
+
## Demo
|
|
771
|
+
|
|
772
|
+
```bash
|
|
773
|
+
npx nx serve core-ui
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
Truy cập: http://localhost:4500/dropdown
|