@libs-ui/components-inputs-multi-language 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,160 +1,454 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @libs-ui/components-inputs-multi-language
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Component nhập liệu đa ngôn ngữ và đa tiền tệ cho Angular, hỗ trợ nhiều kiểu input và validation tích hợp.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- 💰 **Đa tiền tệ**: Chế độ `integer` cho phép nhập liệu các giá trị tiền tệ đi kèm với đơn vị tiền tệ tương ứng.
|
|
9
|
-
- 🛠️ **Đa dạng kiểu View**: Hỗ trợ `text`, `number`, `integer`, `float`, `editor` (Quill) và `textarea`.
|
|
10
|
-
- ✅ **Validation tích hợp**: Kiểm tra tính hợp lệ của dữ liệu (bắt buộc, độ dài tối thiểu/tối đa) cho từng ngôn ngữ.
|
|
11
|
-
- 🔄 **Quản lý linh hoạt**: Thêm/Xóa ngôn ngữ dễ dàng qua giao diện người dùng.
|
|
12
|
-
- 📱 **Responsive Layout**: Hỗ trợ hiển thị theo hàng (`row`) hoặc cột (`column`).
|
|
13
|
-
- 🎨 **Tùy biến giao diện**: Dễ dàng tùy chỉnh class CSS thông qua các inputs.
|
|
7
|
+
`@libs-ui/components-inputs-multi-language` cung cấp hai component chính: `LibsUiComponentsInputsMultiLanguageComponent` để nhập liệu cho một trường dữ liệu trên nhiều ngôn ngữ/tiền tệ, và `LibsUiComponentsInputsMultiLanguageItemsComponent` để quản lý nhiều khối nhập liệu có thể thêm/xóa động. Cả hai component đều hỗ trợ validation tích hợp, giao tiếp qua `FunctionsControl` getter, và hoạt động với Angular Signals + OnPush change detection.
|
|
14
8
|
|
|
15
|
-
##
|
|
9
|
+
## Tính năng
|
|
10
|
+
|
|
11
|
+
- ✅ Nhập liệu đồng thời nhiều ngôn ngữ (vi, en, ...) hoặc nhiều tiền tệ (VND, USD, EUR, ...)
|
|
12
|
+
- ✅ Hỗ trợ 6 kiểu view: `text`, `integer`, `number`, `float`, `bigint`, `editor` (Quill rich text)
|
|
13
|
+
- ✅ Thêm/xóa ngôn ngữ động qua giao diện người dùng
|
|
14
|
+
- ✅ Validation tích hợp: required, min length, max length, pattern, đếm ký tự
|
|
15
|
+
- ✅ Two-way binding qua Angular `model()` Signal API (`[(dataLanguage)]`)
|
|
16
|
+
- ✅ Chế độ đơn ngôn ngữ (`singleLanguage`) để chỉ hiển thị ngôn ngữ mặc định
|
|
17
|
+
- ✅ Chế độ chỉ đọc (`readonly`) và chế độ xem nội dung (`viewContent`)
|
|
18
|
+
- ✅ Layout linh hoạt: hàng ngang (`row`) hoặc cột dọc (`column`)
|
|
19
|
+
- ✅ API `FunctionsControl` để validate và lấy dữ liệu từ component cha qua `ViewChild`
|
|
20
|
+
- ✅ `LibsUiComponentsInputsMultiLanguageItemsComponent` quản lý nhiều khối nhập liệu có thể thêm/xóa
|
|
21
|
+
|
|
22
|
+
## Khi nào sử dụng
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
- Khi xây dựng form quản trị nội dung hỗ trợ đa ngôn ngữ (tên sản phẩm, mô tả, tiêu đề banner)
|
|
25
|
+
- Khi cần nhập đồng thời giá trị cho nhiều tiền tệ (VND, USD, EUR) trên cùng một trường
|
|
26
|
+
- Khi cần trình soạn thảo văn bản giàu tính năng (Quill) cho nội dung đa ngôn ngữ
|
|
27
|
+
- Khi có nhiều "khối" cấu hình lặp lại (nhiều banner, nhiều biến thể sản phẩm) mỗi khối cần nhập đa ngôn ngữ
|
|
28
|
+
- Khi cần validate dữ liệu đa ngôn ngữ theo logic: nếu nhập một ngôn ngữ phụ thì ngôn ngữ mặc định bắt buộc phải có
|
|
29
|
+
|
|
30
|
+
## Cài đặt
|
|
18
31
|
|
|
19
32
|
```bash
|
|
20
33
|
npm install @libs-ui/components-inputs-multi-language
|
|
21
34
|
```
|
|
22
35
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
### Import Module
|
|
36
|
+
## Import
|
|
26
37
|
|
|
27
38
|
```typescript
|
|
39
|
+
// Component nhập liệu đa ngôn ngữ đơn trường
|
|
28
40
|
import { LibsUiComponentsInputsMultiLanguageComponent } from '@libs-ui/components-inputs-multi-language';
|
|
29
41
|
|
|
42
|
+
// Component quản lý nhiều khối nhập liệu
|
|
43
|
+
import { LibsUiComponentsInputsMultiLanguageItemsComponent } from '@libs-ui/components-inputs-multi-language';
|
|
44
|
+
|
|
45
|
+
// Interfaces và types
|
|
46
|
+
import {
|
|
47
|
+
IDataItem,
|
|
48
|
+
IDataMultiLanguage,
|
|
49
|
+
IDataLanguage,
|
|
50
|
+
IEventMultiLanguage,
|
|
51
|
+
IConfigMultiLanguageHeader,
|
|
52
|
+
IInputMultiLanguageFunctionControlEvent,
|
|
53
|
+
IOption,
|
|
54
|
+
ICurrencyUnit,
|
|
55
|
+
TYPE_VIEW_MULTI_LANGUAGE,
|
|
56
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Ví dụ sử dụng
|
|
60
|
+
|
|
61
|
+
### Ví dụ 1 — Nhập liệu văn bản cơ bản (text)
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { Component, signal, viewChild } from '@angular/core';
|
|
65
|
+
import {
|
|
66
|
+
LibsUiComponentsInputsMultiLanguageComponent,
|
|
67
|
+
IDataMultiLanguage,
|
|
68
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
69
|
+
|
|
70
|
+
@Component({
|
|
71
|
+
selector: 'app-product-form',
|
|
72
|
+
standalone: true,
|
|
73
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
74
|
+
imports: [LibsUiComponentsInputsMultiLanguageComponent],
|
|
75
|
+
template: `
|
|
76
|
+
<libs_ui-components-inputs-multi_language
|
|
77
|
+
#nameField
|
|
78
|
+
[(dataLanguage)]="productName"
|
|
79
|
+
[titleField]="'Tên sản phẩm'"
|
|
80
|
+
[placeholder]="'Nhập tên sản phẩm...'"
|
|
81
|
+
[validRequired]="{ isRequired: true, message: 'Tên sản phẩm là bắt buộc' }"
|
|
82
|
+
(outChangeValueInput)="handlerNameChange()"
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<button (click)="handlerSave()">Lưu</button>
|
|
86
|
+
`,
|
|
87
|
+
})
|
|
88
|
+
export class ProductFormComponent {
|
|
89
|
+
protected productName = signal<IDataMultiLanguage>({
|
|
90
|
+
vi: 'Áo thun nam',
|
|
91
|
+
en: 'Men T-Shirt',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
private readonly nameFieldRef = viewChild<LibsUiComponentsInputsMultiLanguageComponent>('nameField');
|
|
95
|
+
|
|
96
|
+
protected handlerNameChange(): void {
|
|
97
|
+
// xử lý khi giá trị thay đổi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected async handlerSave(): Promise<void> {
|
|
101
|
+
const isValid = await this.nameFieldRef()?.FunctionsControl.checkIsValid();
|
|
102
|
+
if (!isValid) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const data = this.nameFieldRef()?.FunctionsControl.getData();
|
|
106
|
+
// gửi data lên API
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Ví dụ 2 — Nhập liệu tiền tệ (integer)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Component, signal } from '@angular/core';
|
|
115
|
+
import {
|
|
116
|
+
LibsUiComponentsInputsMultiLanguageComponent,
|
|
117
|
+
IDataMultiLanguage,
|
|
118
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
119
|
+
|
|
120
|
+
@Component({
|
|
121
|
+
selector: 'app-price-form',
|
|
122
|
+
standalone: true,
|
|
123
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
124
|
+
imports: [LibsUiComponentsInputsMultiLanguageComponent],
|
|
125
|
+
template: `
|
|
126
|
+
<libs_ui-components-inputs-multi_language
|
|
127
|
+
[viewType]="'integer'"
|
|
128
|
+
[(dataLanguage)]="priceData"
|
|
129
|
+
[titleField]="'Giá bán lẻ'"
|
|
130
|
+
[placeholder]="'0'"
|
|
131
|
+
[validRequired]="{ isRequired: true, message: 'Vui lòng nhập giá' }"
|
|
132
|
+
/>
|
|
133
|
+
`,
|
|
134
|
+
})
|
|
135
|
+
export class PriceFormComponent {
|
|
136
|
+
protected priceData = signal<IDataMultiLanguage>({
|
|
137
|
+
VND: 1500000,
|
|
138
|
+
USD: 60,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Ví dụ 3 — Trình soạn thảo Quill (editor)
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { Component, signal } from '@angular/core';
|
|
147
|
+
import {
|
|
148
|
+
LibsUiComponentsInputsMultiLanguageComponent,
|
|
149
|
+
IDataMultiLanguage,
|
|
150
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
151
|
+
|
|
30
152
|
@Component({
|
|
153
|
+
selector: 'app-description-form',
|
|
31
154
|
standalone: true,
|
|
155
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
32
156
|
imports: [LibsUiComponentsInputsMultiLanguageComponent],
|
|
33
|
-
|
|
157
|
+
template: `
|
|
158
|
+
<libs_ui-components-inputs-multi_language
|
|
159
|
+
[viewType]="'editor'"
|
|
160
|
+
[(dataLanguage)]="descriptionData"
|
|
161
|
+
[titleField]="'Mô tả chi tiết'"
|
|
162
|
+
[viewPosition]="'column'"
|
|
163
|
+
/>
|
|
164
|
+
`,
|
|
34
165
|
})
|
|
35
|
-
export class
|
|
166
|
+
export class DescriptionFormComponent {
|
|
167
|
+
protected descriptionData = signal<IDataMultiLanguage>({
|
|
168
|
+
vi: '<p>Mô tả sản phẩm <strong>chi tiết</strong>.</p>',
|
|
169
|
+
en: '<p>Detailed product <strong>description</strong>.</p>',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
36
172
|
```
|
|
37
173
|
|
|
38
|
-
### Ví dụ
|
|
174
|
+
### Ví dụ 4 — Nhiều khối nhập liệu (Items)
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { Component, signal, viewChild } from '@angular/core';
|
|
178
|
+
import {
|
|
179
|
+
LibsUiComponentsInputsMultiLanguageItemsComponent,
|
|
180
|
+
IDataItem,
|
|
181
|
+
IDataMultiLanguage,
|
|
182
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
39
183
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
[
|
|
184
|
+
@Component({
|
|
185
|
+
selector: 'app-banner-form',
|
|
186
|
+
standalone: true,
|
|
187
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
188
|
+
imports: [LibsUiComponentsInputsMultiLanguageItemsComponent],
|
|
189
|
+
template: `
|
|
190
|
+
<libs_ui-components-inputs-multi_language-items
|
|
191
|
+
#bannerItems
|
|
192
|
+
[(dataMultiLanguage)]="bannerRows"
|
|
193
|
+
[configItems]="bannerConfig"
|
|
194
|
+
[labelAddItem]="'Thêm banner'"
|
|
195
|
+
(outChangeValue)="handlerBannerChange()"
|
|
196
|
+
/>
|
|
197
|
+
|
|
198
|
+
<button (click)="handlerSave()">Lưu</button>
|
|
199
|
+
`,
|
|
200
|
+
})
|
|
201
|
+
export class BannerFormComponent {
|
|
202
|
+
protected bannerRows = signal<IDataMultiLanguage[]>([
|
|
203
|
+
{
|
|
204
|
+
title: { vi: 'Banner mùa hè', en: 'Summer banner' },
|
|
205
|
+
content: { vi: 'Khuyến mãi lớn', en: 'Big sale' },
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
protected readonly bannerConfig: IDataItem[] = [
|
|
210
|
+
{
|
|
211
|
+
keyBindData: 'content',
|
|
212
|
+
type: 'text',
|
|
213
|
+
titleField: 'Nội dung banner',
|
|
214
|
+
header: {
|
|
215
|
+
title: 'Tiêu đề banner',
|
|
216
|
+
keyBindData: 'title',
|
|
217
|
+
type: 'text',
|
|
218
|
+
dataDefault: {
|
|
219
|
+
title: { vi: '', en: '' },
|
|
220
|
+
content: { vi: '', en: '' },
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
private readonly bannerItemsRef =
|
|
227
|
+
viewChild<LibsUiComponentsInputsMultiLanguageItemsComponent>('bannerItems');
|
|
228
|
+
|
|
229
|
+
protected handlerBannerChange(): void {
|
|
230
|
+
// xử lý khi giá trị thay đổi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
protected async handlerSave(): Promise<void> {
|
|
234
|
+
const isValid = await this.bannerItemsRef()?.FunctionsControl.checkIsValid();
|
|
235
|
+
if (!isValid) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const data = this.bannerItemsRef()?.FunctionsControl.getData();
|
|
239
|
+
// gửi data lên API
|
|
240
|
+
}
|
|
241
|
+
}
|
|
45
242
|
```
|
|
46
243
|
|
|
47
|
-
### Ví dụ
|
|
244
|
+
### Ví dụ 5 — Chế độ đơn ngôn ngữ và chỉ đọc
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { Component, signal } from '@angular/core';
|
|
248
|
+
import {
|
|
249
|
+
LibsUiComponentsInputsMultiLanguageComponent,
|
|
250
|
+
IDataMultiLanguage,
|
|
251
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
48
252
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
[
|
|
253
|
+
@Component({
|
|
254
|
+
selector: 'app-detail-view',
|
|
255
|
+
standalone: true,
|
|
256
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
257
|
+
imports: [LibsUiComponentsInputsMultiLanguageComponent],
|
|
258
|
+
template: `
|
|
259
|
+
<!-- Chỉ đọc -->
|
|
260
|
+
<libs_ui-components-inputs-multi_language
|
|
261
|
+
[dataLanguage]="titleData()"
|
|
262
|
+
[readonly]="true"
|
|
263
|
+
[titleField]="'Tên sản phẩm'"
|
|
264
|
+
/>
|
|
265
|
+
|
|
266
|
+
<!-- Đơn ngôn ngữ (chỉ nhập ngôn ngữ mặc định) -->
|
|
267
|
+
<libs_ui-components-inputs-multi_language
|
|
268
|
+
[(dataLanguage)]="titleData"
|
|
269
|
+
[singleLanguage]="true"
|
|
270
|
+
[titleField]="'Tên sản phẩm (đơn ngôn ngữ)'"
|
|
271
|
+
/>
|
|
272
|
+
`,
|
|
273
|
+
})
|
|
274
|
+
export class DetailViewComponent {
|
|
275
|
+
protected titleData = signal<IDataMultiLanguage>({
|
|
276
|
+
vi: 'Áo thun cao cấp',
|
|
277
|
+
en: 'Premium T-Shirt',
|
|
278
|
+
});
|
|
279
|
+
}
|
|
54
280
|
```
|
|
55
281
|
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `validRequired`
|
|
85
|
-
| `valueUpDownNumber`
|
|
86
|
-
| `viewContent`
|
|
87
|
-
| `viewPosition`
|
|
88
|
-
| `viewType`
|
|
89
|
-
| `zIndex`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
|
94
|
-
|
|
95
|
-
| `outChangeTypeLanguage` | `boolean`
|
|
96
|
-
| `outChangeValueInput`
|
|
97
|
-
| `outEventMultiLanguage` | `IEventMultiLanguage`
|
|
98
|
-
|
|
282
|
+
## @Input() — LibsUiComponentsInputsMultiLanguageComponent
|
|
283
|
+
|
|
284
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
285
|
+
|---|---|---|---|---|
|
|
286
|
+
| `acceptNegativeValue` | `boolean` | `undefined` | Cho phép nhập giá trị số âm | `[acceptNegativeValue]="true"` |
|
|
287
|
+
| `classIconAddLanguage` | `string` | `'libs-ui-icon-add'` | Class icon cho nút thêm ngôn ngữ | `[classIconAddLanguage]="'libs-ui-icon-plus'"` |
|
|
288
|
+
| `classIncludeKey` | `string` | `'libs-ui-font-h4r'` | Class CSS cho phần hiển thị nhãn key (ngôn ngữ) | `[classIncludeKey]="'libs-ui-font-h5r'"` |
|
|
289
|
+
| `classIncludeValue` | `string` | `'libs-ui-font-h4s'` | Class CSS cho phần hiển thị giá trị | `[classIncludeValue]="'libs-ui-font-h5s'"` |
|
|
290
|
+
| `countCharacters` | `boolean` | `undefined` | Hiển thị bộ đếm ký tự bên dưới input | `[countCharacters]="true"` |
|
|
291
|
+
| `data` | `Array<IDataItem>` | `[]` | **(model)** Cấu hình các item nhập liệu (tự động sinh nếu không truyền) | `[(data)]="itemsConfig"` |
|
|
292
|
+
| `dataLanguage` | `IDataMultiLanguage` | `{}` | **(model)** Dữ liệu ngôn ngữ/tiền tệ, dùng two-way binding | `[(dataLanguage)]="myData"` |
|
|
293
|
+
| `extendClass` | `string` | `undefined` | **(model)** Class CSS mở rộng cho container chính | `[(extendClass)]="'mt-4'"` |
|
|
294
|
+
| `extendClassContent` | `string` | `undefined` | Class CSS mở rộng cho vùng nội dung input | `[extendClassContent]="'p-2'"` |
|
|
295
|
+
| `ignoreAdd` | `boolean` | `undefined` | **(model)** Ẩn nút thêm ngôn ngữ mới | `[(ignoreAdd)]="hideAdd"` |
|
|
296
|
+
| `ignoreRemove` | `boolean` | `undefined` | Ẩn nút xóa ngôn ngữ | `[ignoreRemove]="true"` |
|
|
297
|
+
| `keyHeader` | `string` | `undefined` | Key dùng để xác định header trong dữ liệu | `[keyHeader]="'title'"` |
|
|
298
|
+
| `maxValueNumber` | `number` | `undefined` | Giá trị tối đa cho input kiểu số | `[maxValueNumber]="9999999"` |
|
|
299
|
+
| `onlyAcceptNegativeValue` | `boolean` | `undefined` | Chỉ chấp nhận giá trị âm | `[onlyAcceptNegativeValue]="true"` |
|
|
300
|
+
| `optionsLanguage` | `Array<IOption>` | `langOptions()` | Danh sách tùy chọn ngôn ngữ/tiền tệ để hiển thị trong dropdown | `[optionsLanguage]="customLangs"` |
|
|
301
|
+
| `placeholder` | `string` | `undefined` | Placeholder cho tất cả các input bên trong | `[placeholder]="'Nhập nội dung...'"` |
|
|
302
|
+
| `readonly` | `boolean` | `undefined` | Chế độ chỉ đọc, không cho phép chỉnh sửa | `[readonly]="true"` |
|
|
303
|
+
| `singleLanguage` | `boolean` | `undefined` | Chỉ hiển thị ngôn ngữ mặc định đầu tiên, ẩn lựa chọn ngôn ngữ | `[singleLanguage]="true"` |
|
|
304
|
+
| `textArea` | `boolean` | `undefined` | Dùng textarea nhiều dòng thay vì input một dòng | `[textArea]="true"` |
|
|
305
|
+
| `titleField` | `string` | `undefined` | Nhãn tiêu đề hiển thị phía trên vùng nhập liệu | `[titleField]="'Tên sản phẩm'"` |
|
|
306
|
+
| `validMaxLength` | `IValidLength` | `{ length: 0 }` | Cấu hình độ dài ký tự tối đa (0 = không giới hạn) | `[validMaxLength]="{ length: 500, message: 'Tối đa 500 ký tự' }"` |
|
|
307
|
+
| `validMinLength` | `IValidLength` | `undefined` | Cấu hình độ dài ký tự tối thiểu | `[validMinLength]="{ length: 10, message: 'Tối thiểu 10 ký tự' }"` |
|
|
308
|
+
| `validOneDefaultMultiLanguage` | `string` | `undefined` | Key ngôn ngữ mặc định bắt buộc có giá trị nếu có bất kỳ ngôn ngữ nào khác được nhập | `[validOneDefaultMultiLanguage]="'vi'"` |
|
|
309
|
+
| `validPattern` | `Array<IValidPattern>` | `undefined` | Danh sách regex pattern dùng để validate | `[validPattern]="patternRules"` |
|
|
310
|
+
| `validRequired` | `IValidRequired` | `{ isRequired: false, message: '' }` | Cấu hình validation bắt buộc nhập | `[validRequired]="{ isRequired: true, message: 'Bắt buộc nhập' }"` |
|
|
311
|
+
| `valueUpDownNumber` | `number` | `undefined` | Bước nhảy khi bấm nút tăng/giảm với input kiểu số | `[valueUpDownNumber]="1000"` |
|
|
312
|
+
| `viewContent` | `boolean` | `undefined` | Chỉ hiển thị nội dung dạng text, không render input | `[viewContent]="true"` |
|
|
313
|
+
| `viewPosition` | `'row' \| 'column'` | `'row'` | Hướng hiển thị của nhãn và vùng input | `[viewPosition]="'column'"` |
|
|
314
|
+
| `viewType` | `TYPE_VIEW_MULTI_LANGUAGE` | `'text'` | Kiểu input: `'text'` \| `'integer'` \| `'number'` \| `'float'` \| `'bigint'` \| `'editor'` | `[viewType]="'integer'"` |
|
|
315
|
+
| `zIndex` | `number` | `undefined` | z-index cho dropdown/popover bên trong | `[zIndex]="1050"` |
|
|
316
|
+
|
|
317
|
+
## @Output() — LibsUiComponentsInputsMultiLanguageComponent
|
|
318
|
+
|
|
319
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
320
|
+
|---|---|---|---|---|
|
|
321
|
+
| `(outChangeTypeLanguage)` | `boolean` | Phát ra khi chuyển đổi chế độ đơn/đa ngôn ngữ | `handlerChangeTypeLanguage(event: boolean): void { event.stopPropagation(); /* xử lý */ }` | `(outChangeTypeLanguage)="handlerChangeTypeLanguage($event)"` |
|
|
322
|
+
| `(outChangeValueInput)` | `void` | Phát ra khi giá trị trong bất kỳ input nào thay đổi (debounce 500ms) | `handlerValueChange(): void { /* xử lý */ }` | `(outChangeValueInput)="handlerValueChange()"` |
|
|
323
|
+
| `(outEventMultiLanguage)` | `IEventMultiLanguage` | Phát ra khi người dùng thêm hoặc xóa một ngôn ngữ | `handlerLangEvent(event: IEventMultiLanguage): void { event.stopPropagation(); /* xử lý action: 'add' | 'remove' */ }` | `(outEventMultiLanguage)="handlerLangEvent($event)"` |
|
|
324
|
+
|
|
325
|
+
## FunctionsControl — LibsUiComponentsInputsMultiLanguageComponent
|
|
326
|
+
|
|
327
|
+
Truy cập qua `ViewChild` với getter `FunctionsControl`:
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
private readonly fieldRef = viewChild<LibsUiComponentsInputsMultiLanguageComponent>('fieldRef');
|
|
331
|
+
|
|
332
|
+
// Validate tất cả input bên trong
|
|
333
|
+
const isValid = await this.fieldRef()?.FunctionsControl.checkIsValid(); // Promise<boolean>
|
|
334
|
+
|
|
335
|
+
// Lấy dữ liệu hiện tại
|
|
336
|
+
const data = this.fieldRef()?.FunctionsControl.getData(); // IDataMultiLanguage | IDataMultiLanguage[] | undefined
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## @Input() — LibsUiComponentsInputsMultiLanguageItemsComponent
|
|
340
|
+
|
|
341
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
342
|
+
|---|---|---|---|---|
|
|
343
|
+
| `acceptNegativeValue` | `boolean` | `undefined` | Cho phép số âm trong input số | `[acceptNegativeValue]="true"` |
|
|
344
|
+
| `classIconAddLanguage` | `string` | `'libs-ui-icon-add'` | Class icon cho nút thêm ngôn ngữ trong các component con | `[classIconAddLanguage]="'libs-ui-icon-plus'"` |
|
|
345
|
+
| `configItems` | `Array<IDataItem>` | `[]` | Cấu hình các cột; phần tử đầu tiên chứa `header` cho dòng tiêu đề | `[configItems]="bannerConfig"` |
|
|
346
|
+
| `dataMultiLanguage` | `Array<IDataMultiLanguage>` | `[{}]` | **(model)** Danh sách các khối dữ liệu | `[(dataMultiLanguage)]="bannerRows"` |
|
|
347
|
+
| `ignoreAdd` | `boolean` | `undefined` | Ẩn nút thêm khối mới | `[ignoreAdd]="true"` |
|
|
348
|
+
| `labelAddItem` | `string` | `undefined` | Nhãn nút thêm khối mới | `[labelAddItem]="'Thêm banner'"` |
|
|
349
|
+
| `maxValueNumber` | `number` | `undefined` | Giá trị tối đa cho input kiểu số | `[maxValueNumber]="100"` |
|
|
350
|
+
| `onlyAcceptNegativeValue` | `boolean` | `undefined` | Chỉ chấp nhận số âm | `[onlyAcceptNegativeValue]="true"` |
|
|
351
|
+
| `singleLanguage` | `boolean` | `undefined` | Truyền xuống component con: chỉ hiển thị ngôn ngữ mặc định | `[singleLanguage]="true"` |
|
|
352
|
+
| `valueUpDownNumber` | `number` | `undefined` | Bước nhảy tăng/giảm cho input số | `[valueUpDownNumber]="1"` |
|
|
353
|
+
| `zIndex` | `number` | `undefined` | z-index cho overlay bên trong | `[zIndex]="1050"` |
|
|
354
|
+
|
|
355
|
+
## @Output() — LibsUiComponentsInputsMultiLanguageItemsComponent
|
|
356
|
+
|
|
357
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
358
|
+
|---|---|---|---|---|
|
|
359
|
+
| `(outChangeValue)` | `void` | Phát ra khi giá trị header hoặc bất kỳ input đa ngôn ngữ nào thay đổi | `handlerItemsChange(): void { /* xử lý */ }` | `(outChangeValue)="handlerItemsChange()"` |
|
|
360
|
+
|
|
361
|
+
## FunctionsControl — LibsUiComponentsInputsMultiLanguageItemsComponent
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
private readonly itemsRef = viewChild<LibsUiComponentsInputsMultiLanguageItemsComponent>('itemsRef');
|
|
365
|
+
|
|
366
|
+
// Validate tất cả khối
|
|
367
|
+
const isValid = await this.itemsRef()?.FunctionsControl.checkIsValid(); // Promise<boolean>
|
|
368
|
+
|
|
369
|
+
// Lấy toàn bộ dữ liệu các khối
|
|
370
|
+
const rows = this.itemsRef()?.FunctionsControl.getData(); // IDataMultiLanguage[] | IDataMultiLanguage | undefined
|
|
371
|
+
```
|
|
99
372
|
|
|
100
373
|
## Types & Interfaces
|
|
101
374
|
|
|
102
375
|
```typescript
|
|
376
|
+
import {
|
|
377
|
+
IDataItem,
|
|
378
|
+
IDataMultiLanguage,
|
|
379
|
+
IDataLanguage,
|
|
380
|
+
IEventMultiLanguage,
|
|
381
|
+
IConfigMultiLanguageHeader,
|
|
382
|
+
IInputMultiLanguageFunctionControlEvent,
|
|
383
|
+
IOption,
|
|
384
|
+
ICurrencyUnit,
|
|
385
|
+
TYPE_VIEW_MULTI_LANGUAGE,
|
|
386
|
+
} from '@libs-ui/components-inputs-multi-language';
|
|
387
|
+
|
|
388
|
+
/** Kiểu view được hỗ trợ */
|
|
389
|
+
export type TYPE_VIEW_MULTI_LANGUAGE = 'text' | 'integer' | 'number' | 'editor' | 'float' | 'bigint';
|
|
390
|
+
|
|
391
|
+
/** Dữ liệu đa ngôn ngữ: key là mã ngôn ngữ/tiền tệ, value là chuỗi, số hoặc object lồng nhau */
|
|
392
|
+
export interface IDataMultiLanguage {
|
|
393
|
+
[param: string]: IDataLanguage | string | number;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Dữ liệu ngôn ngữ lồng nhau (cho cấu trúc items) */
|
|
397
|
+
export interface IDataLanguage {
|
|
398
|
+
[param: string]: string | number;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Cấu hình một item nhập liệu */
|
|
103
402
|
export interface IDataItem {
|
|
104
|
-
keyBindData?: string;
|
|
105
|
-
placeholder?: string;
|
|
106
|
-
type?: string;
|
|
107
|
-
header?: IConfigMultiLanguageHeader;
|
|
108
|
-
isOneValue?: boolean;
|
|
109
|
-
classIncludeInput?: string;
|
|
110
|
-
titleField?: string;
|
|
111
|
-
isCountCharacters?: boolean;
|
|
403
|
+
keyBindData?: string; // key để bind vào object dữ liệu
|
|
404
|
+
placeholder?: string; // placeholder cho input
|
|
405
|
+
type?: string; // kiểu input (text, integer, editor, ...)
|
|
406
|
+
header?: IConfigMultiLanguageHeader; // cấu hình dòng header (dùng trong Items)
|
|
407
|
+
isOneValue?: boolean; // chỉ có 1 giá trị
|
|
408
|
+
classIncludeInput?: string; // class CSS cho input
|
|
409
|
+
titleField?: string; // nhãn tiêu đề
|
|
410
|
+
isCountCharacters?: boolean; // hiển thị đếm ký tự
|
|
112
411
|
validRequired?: IValidRequired;
|
|
113
412
|
dataMultiLanguage?: IDataMultiLanguage;
|
|
114
413
|
validRequiredLangDefault?: IValidRequired;
|
|
115
414
|
validRequiredDefault?: IValidRequired;
|
|
415
|
+
minHeightEditorContentDefault?: number; // chiều cao tối thiểu editor (px)
|
|
416
|
+
maxHeightEditorContentDefault?: number; // chiều cao tối đa editor (px)
|
|
417
|
+
heightEditorContentDefault?: number; // chiều cao mặc định editor (px)
|
|
418
|
+
resize?: 'vertical' | 'none'; // cho phép resize textarea
|
|
116
419
|
}
|
|
117
420
|
|
|
421
|
+
/** Cấu hình dòng header trong LibsUiComponentsInputsMultiLanguageItemsComponent */
|
|
118
422
|
export interface IConfigMultiLanguageHeader {
|
|
119
|
-
title?: string;
|
|
120
|
-
isShowInput?: boolean;
|
|
423
|
+
title?: string; // nhãn hiển thị cho header
|
|
424
|
+
isShowInput?: boolean; // có hiển thị input không
|
|
121
425
|
validRequired?: {
|
|
122
426
|
isRequired?: boolean;
|
|
123
427
|
};
|
|
124
|
-
keyBindData?: string;
|
|
125
|
-
dataDefault?:
|
|
126
|
-
|
|
127
|
-
| {
|
|
128
|
-
total?: IDataMultiLanguage;
|
|
129
|
-
};
|
|
130
|
-
type?: string;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface IDataMultiLanguage {
|
|
134
|
-
[param: string]: IDataLanguage | string | number;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface IDataLanguage {
|
|
138
|
-
[param: string]: string | number;
|
|
428
|
+
keyBindData?: string; // key trỏ tới field header trong dữ liệu
|
|
429
|
+
dataDefault?: IDataMultiLanguage | { total?: IDataMultiLanguage }; // dữ liệu mặc định khi thêm khối mới
|
|
430
|
+
type?: string; // kiểu input của header
|
|
139
431
|
}
|
|
140
432
|
|
|
433
|
+
/** Event phát ra khi thêm/xóa ngôn ngữ */
|
|
141
434
|
export interface IEventMultiLanguage {
|
|
142
435
|
action: 'add' | 'remove';
|
|
143
436
|
data: IDataMultiLanguage;
|
|
144
437
|
}
|
|
145
438
|
|
|
146
|
-
|
|
147
|
-
|
|
439
|
+
/** API FunctionsControl */
|
|
148
440
|
export interface IInputMultiLanguageFunctionControlEvent {
|
|
149
441
|
checkIsValid: () => Promise<boolean>;
|
|
150
|
-
getData: () =>
|
|
442
|
+
getData: () => IDataMultiLanguage[] | IDataMultiLanguage | undefined;
|
|
151
443
|
}
|
|
152
444
|
|
|
445
|
+
/** Một tùy chọn ngôn ngữ/tiền tệ */
|
|
153
446
|
export interface IOption {
|
|
154
|
-
label: string;
|
|
155
|
-
key: string;
|
|
447
|
+
label: string; // tên hiển thị (ví dụ: 'Tiếng Việt', 'USD')
|
|
448
|
+
key: string; // mã định danh (ví dụ: 'vi', 'USD')
|
|
156
449
|
}
|
|
157
450
|
|
|
451
|
+
/** Đơn vị tiền tệ */
|
|
158
452
|
export interface ICurrencyUnit {
|
|
159
453
|
label: string;
|
|
160
454
|
id: string;
|
|
@@ -162,13 +456,28 @@ export interface ICurrencyUnit {
|
|
|
162
456
|
}
|
|
163
457
|
```
|
|
164
458
|
|
|
165
|
-
##
|
|
459
|
+
## Lưu ý quan trọng
|
|
460
|
+
|
|
461
|
+
⚠️ **Selector với dấu gạch dưới**: Selector của component dùng dấu gạch dưới theo quy ước libs-ui. Dùng đúng `libs_ui-components-inputs-multi_language` (không phải `libs-ui-components-inputs-multi-language`).
|
|
462
|
+
|
|
463
|
+
⚠️ **viewType = 'integer' tự động chuyển sang tiền tệ**: Khi `viewType` là `'integer'`, component tự động thay danh sách ngôn ngữ bằng danh sách tiền tệ hỗ trợ (lấy từ `UtilsCache`). Dữ liệu `dataLanguage` lúc này có dạng `{ VND: 1500000, USD: 60 }`.
|
|
464
|
+
|
|
465
|
+
⚠️ **Danh sách ngôn ngữ/tiền tệ hỗ trợ từ UtilsCache**: Danh sách ngôn ngữ mặc định lấy từ `UtilsCache.Get('test_lang', ['vi', 'en'])`. Để tùy chỉnh, truyền `[optionsLanguage]` với mảng `IOption[]` của riêng bạn.
|
|
466
|
+
|
|
467
|
+
⚠️ **FunctionsControl qua ViewChild**: Để validate và lấy dữ liệu từ component cha, dùng `viewChild()` (Signal API) với template variable, rồi truy cập getter `FunctionsControl` — không gọi method trực tiếp trên component.
|
|
166
468
|
|
|
167
|
-
|
|
168
|
-
- **Styling**: Tailwind CSS
|
|
169
|
-
- **Utilities**: `@libs-ui/utils`
|
|
170
|
-
- **I18n**: `@ngx-translate/core`
|
|
469
|
+
⚠️ **Validation thông minh**: Khi có nhiều ngôn ngữ và `validRequired` không được bật, component tự động bật required cho ngôn ngữ mặc định nếu người dùng đã nhập bất kỳ ngôn ngữ phụ nào. Logic này chạy tự động với debounce 500ms.
|
|
171
470
|
|
|
172
|
-
|
|
471
|
+
⚠️ **IDataItem.header trong Items**: Trong `LibsUiComponentsInputsMultiLanguageItemsComponent`, `configItems[0].header.dataDefault` xác định cấu trúc dữ liệu mặc định khi thêm khối mới. Cấu trúc này PHẢI khớp với cấu trúc của `dataMultiLanguage`.
|
|
472
|
+
|
|
473
|
+
⚠️ **Two-way binding với model()**: Cả `data`, `dataLanguage`, `extendClass`, `ignoreAdd` đều là `model()` signal — dùng cú pháp `[(xxx)]="signal"` để two-way binding. Không dùng `[xxx]="signal()"` với dấu gọi vì sẽ mất two-way binding.
|
|
474
|
+
|
|
475
|
+
## Demo
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
npx nx serve core-ui
|
|
479
|
+
```
|
|
173
480
|
|
|
174
|
-
|
|
481
|
+
Truy cập:
|
|
482
|
+
- `LibsUiComponentsInputsMultiLanguageComponent`: http://localhost:4500/components/inputs/multi-language
|
|
483
|
+
- `LibsUiComponentsInputsMultiLanguageItemsComponent`: http://localhost:4500/components/inputs/multi-language-items
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Pipe, signal, computed, viewChildren, input, model, output, effect, untracked,
|
|
2
|
+
import { Pipe, signal, computed, viewChildren, input, model, output, effect, untracked, Component, ChangeDetectionStrategy } from '@angular/core';
|
|
3
3
|
import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
|
|
4
4
|
import { LibsUiComponentsDropdownComponent } from '@libs-ui/components-dropdown';
|
|
5
5
|
import { LibsUiComponentsInputsQuill2xComponent } from '@libs-ui/components-inputs-quill2x';
|