npm package: @libs-ui/components-inputs-add
Description: > Component cho phép thêm/xóa nhiều input fields động với validation và customization đầy đủ
Latest version: 0.2.356-23

# @libs-ui/components-inputs-add

> Component cho phép thêm/xóa nhiều input fields động với validation và customization đầy đủ

**Version:** 0.2.355-14

## Giới thiệu

`LibsUiComponentsInputsAddComponent` là một standalone Angular component để quản lý danh sách các input fields động. Component cho phép người dùng thêm/xóa fields, validate từng field riêng biệt, và kiểm tra duplicate values.

### Tính năng

- ✅ Thêm/xóa input fields động
- ✅ Validation cho từng field riêng biệt
- ✅ Kiểm tra duplicate values
- ✅ Min/Max items configuration
- ✅ Custom button configuration
- ✅ OnPush Change Detection
- ✅ Angular Signals
- ✅ Two-way binding với items array

## Khi nào sử dụng

- Cần nhập nhiều giá trị cùng loại (emails, phone numbers, URLs...)
- Cho phép người dùng thêm/xóa fields động
- Cần validation cho từng field riêng biệt
- Quản lý danh sách inputs với min/max items
- Cần kiểm tra duplicate values giữa các fields

## Important Notes

⚠️ **fieldNameBind Required**: Phải truyền fieldNameBind để xác định property name trong items array.

⚠️ **items Model**: items là model (two-way binding), component tự động cập nhật array khi add/remove.

⚠️ **Validation Timing**: Validation chạy sau 250ms debounce khi value change hoặc remove item.

⚠️ **ignoreValidEmptyField**: Khi true, chỉ cần ít nhất 1 field có giá trị là valid (không bắt buộc tất cả).

## Cài đặt

```bash
# npm
npm install @libs-ui/components-inputs-add

# yarn
yarn add @libs-ui/components-inputs-add
```

## Import

```typescript
import { LibsUiComponentsInputsAddComponent, IInputAdd, IInputAddFunctionControlEvent } from '@libs-ui/components-inputs-add';

@Component({
  standalone: true,
  imports: [LibsUiComponentsInputsAddComponent],
  // ...
})
export class YourComponent {}
```

## Ví dụ

### Basic

```html
<libs_ui-components-inputs-add
  [fieldNameBind]="'email'"
  [(items)]="emailItems"
  [placeholder]="'Nhập email'"
  [labelConfig]="{
    labelLeft: 'Email addresses',
    required: true
  }" />
```

```typescript
export class ExampleComponent {
  emailItems = signal<Array<IInputAdd>>([{ email: '' }]);
}
```

### Min/Max Items

```html
<libs_ui-components-inputs-add
  [fieldNameBind]="'phone'"
  [(items)]="phoneItems"
  [minItems]="2"
  [maxItems]="10"
  [placeholder]="'Nhập số điện thoại'" />
```

### With Validation & Duplicate Check

```html
<libs_ui-components-inputs-add
  [fieldNameBind]="'url'"
  [(items)]="urlItems"
  [placeholder]="'https://example.com'"
  [validRequired]="{
    isRequired: true,
    message: 'URL is required'
  }"
  [validDuplicate]="{
    message: 'URL already exists'
  }"
  (outValueChange)="onValueChange($event)"
  (outFunctionsControl)="onFunctionsControl($event)" />
```

```typescript
export class ExampleComponent {
  urlItems = signal<Array<IInputAdd>>([{ url: '' }]);
  functionControls = signal<IInputAddFunctionControlEvent | null>(null);

  onValueChange(event: IEmitValueChange) {
    console.log('Value changed:', event.value, event.item);
  }

  async validateAll() {
    const isValid = await this.functionControls()?.checkIsValid();
    console.log('Is valid:', isValid);
  }
}
```

### Custom Button & Ignore Empty Field Validation

```html
<libs_ui-components-inputs-add
  [fieldNameBind]="'tag'"
  [(items)]="tagItems"
  [maxItems]="8"
  [ignoreValidEmptyField]="true"
  [addItemButtonConfig]="{
    type: 'button-primary',
    label: 'Add Tag',
    classIconLeft: 'libs-ui-icon-add'
  }"
  [validRequired]="{
    isRequired: true,
    message: 'At least one tag is required'
  }" />
```

## API

### libs_ui-components-inputs-add

#### Inputs

| Property                           | Type                             | Default                                   | Description                               |
| ---------------------------------- | -------------------------------- | ----------------------------------------- | ----------------------------------------- |
| `[acceptNegativeValue]`            | `boolean`                        | `false`                                   | Cho phép giá trị âm (với dataType number) |
| `[addItemButtonConfig]`            | `IButton`                        | `undefined`                               | Cấu hình button "Add New"                 |
| `[autoAddZeroLessThan10InTypeInt]` | `boolean`                        | `false`                                   | Tự động thêm số 0 nếu < 10 (kiểu int)     |
| `[autoRemoveEmoji]`                | `boolean`                        | `false`                                   | Tự động loại bỏ emoji khỏi input          |
| `[backgroundNone]`                 | `boolean`                        | `false`                                   | Sử dụng background trong suốt             |
| `[borderError]`                    | `boolean`                        | `false`                                   | Hiển thị border màu đỏ khi có lỗi         |
| `[classContainerBottomInput]`      | `string`                         | `undefined`                               | Class CSS cho container dưới input        |
| `[classContainerInput]`            | `string`                         | `undefined`                               | Class CSS cho container bao quanh input   |
| `[classInclude]`                   | `string`                         | `undefined`                               | Class CSS cho container chính             |
| `[classIncludeInput]`              | `string`                         | `undefined`                               | Class CSS cho mỗi input field             |
| `[classMessageErrorInclude]`       | `string`                         | `undefined`                               | Class CSS cho message lỗi                 |
| `[configItemAddToItems]`           | `IInputAdd`                      | `undefined`                               | Template config khi add item mới          |
| `[configUnitLeft]`                 | `IInputValidUnitConfig`          | `{ fieldKey: "id", fieldLabel: "label" }` | Cấu hình hiển thị unit bên trái           |
| `[configUnitRight]`                | `IInputValidUnitConfig`          | `{ fieldKey: "id", fieldLabel: "label" }` | Cấu hình hiển thị unit bên phải           |
| `[dataType]`                       | `TYPE_DATA_TYPE_INPUT`           | `undefined`                               | Loại dữ liệu: text, number, email...      |
| `[defaultHeight]`                  | `number`                         | `undefined`                               | Chiều cao mặc định                        |
| `[disable]`                        | `boolean`                        | `false`                                   | Disable tất cả inputs                     |
| `[emitEmptyInDataTypeNumber]`      | `boolean`                        | `false`                                   | Emit giá trị rỗng khi dataType là number  |
| `[fieldNameBind]`                  | `string`                         | `required`                                | Tên property trong items array            |
| `[fixedFloat]`                     | `number`                         | `undefined`                               | Số lượng số lẻ sau dấu phẩy               |
| `[focusTimeOut]`                   | `number`                         | `undefined`                               | Thời gian delay focus                     |
| `[formInputSpacing]`               | `number`                         | `8`                                       | Khoảng cách giữa các input (px)           |
| `[functionValid]`                  | `TYPE_FUNCTION_INPUT_VALID`      | `undefined`                               | Hàm validate tùy chỉnh                    |
| `[ignoreAddItem]`                  | `boolean`                        | `false`                                   | Ẩn button "Add New"                       |
| `[ignoreRemove]`                   | `boolean`                        | `false`                                   | Ẩn button remove                          |
| `[ignoreShowError]`                | `boolean`                        | `false`                                   | Ẩn hiển thị lỗi                           |
| `[ignoreStopPropagationEvent]`     | `boolean`                        | `false`                                   | Không chặn sự kiện lan truyền             |
| `[ignoreUnitRightClassReadOnly]`   | `boolean`                        | `false`                                   | Bỏ class readonly cho unit phải           |
| `[ignoreValidEmptyField]`          | `boolean`                        | `false`                                   | Chỉ cần ít nhất 1 field có giá trị        |
| `[ignoreWidthInput100]`            | `boolean`                        | `false`                                   | Bỏ width 100% cho input                   |
| `[isBaselineStyle]`                | `boolean`                        | `false`                                   | Căn chỉnh baseline items                  |
| `[items]`                          | `Array<IInputAdd>`               | `required`                                | Array items (model binding)               |
| `[keepPlaceholderOnly]`            | `boolean`                        | `false`                                   | Chỉ giữ placeholder                       |
| `[keySelectedUnitLeft]`            | `any`                            | `undefined`                               | Key unit trái đang chọn                   |
| `[keySelectedUnitRight]`           | `any`                            | `undefined`                               | Key unit phải đang chọn                   |
| `[labelConfig]`                    | `ILabel`                         | `undefined`                               | Cấu hình label cho component              |
| `[leftTemplateItems]`              | `ITemplateRightLeftItem`         | `undefined`                               | Template item bên trái                    |
| `[maxItems]`                       | `number`                         | `5`                                       | Số lượng items tối đa                     |
| `[maxLength]`                      | `number`                         | `undefined`                               | Độ dài tối đa cho input                   |
| `[maxLengthNumberCount]`           | `number`                         | `undefined`                               | Max length cho number count               |
| `[maxValueNumber]`                 | `number`                         | `undefined`                               | Giá trị số tối đa                         |
| `[minItems]`                       | `number`                         | `1`                                       | Số lượng items tối thiểu                  |
| `[minValueNumber]`                 | `number`                         | `undefined`                               | Giá trị số tối thiểu                      |
| `[noBorder]`                       | `boolean`                        | `false`                                   | Không hiển thị border                     |
| `[onlyAcceptNegativeValue]`        | `boolean`                        | `false`                                   | Chỉ chấp nhận số âm                       |
| `[paddingRightCustomSpecific]`     | `number`                         | `undefined`                               | Custom padding phải                       |
| `[placeholder]`                    | `string`                         | `undefined`                               | Placeholder cho inputs                    |
| `[positionMessageErrorStartInput]` | `boolean`                        | `false`                                   | Hiển thị lỗi bắt đầu từ input             |
| `[readonly]`                       | `boolean`                        | `false`                                   | Readonly tất cả inputs                    |
| `[resetAutoCompletePassword]`      | `boolean`                        | `false`                                   | Reset autocomplete password               |
| `[resize]`                         | `'auto' \| 'none'`               | `undefined`                               | Khả năng resize                           |
| `[rightTemplateItems]`             | `ITemplateRightLeftItem`         | `undefined`                               | Template item bên phải                    |
| `[showCount]`                      | `boolean`                        | `false`                                   | Hiển thị đếm ký tự                        |
| `[tagInput]`                       | `TYPE_TAG_INPUT`                 | `undefined`                               | Tag input (textarea, input...)            |
| `[templateLeftBottomInput]`        | `TemplateRef<TYPE_TEMPLATE_REF>` | `undefined`                               | Template dưới trái input                  |
| `[templateLeftOutlet]`             | `TemplateRef<TYPE_TEMPLATE_REF>` | `undefined`                               | Template outlet bên trái                  |
| `[templateRightBottomInput]`       | `TemplateRef<TYPE_TEMPLATE_REF>` | `undefined`                               | Template dưới phải input                  |
| `[templateRightOutlet]`            | `TemplateRef<TYPE_TEMPLATE_REF>` | `undefined`                               | Template outlet bên phải                  |
| `[typeInput]`                      | `TYPE_INPUT`                     | `undefined`                               | Loại input (text, password...)            |
| `[unitsLeft]`                      | `Array<any>`                     | `undefined`                               | Danh sách units bên trái                  |
| `[unitsRight]`                     | `Array<any>`                     | `undefined`                               | Danh sách units bên phải                  |
| `[useColorModeExist]`              | `boolean`                        | `false`                                   | Sử dụng color mode có sẵn                 |
| `[validDuplicate]`                 | `IMessageTranslate`              | `undefined`                               | Message khi có giá trị duplicate          |
| `[validMaxValue]`                  | `IMessageTranslate`              | `undefined`                               | Validate giá trị tối đa                   |
| `[validMaxLength]`                 | `IMessageTranslate`              | `undefined`                               | Validate độ dài tối đa                    |
| `[validMinLength]`                 | `IValidLength`                   | `undefined`                               | Validate độ dài tối thiểu                 |
| `[validMinValue]`                  | `IMessageTranslate`              | `undefined`                               | Validate giá trị tối thiểu                |
| `[validPattern]`                   | `Array<IValidPattern>`           | `undefined`                               | Validate theo pattern regex               |
| `[validRequired]`                  | `IValidRequired`                 | `undefined`                               | Validation required                       |
| `[valuePatternShowError]`          | `boolean`                        | `false`                                   | Hiển thị lỗi pattern                      |
| `[valueUpDownNumber]`              | `number`                         | `undefined`                               | Bước nhảy tăng giảm số                    |

#### Outputs

| Property                | Type                            | Description                      |
| ----------------------- | ------------------------------- | -------------------------------- |
| `(outAddItem)`          | `IInputAdd`                     | Emit khi thêm item mới           |
| `(outClickButtonLabel)` | `IButton`                       | Emit khi click button trên label |
| `(outFunctionsControl)` | `IInputAddFunctionControlEvent` | Emit function controls           |
| `(outLabelLeftClick)`   | `MouseEvent`                    | Emit khi click label trái        |
| `(outLabelRightClick)`  | `boolean`                       | Emit khi click label phải        |
| `(outRemove)`           | `IInputAdd`                     | Emit khi xóa item                |
| `(outSwitchEventLabel)` | `ISwitchEvent`                  | Emit sự kiện switch trên label   |
| `(outValueChange)`      | `IEmitValueChange`              | Emit khi giá trị thay đổi        |

#### FunctionsControl Methods

| Method                     | Description                                               |
| -------------------------- | --------------------------------------------------------- |
| `checkIsValid()`           | Kiểm tra validation tất cả items, trả về Promise<boolean> |
| `setMessageError(message)` | Set error message cho tất cả items                        |

## Types & Interfaces

```typescript
interface IInputAdd extends Record<string, any> {
  uniqKey?: string;
  disable?: boolean;
  readonly?: boolean;
  ignoreRemove?: boolean;
  labelConfig?: ILabel;
  placeholder?: string;
  maxValueNumber?: number;
  minValueNumber?: number;
  maxLength?: number;

  unitsLeft?: Array<any>;
  configUnitLeft?: IInputValidUnitConfig;
  keySelectedUnitLeft?: any;

  unitsRight?: Array<any>;
  configUnitRight?: IInputValidUnitConfig;
  keySelectedUnitRight?: any;

  templateLeftOutlet?: TemplateRef<TYPE_TEMPLATE_REF>;
  templateRightOutlet?: TemplateRef<TYPE_TEMPLATE_REF>;
  functionControl?: IInputValidFunctionControlEvent;

  [key: string]: any;
}

interface IInputAddFunctionControlEvent {
  checkIsValid: () => Promise<boolean>;
  setMessageError: (message: string) => Promise<void>;
}

interface IEmitValueChange {
  value: string | number;
  item: IInputAdd;
}
```

## Công nghệ

| Technology      | Version | Purpose          |
| --------------- | ------- | ---------------- |
| Angular         | 18+     | Framework        |
| Angular Signals | -       | State management |
| TailwindCSS     | 3.x     | Styling          |
| OnPush          | -       | Change Detection |

## Demo

```bash
npx nx serve core-ui
```

Truy cập: `http://localhost:4500/inputs/add`

## Unit Tests

```bash
# Chạy tests
npx nx test components-inputs-add

# Coverage
npx nx test components-inputs-add --coverage

# Watch mode
npx nx test components-inputs-add --watch
```

## License

MIT