@libs-ui/components-inputs-multi-language 0.2.356-8 → 0.2.357-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,161 +1,454 @@
1
- # Multi-Language Input Component
1
+ # @libs-ui/components-inputs-multi-language
2
2
 
3
- `@libs-ui/components-inputs-multi-language` một component mạnh mẽ hỗ trợ nhập liệu đa ngôn ngữ và đa tiền tệ trong các ứng dụng Angular. Component này cung cấp giao diện linh hoạt cho phép người dùng quản lý nội dung giữa nhiều ngôn ngữ khác nhau, hỗ trợ cả định dạng văn bản thuần túy, số học trình chỉnh sửa văn bản giàu tính năng (Quill).
3
+ > Component nhập liệu đa ngôn ngữ và đa tiền tệ cho Angular, hỗ trợ nhiều kiểu inputvalidation tích hợp.
4
4
 
5
- ## Tính năng nổi bật
5
+ ## Giới thiệu
6
6
 
7
- - 🌍 **Đa ngôn ngữ**: Hỗ trợ nhập liệu cho nhiều ngôn ngữ đồng thời, tự động quản lý danh sách ngôn ngữ kích hoạt.
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ệ, `LibsUiComponentsInputsMultiLanguageItemsComponent` để quản lý nhiều khối nhập liệu 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
- ## Cài đặt
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
- Sử dụng npm hoặc yarn để cài đặt:
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
- ## Cách sử dụng
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 YourComponent {}
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ụ bản (Nhập liệu văn bản)
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
- ```html
41
- <libs_ui-components-inputs-multi_language
42
- [(dataLanguage)]="myData"
43
- [placeholder]="'Nhập tiêu đề...'"
44
- [titleField]="'Tiêu đề'"></libs_ui-components-inputs-multi_language>
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ụ nâng cao (Đa tiền tệ)
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
- ```html
50
- <libs_ui-components-inputs-multi_language
51
- [viewType]="'integer'"
52
- [(dataLanguage)]="priceData"
53
- [titleField]="'Giá sản phẩm'"></libs_ui-components-inputs-multi_language>
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
- ## API Reference
57
-
58
- ### Inputs
59
-
60
- | Thuộc tính | Kiểu dữ liệu | Mặc định | tả |
61
- | :----------------------------- | :------------------------- | :------------------- | :----------------------------------------------------------------------- |
62
- | `acceptNegativeValue` | `boolean` | `undefined` | Cho phép nhập số âm. |
63
- | `classIncludeKey` | `string` | `'libs-ui-font-h4r'` | Class cho phần hiển thị key (label). |
64
- | `classIncludeValue` | `string` | `'libs-ui-font-h4s'` | Class cho phần hiển thị value. |
65
- | `countCharacters` | `boolean` | `undefined` | Hiển thị bộ đếm tự. |
66
- | `data` | `Array<IDataItem>` | `[]` | **(Model)** Cấu hình các item nhập liệu bên trong. |
67
- | `dataLanguage` | `IDataMultiLanguage` | `{}` | **(Model)** Dữ liệu ngôn ngữ hiện tại. |
68
- | `extendClass` | `string` | `undefined` | Class mở rộng cho container chính. |
69
- | `extendClassContent` | `string` | `undefined` | Class mở rộng cho phần nội dung input. |
70
- | `ignoreAdd` | `boolean` | `undefined` | Ẩn nút thêm ngôn ngữ mới. |
71
- | `ignoreRemove` | `boolean` | `undefined` | Ẩn nút xóa ngôn ngữ. |
72
- | `keyHeader` | `string` | `undefined` | Key dùng để hiển thị header (nếu có). |
73
- | `maxValueNumber` | `number` | `undefined` | Giá trị tối đa cho input số. |
74
- | `multiLine` | `boolean` | `undefined` | Cho phép nhập nhiều dòng (textarea). |
75
- | `onlyAcceptNegativeValue` | `boolean` | `undefined` | Chỉ chấp nhận giá trị âm. |
76
- | `optionsLanguage` | `Array<IOption>` | `langOptions()` | Danh sách các tùy chọn ngôn ngữ/tiền tệ. |
77
- | `placeholder` | `string` | `undefined` | Placeholder cho các input. |
78
- | `readonly` | `boolean` | `false` | Chế độ chỉ đọc. |
79
- | `singleLanguage` | `boolean` | `undefined` | Đơn ngôn ngữ (chỉ hiển thị 1 ngôn ngữ mặc định). |
80
- | `textArea` | `boolean` | `undefined` | Sử dụng textarea thay input text. |
81
- | `titleField` | `string` | `undefined` | Tiêu đề chung cho các trường nhập liệu. |
82
- | `validMaxLength` | `IValidLength` | `{ length: 0 }` | Cấu hình độ dài tối đa cho validation. |
83
- | `validMinLength` | `IValidLength` | `undefined` | Cấu hình độ dài tối thiểu cho validation. |
84
- | `validOneDefaultMultiLanguage` | `string` | `undefined` | Key ngôn ngữ mặc định bắt buộc nhập nếu chỉ nhập 1 ngôn ngữ |
85
- | `validRequired` | `IValidRequired` | `validRequired()` | Cấu hình validation bắt buộc. |
86
- | `valueUpDownNumber` | `number` | `undefined` | Bước nhảy cho nút tăng giảm giá trị số. |
87
- | `viewContent` | `boolean` | `undefined` | Chỉ hiển thị nội dung (view mode), không phải input. |
88
- | `viewPosition` | `'row' \| 'column'` | `'row'` | Hướng hiển thị của label input. |
89
- | `viewType` | `TYPE_VIEW_MULTI_LANGUAGE` | `'text'` | Kiểu hiển thị: `text`, `integer`, `number`, `editor`, `float`, `bigint`. |
90
- | `zIndex` | `number` | `undefined` | Z-index cho các thành phần dropdown/popover. |
91
-
92
- ### Outputs
93
-
94
- | Sự kiện | Kiểu dữ liệu | Mô tả |
95
- | :---------------------- | :---------------------------------------- | :------------------------------------------------------------------------- |
96
- | `outChangeTypeLanguage` | `boolean` | Phát ra khi thay đổi loại ngôn ngữ (đơn/đa). |
97
- | `outChangeValueInput` | `void` | Phát ra khi giá trị trong bất kỳ input nào thay đổi. |
98
- | `outEventMultiLanguage` | `IEventMultiLanguage` | Phát ra khi có hành động thêm hoặc xóa ngôn ngữ. |
99
- | `outFunctionsControl` | `IInputMultiLanguageFunctionControlEvent` | Cung cấp các phương thức điều khiển component (`checkIsValid`, `getData`). |
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 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 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 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 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ù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
+ ```
100
372
 
101
373
  ## Types & Interfaces
102
374
 
103
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 */
104
402
  export interface IDataItem {
105
- keyBindData?: string;
106
- placeholder?: string;
107
- type?: string;
108
- header?: IConfigMultiLanguageHeader;
109
- isOneValue?: boolean;
110
- classIncludeInput?: string;
111
- titleField?: string;
112
- 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ự
113
411
  validRequired?: IValidRequired;
114
412
  dataMultiLanguage?: IDataMultiLanguage;
115
413
  validRequiredLangDefault?: IValidRequired;
116
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
117
419
  }
118
420
 
421
+ /** Cấu hình dòng header trong LibsUiComponentsInputsMultiLanguageItemsComponent */
119
422
  export interface IConfigMultiLanguageHeader {
120
- title?: string;
121
- isShowInput?: boolean;
423
+ title?: string; // nhãn hiển thị cho header
424
+ isShowInput?: boolean; // có hiển thị input không
122
425
  validRequired?: {
123
426
  isRequired?: boolean;
124
427
  };
125
- keyBindData?: string;
126
- dataDefault?:
127
- | IDataMultiLanguage
128
- | {
129
- total?: IDataMultiLanguage;
130
- };
131
- type?: string;
132
- }
133
-
134
- export interface IDataMultiLanguage {
135
- [param: string]: IDataLanguage | string | number;
136
- }
137
-
138
- export interface IDataLanguage {
139
- [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
140
431
  }
141
432
 
433
+ /** Event phát ra khi thêm/xóa ngôn ngữ */
142
434
  export interface IEventMultiLanguage {
143
435
  action: 'add' | 'remove';
144
436
  data: IDataMultiLanguage;
145
437
  }
146
438
 
147
- export type TYPE_VIEW_MULTI_LANGUAGE = 'text' | 'integer' | 'number' | 'editor' | 'float' | 'bigint';
148
-
439
+ /** API FunctionsControl */
149
440
  export interface IInputMultiLanguageFunctionControlEvent {
150
441
  checkIsValid: () => Promise<boolean>;
151
- getData: () => Promise<IDataMultiLanguage[] | IDataMultiLanguage | undefined>;
442
+ getData: () => IDataMultiLanguage[] | IDataMultiLanguage | undefined;
152
443
  }
153
444
 
445
+ /** Một tùy chọn ngôn ngữ/tiền tệ */
154
446
  export interface IOption {
155
- label: string;
156
- 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')
157
449
  }
158
450
 
451
+ /** Đơn vị tiền tệ */
159
452
  export interface ICurrencyUnit {
160
453
  label: string;
161
454
  id: string;
@@ -163,13 +456,28 @@ export interface ICurrencyUnit {
163
456
  }
164
457
  ```
165
458
 
166
- ## Tech Stack
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.
167
468
 
168
- - **Framework**: Angular 18+
169
- - **Styling**: Tailwind CSS
170
- - **Utilities**: `@libs-ui/utils`
171
- - **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.
172
470
 
173
- ## License
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
+ ```
174
480
 
175
- MIT
481
+ Truy cập:
482
+ - `LibsUiComponentsInputsMultiLanguageComponent`: http://localhost:4500/components/inputs/multi-language
483
+ - `LibsUiComponentsInputsMultiLanguageItemsComponent`: http://localhost:4500/components/inputs/multi-language-items
@@ -1,2 +1,2 @@
1
1
  export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXVsdGktbGFuZ3VhZ2UuaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL2lucHV0cy9tdWx0aS1sYW5ndWFnZS9zcmMvaW50ZXJmYWNlcy9tdWx0aS1sYW5ndWFnZS5pbnRlcmZhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IElWYWxpZFJlcXVpcmVkIH0gZnJvbSAnQGxpYnMtdWkvY29tcG9uZW50cy1pbnB1dHMtdmFsaWQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElEYXRhSXRlbSB7XG4gIGtleUJpbmREYXRhPzogc3RyaW5nO1xuICBwbGFjZWhvbGRlcj86IHN0cmluZztcbiAgdHlwZT86IHN0cmluZztcbiAgaGVhZGVyPzogSUNvbmZpZ011bHRpTGFuZ3VhZ2VIZWFkZXI7XG4gIGlzT25lVmFsdWU/OiBib29sZWFuO1xuICBjbGFzc0luY2x1ZGVJbnB1dD86IHN0cmluZztcbiAgdGl0bGVGaWVsZD86IHN0cmluZztcbiAgaXNDb3VudENoYXJhY3RlcnM/OiBib29sZWFuO1xuICB2YWxpZFJlcXVpcmVkPzogSVZhbGlkUmVxdWlyZWQ7XG4gIGRhdGFNdWx0aUxhbmd1YWdlPzogSURhdGFNdWx0aUxhbmd1YWdlO1xuICB2YWxpZFJlcXVpcmVkTGFuZ0RlZmF1bHQ/OiBJVmFsaWRSZXF1aXJlZDtcbiAgdmFsaWRSZXF1aXJlZERlZmF1bHQ/OiBJVmFsaWRSZXF1aXJlZDtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJRGF0YU11bHRpTGFuZ3VhZ2Uge1xuICBbcGFyYW06IHN0cmluZ106IElEYXRhTGFuZ3VhZ2UgfCBzdHJpbmcgfCBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSURhdGFMYW5ndWFnZSB7XG4gIFtwYXJhbTogc3RyaW5nXTogc3RyaW5nIHwgbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIElFdmVudE11bHRpTGFuZ3VhZ2Uge1xuICBhY3Rpb246ICdhZGQnIHwgJ3JlbW92ZSc7XG4gIGRhdGE6IElEYXRhTXVsdGlMYW5ndWFnZTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJQ29uZmlnTXVsdGlMYW5ndWFnZUhlYWRlciB7XG4gIHRpdGxlPzogc3RyaW5nO1xuICBpc1Nob3dJbnB1dD86IGJvb2xlYW47XG4gIHZhbGlkUmVxdWlyZWQ/OiB7XG4gICAgaXNSZXF1aXJlZD86IGJvb2xlYW47XG4gIH07XG4gIGtleUJpbmREYXRhPzogc3RyaW5nO1xuICBkYXRhRGVmYXVsdD86XG4gICAgfCBJRGF0YU11bHRpTGFuZ3VhZ2VcbiAgICB8IHtcbiAgICAgICAgdG90YWw/OiBJRGF0YU11bHRpTGFuZ3VhZ2U7XG4gICAgICB9O1xuICB0eXBlPzogc3RyaW5nO1xufVxuXG5leHBvcnQgdHlwZSBUWVBFX1ZJRVdfTVVMVElfTEFOR1VBR0UgPSAndGV4dCcgfCAnaW50ZWdlcicgfCAnbnVtYmVyJyB8ICdlZGl0b3InIHwgJ2Zsb2F0JyB8ICdiaWdpbnQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElJbnB1dE11bHRpTGFuZ3VhZ2VGdW5jdGlvbkNvbnRyb2xFdmVudCB7XG4gIGNoZWNrSXNWYWxpZDogKCkgPT4gUHJvbWlzZTxib29sZWFuPjtcbiAgZ2V0RGF0YTogKCkgPT4gUHJvbWlzZTxJRGF0YU11bHRpTGFuZ3VhZ2VbXSB8IElEYXRhTXVsdGlMYW5ndWFnZSB8IHVuZGVmaW5lZD47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSUN1cnJlbmN5VW5pdCB7XG4gIGxhYmVsOiBzdHJpbmc7XG4gIGlkOiBzdHJpbmc7XG4gIHR5cGU6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJT3B0aW9uIHtcbiAgbGFiZWw6IHN0cmluZztcbiAga2V5OiBzdHJpbmc7XG59XG4iXX0=
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXVsdGktbGFuZ3VhZ2UuaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vbGlicy11aS9jb21wb25lbnRzL2lucHV0cy9tdWx0aS1sYW5ndWFnZS9zcmMvaW50ZXJmYWNlcy9tdWx0aS1sYW5ndWFnZS5pbnRlcmZhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IElWYWxpZFJlcXVpcmVkIH0gZnJvbSAnQGxpYnMtdWkvY29tcG9uZW50cy1pbnB1dHMtdmFsaWQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElEYXRhSXRlbSB7XG4gIGtleUJpbmREYXRhPzogc3RyaW5nO1xuICBwbGFjZWhvbGRlcj86IHN0cmluZztcbiAgdHlwZT86IHN0cmluZztcbiAgaGVhZGVyPzogSUNvbmZpZ011bHRpTGFuZ3VhZ2VIZWFkZXI7XG4gIGlzT25lVmFsdWU/OiBib29sZWFuO1xuICBjbGFzc0luY2x1ZGVJbnB1dD86IHN0cmluZztcbiAgdGl0bGVGaWVsZD86IHN0cmluZztcbiAgaXNDb3VudENoYXJhY3RlcnM/OiBib29sZWFuO1xuICB2YWxpZFJlcXVpcmVkPzogSVZhbGlkUmVxdWlyZWQ7XG4gIGRhdGFNdWx0aUxhbmd1YWdlPzogSURhdGFNdWx0aUxhbmd1YWdlO1xuICB2YWxpZFJlcXVpcmVkTGFuZ0RlZmF1bHQ/OiBJVmFsaWRSZXF1aXJlZDtcbiAgdmFsaWRSZXF1aXJlZERlZmF1bHQ/OiBJVmFsaWRSZXF1aXJlZDtcbiAgbWluSGVpZ2h0RWRpdG9yQ29udGVudERlZmF1bHQ/OiBudW1iZXI7XG4gIG1heEhlaWdodEVkaXRvckNvbnRlbnREZWZhdWx0PzogbnVtYmVyO1xuICBoZWlnaHRFZGl0b3JDb250ZW50RGVmYXVsdD86IG51bWJlcjtcbiAgcmVzaXplPzogJ3ZlcnRpY2FsJyB8ICdub25lJztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJRGF0YU11bHRpTGFuZ3VhZ2Uge1xuICBbcGFyYW06IHN0cmluZ106IElEYXRhTGFuZ3VhZ2UgfCBzdHJpbmcgfCBudW1iZXI7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSURhdGFMYW5ndWFnZSB7XG4gIFtwYXJhbTogc3RyaW5nXTogc3RyaW5nIHwgbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIElFdmVudE11bHRpTGFuZ3VhZ2Uge1xuICBhY3Rpb246ICdhZGQnIHwgJ3JlbW92ZSc7XG4gIGRhdGE6IElEYXRhTXVsdGlMYW5ndWFnZTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJQ29uZmlnTXVsdGlMYW5ndWFnZUhlYWRlciB7XG4gIHRpdGxlPzogc3RyaW5nO1xuICBpc1Nob3dJbnB1dD86IGJvb2xlYW47XG4gIHZhbGlkUmVxdWlyZWQ/OiB7XG4gICAgaXNSZXF1aXJlZD86IGJvb2xlYW47XG4gIH07XG4gIGtleUJpbmREYXRhPzogc3RyaW5nO1xuICBkYXRhRGVmYXVsdD86XG4gICAgfCBJRGF0YU11bHRpTGFuZ3VhZ2VcbiAgICB8IHtcbiAgICAgICAgdG90YWw/OiBJRGF0YU11bHRpTGFuZ3VhZ2U7XG4gICAgICB9O1xuICB0eXBlPzogc3RyaW5nO1xufVxuXG5leHBvcnQgdHlwZSBUWVBFX1ZJRVdfTVVMVElfTEFOR1VBR0UgPSAndGV4dCcgfCAnaW50ZWdlcicgfCAnbnVtYmVyJyB8ICdlZGl0b3InIHwgJ2Zsb2F0JyB8ICdiaWdpbnQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIElJbnB1dE11bHRpTGFuZ3VhZ2VGdW5jdGlvbkNvbnRyb2xFdmVudCB7XG4gIGNoZWNrSXNWYWxpZDogKCkgPT4gUHJvbWlzZTxib29sZWFuPjtcbiAgZ2V0RGF0YTogKCkgPT4gSURhdGFNdWx0aUxhbmd1YWdlW10gfCBJRGF0YU11bHRpTGFuZ3VhZ2UgfCB1bmRlZmluZWQ7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgSUN1cnJlbmN5VW5pdCB7XG4gIGxhYmVsOiBzdHJpbmc7XG4gIGlkOiBzdHJpbmc7XG4gIHR5cGU6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJT3B0aW9uIHtcbiAgbGFiZWw6IHN0cmluZztcbiAga2V5OiBzdHJpbmc7XG59XG4iXX0=