@libs-ui/components-inputs-input
v0.2.357-4
Published
> Universal input component hỗ trợ text, number, textarea và iframe-textarea với validation, formatting và customization đầy đủ.
Readme
@libs-ui/components-inputs-input
Universal input component hỗ trợ text, number, textarea và iframe-textarea với validation, formatting và customization đầy đủ.
Giới thiệu
LibsUiComponentsInputsInputComponent là Angular standalone component đa năng cho việc nhập liệu. Component hỗ trợ 4 kiểu dữ liệu (string, int, float, bigint), 3 loại tag (input, textarea, iframe-textarea), tự động format number theo locale (VI/EN), auto-resize textarea, và cung cấp API điều khiển ngoài qua FunctionsControl. Tích hợp sẵn OnPush Change Detection và Angular Signals.
Tính năng
- ✅ Hỗ trợ 4 kiểu dữ liệu:
string,int,float,bigint - ✅ Hỗ trợ 3 loại tag:
input,textarea,iframe-textarea - ✅ Tự động format number theo ngôn ngữ (VI dùng
.phân cách nghìn, EN dùng,) - ✅ Auto-resize textarea theo nội dung (min/max height có thể cấu hình)
- ✅ Validation min/max value cho number input
- ✅ Hiển thị character count (
showCount) - ✅ Icon left/right kèm popover tooltip
- ✅ Template slot tùy chỉnh dưới input (trái/phải)
- ✅ Nút tăng/giảm (up/down) cho number input
- ✅ Drag & drop file vào input
- ✅ Điều khiển từ bên ngoài qua
FunctionsControl(focus, blur, insert, reset, selectAll) - ✅
iframe-textarea: cô lập CSS toàn cục, style riêng quaiframeTextareaCustomStyle - ✅ OnPush Change Detection + Angular Signals
Khi nào sử dụng
- Cần input field cơ bản cho text hoặc number
- Cần textarea tự động co giãn theo nội dung
- Cần format number theo locale VI/EN
- Cần validation min/max cho number input
- Cần hiển thị số ký tự đã nhập
- Cần custom icon trái/phải với popover tooltip
- Cần điều khiển input từ component cha (focus, insert text, reset)
- Cần drag & drop file vào vùng input
- Cần cô lập editor content khỏi CSS toàn cục (dùng
iframe-textarea)
Cài đặt
npm install @libs-ui/components-inputs-inputImport
import { LibsUiComponentsInputsInputComponent } from '@libs-ui/components-inputs-input';
@Component({
standalone: true,
imports: [LibsUiComponentsInputsInputComponent],
})
export class YourComponent {}Import thêm types/interfaces khi cần:
import {
LibsUiComponentsInputsInputComponent,
IInputFunctionControlEvent,
IFocusAndBlurEvent,
IIframeTextareaCustomStyle,
TYPE_DATA_TYPE_INPUT,
TYPE_TAG_INPUT,
TYPE_INPUT,
TYPE_INPUT_RESIZE_MODE,
TYPE_MODE_INPUT,
} from '@libs-ui/components-inputs-input';Ví dụ sử dụng
Basic Text Input
<libs_ui-components-inputs-input
[(value)]="textValue"
[placeholder]="'Nhập nội dung'"
(outChange)="handlerTextChange($event)" />protected textValue = signal('');
handlerTextChange(value: string): void {
// value đã được emit
}Number Input với Min/Max
<libs_ui-components-inputs-input
[dataType]="'int'"
[(value)]="numberValue"
[minValueNumber]="0"
[maxValueNumber]="100"
[placeholder]="'Nhập số 0-100'"
(outChange)="handlerNumberChange($event)" />protected numberValue = signal<number>(0);
handlerNumberChange(value: number): void {
// value là number đã được parse
}Float Input với Fixed Decimal và Giá trị âm
<libs_ui-components-inputs-input
[dataType]="'float'"
[(value)]="floatValue"
[fixedFloat]="2"
[acceptNegativeValue]="true"
[placeholder]="'Nhập số thực (vd: -99.99)'"
(outChange)="handlerFloatChange($event)" />protected floatValue = signal<number>(0);
handlerFloatChange(value: number): void {
// value là số thực, tối đa 2 chữ số thập phân
}Bigint Input với Precision tùy chỉnh
<libs_ui-components-inputs-input
[dataType]="'bigint'"
[(value)]="bigintValue"
[maxLengthNumberCount]="19"
[fixedFloat]="4"
[placeholder]="'Nhập giá trị lớn'"
(outChange)="handlerBigintChange($event)" />protected bigintValue = signal('');
handlerBigintChange(value: string): void {
// bigint trả về string thay vì number
}Textarea với Auto-resize và Character Count
<libs_ui-components-inputs-input
[tagInput]="'textarea'"
[(value)]="textareaValue"
[defaultHeight]="80"
[minHeightTextArea]="80"
[maxHeightTextArea]="300"
[maxLength]="500"
[showCount]="true"
[placeholder]="'Nhập mô tả...'"
(outChange)="handlerTextareaChange($event)"
(outHeightAreaChange)="handlerHeightChange($event)" />protected textareaValue = signal('');
handlerTextareaChange(value: string): void {
// value là chuỗi text
}
handlerHeightChange(data: { isChange: boolean; height: number }): void {
// data.height là chiều cao mới của textarea
}Password Input
<libs_ui-components-inputs-input
[typeInput]="'password'"
[(value)]="passwordValue"
[resetAutoCompletePassword]="true"
[placeholder]="'Nhập mật khẩu'"
(outChange)="handlerPasswordChange($event)" />protected passwordValue = signal('');
handlerPasswordChange(value: string): void {
// xử lý password
}Input với Icon Left/Right và Popover
<libs_ui-components-inputs-input
[(value)]="searchValue"
[iconLeftClass]="'libs-ui-icon-search'"
[iconRightClass]="'libs-ui-icon-close'"
[popoverContentIconLeft]="'Tìm kiếm'"
[popoverContentIconRight]="'Xóa nội dung'"
[placeholder]="'Tìm kiếm...'"
(outChange)="handlerSearchChange($event)"
(outIconLeft)="handlerIconLeftClick($event)"
(outIconRight)="handlerIconRightClick($event)" />protected searchValue = signal('');
handlerSearchChange(value: string): void {
// xử lý tìm kiếm
}
handlerIconLeftClick(eventName: string): void {
// eventName = 'click'
}
handlerIconRightClick(eventName: string): void {
this.searchValue.set('');
}External Control qua FunctionsControl
<libs_ui-components-inputs-input
[(value)]="controlledValue"
[placeholder]="'Điều khiển từ bên ngoài'"
(outFunctionsControl)="handlerFunctionsControl($event)" />
<button (click)="handlerFocusInput()">Focus</button>
<button (click)="handlerInsertText()">Insert "Hello"</button>
<button (click)="handlerResetInput()">Reset</button>
<button (click)="handlerSelectAll()">Select All</button>import { signal } from '@angular/core';
import { IInputFunctionControlEvent } from '@libs-ui/components-inputs-input';
protected controlledValue = signal('');
private functionControl = signal<IInputFunctionControlEvent | null>(null);
handlerFunctionsControl(control: IInputFunctionControlEvent): void {
this.functionControl.set(control);
}
async handlerFocusInput(): Promise<void> {
await this.functionControl()?.focus();
}
async handlerInsertText(): Promise<void> {
await this.functionControl()?.insertContent('Hello');
}
async handlerResetInput(): Promise<void> {
await this.functionControl()?.resetValue();
}
async handlerSelectAll(): Promise<void> {
await this.functionControl()?.selectAllContent();
}IFrame Textarea với Custom Style
<libs_ui-components-inputs-input
[tagInput]="'iframe-textarea'"
[(value)]="iframeValue"
[iframeTextareaCustomStyle]="iframeStyle"
[defaultHeight]="120"
[maxHeightTextArea]="400"
[placeholder]="'Nhập nội dung (cô lập CSS)'"
(outChange)="handlerIframeChange($event)"
(outFunctionsControl)="handlerIframeFunctionsControl($event)" />import { IIframeTextareaCustomStyle, IInputFunctionControlEvent } from '@libs-ui/components-inputs-input';
protected iframeValue = signal('');
protected iframeStyle: IIframeTextareaCustomStyle = {
fontSize: '14px',
lineHeight: '1.6',
padding: '8px 12px',
color: '#333333',
backgroundColor: '#ffffff',
borderColor: '#e2e8f0',
borderRadius: '6px',
};
handlerIframeChange(value: string): void {
// value là nội dung textarea bên trong iframe
}
handlerIframeFunctionsControl(control: IInputFunctionControlEvent): void {
// lưu control để điều khiển từ bên ngoài
}Number Input với Nút Up/Down
<libs_ui-components-inputs-input
[dataType]="'int'"
[(value)]="stepValue"
[minValueNumber]="0"
[maxValueNumber]="10"
[valueUpDownNumber]="1"
[placeholder]="'0'"
(outChange)="handlerStepChange($event)"
(outChangeValueByButtonUpDown)="handlerUpDownClick()" />protected stepValue = signal<number>(0);
handlerStepChange(value: number): void {
// value thay đổi
}
handlerUpDownClick(): void {
// được gọi khi click nút tăng/giảm
}Textarea không xuống dòng khi Enter
<libs_ui-components-inputs-input
[tagInput]="'textarea'"
[(value)]="singleLineValue"
[textAreaEnterNotNewLine]="true"
[placeholder]="'Enter để submit, Shift+Enter xuống dòng'"
(outChange)="handlerChange($event)"
(outEnterEvent)="handlerEnter($event)" />import { IEvent } from '@libs-ui/interfaces-types';
protected singleLineValue = signal('');
handlerChange(value: string): void {
// xử lý thay đổi
}
handlerEnter(event: IEvent): void {
event.stopPropagation();
// xử lý submit khi nhấn Enter
}Drag & Drop File vào Input
<libs_ui-components-inputs-input
[(value)]="fileDropValue"
[placeholder]="'Kéo file vào đây hoặc nhập text'"
(outChange)="handlerChange($event)"
(outFilesDrop)="handlerFilesDrop($event)"
(outFileDrop)="handlerFileDrop($event)" />protected fileDropValue = signal('');
handlerChange(value: string): void {
// xử lý text thay đổi
}
handlerFilesDrop(files: File[]): void {
// xử lý nhiều file drop
files.forEach(file => console.log(file.name));
}
handlerFileDrop(file: File): void {
// xử lý từng file riêng lẻ
}@Input()
| Input | Type | Default | Mô tả | Ví dụ |
|---|---|---|---|---|
| [acceptNegativeValue] | boolean | false | Cho phép nhập giá trị âm | [acceptNegativeValue]="true" |
| [acceptOnlyClickIcon] | boolean | false | Cho phép click icon kể cả khi disable/readonly | [acceptOnlyClickIcon]="true" |
| [autoAddZeroLessThan10InTypeInt] | boolean | false | Tự động thêm số 0 trước nếu giá trị < 10 (dataType=int) | [autoAddZeroLessThan10InTypeInt]="true" |
| [autoRemoveEmoji] | boolean | false | Tự động loại bỏ emoji khỏi chuỗi nhập | [autoRemoveEmoji]="true" |
| [backgroundNone] | boolean | false | Làm trong suốt background của input | [backgroundNone]="true" |
| [blurTimeOut] | number | 32 | Delay (ms) trước khi blur được thực thi | [blurTimeOut]="50" |
| [borderError] | boolean | false | Hiển thị border màu đỏ báo lỗi | [borderError]="hasError()" |
| [classContainerBottomInput] | string | ' ' | CSS class cho container bên dưới input | [classContainerBottomInput]="'flex gap-2'" |
| [classContainerInput] | string | 'w-full' | CSS class cho container bọc input | [classContainerInput]="'w-64'" |
| [classInclude] | string | ' w-full ' | CSS class bổ sung cho element input | [classInclude]="'text-right'" |
| [dataType] | TYPE_DATA_TYPE_INPUT | 'string' | Kiểu dữ liệu: 'string' | 'int' | 'float' | 'bigint' | [dataType]="'int'" |
| [defaultHeight] | number | 32 | Chiều cao mặc định (px) | [defaultHeight]="40" |
| [disable] | boolean | false | Vô hiệu hóa input | [disable]="isLoading()" |
| [emitEmptyInDataTypeNumber] | boolean | false | Emit undefined khi xóa hết giá trị number | [emitEmptyInDataTypeNumber]="true" |
| [(fixedFloat)] | number | undefined | Số chữ số thập phân tối đa (dùng với float/bigint) | [(fixedFloat)]="2" |
| [focusInput] | boolean | false | Tự động focus input khi render | [focusInput]="true" |
| [focusTimeOut] | number | 600 | Delay (ms) trước khi focus được thực thi | [focusTimeOut]="300" |
| [iconLeftClass] | string | '' | CSS class cho icon bên trái | [iconLeftClass]="'libs-ui-icon-search'" |
| [iconRightClass] | string | undefined | CSS class cho icon bên phải | [iconRightClass]="'libs-ui-icon-close'" |
| [iframeTextareaCustomStyle] | IIframeTextareaCustomStyle | undefined | Custom style cho nội dung bên trong iframe-textarea | [iframeTextareaCustomStyle]="myStyle" |
| [ignoreBlockInputMaxValue] | boolean | undefined | Không chặn nhập khi vượt quá maxValueNumber | [ignoreBlockInputMaxValue]="true" |
| [ignoreStopPropagationEvent] | boolean | undefined | Không gọi stopPropagation trên sự kiện native | [ignoreStopPropagationEvent]="true" |
| [ignoreWidthInput100] | boolean | undefined | Không áp dụng w-full cho element input | [ignoreWidthInput100]="true" |
| [keepPlaceholderOnly] | boolean | false | Chỉ hiển thị placeholder, ẩn giá trị thực | [keepPlaceholderOnly]="true" |
| [keepZeroInTypeInt] | boolean | false | Giữ số 0 ở đầu khi nhập (dataType=int) | [keepZeroInTypeInt]="true" |
| [(maxLength)] | number | undefined | Số ký tự tối đa được nhập | [(maxLength)]="100" |
| [(maxLengthNumberCount)] | number | undefined | Số chữ số tối đa phần nguyên (dataType=bigint) | [(maxLengthNumberCount)]="19" |
| [maxHeightTextArea] | number | 250 | Chiều cao tối đa textarea (px) | [maxHeightTextArea]="400" |
| [(maxValueNumber)] | number | undefined | Giá trị tối đa cho number input | [(maxValueNumber)]="100" |
| [minHeightTextArea] | number | undefined | Chiều cao tối thiểu textarea (px) | [minHeightTextArea]="80" |
| [(minValueNumber)] | number | undefined | Giá trị tối thiểu cho number input | [(minValueNumber)]="0" |
| [modeInput] | TYPE_MODE_INPUT | 'text' | inputmode cho mobile keyboard: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | [modeInput]="'numeric'" |
| [noBorder] | boolean | false | Ẩn border của input | [noBorder]="true" |
| [onlyAcceptNegativeValue] | boolean | false | Chỉ chấp nhận số âm (tự động set acceptNegativeValue=true) | [onlyAcceptNegativeValue]="true" |
| [placeholder] | string | ' ' | Placeholder text (hỗ trợ i18n key) | [placeholder]="'i18n_enter_value'" |
| [popoverContentIconLeft] | string | '' | Nội dung popover khi hover icon trái | [popoverContentIconLeft]="'Tìm kiếm'" |
| [popoverContentIconRight] | string | '' | Nội dung popover khi hover icon phải | [popoverContentIconRight]="'Xóa'" |
| [readonly] | boolean | false | Chỉ đọc, không cho phép chỉnh sửa | [readonly]="isViewing()" |
| [resetAutoCompletePassword] | boolean | false | Tắt autocomplete của browser (dùng cho password) | [resetAutoCompletePassword]="true" |
| [resize] | TYPE_INPUT_RESIZE_MODE | 'vertical' | Chế độ resize textarea: 'auto' | 'vertical' | 'horizontal' | 'none' | [resize]="'none'" |
| [selectAllTimeOut] | number | 600 | Delay (ms) trước khi selectAll được thực thi | [selectAllTimeOut]="300" |
| [setIconRightColorSameColorDisableReadOnly] | boolean | false | Icon phải có màu giống màu text disable/readonly | [setIconRightColorSameColorDisableReadOnly]="true" |
| [showCount] | boolean | false | Hiển thị số ký tự đã nhập (dataType=string) | [showCount]="true" |
| [tabInsertContentTagInput] | boolean | false | Cho phép dùng phím Tab để chèn ký tự tab vào input | [tabInsertContentTagInput]="true" |
| [tagInput] | TYPE_TAG_INPUT | 'input' | Loại element: 'input' | 'textarea' | 'iframe-textarea' | [tagInput]="'textarea'" |
| [templateLeftBottomInput] | TemplateRef | undefined | Template tùy chỉnh ở góc dưới trái | [templateLeftBottomInput]="myLeftTpl" |
| [templateRightBottomInput] | TemplateRef | undefined | Template tùy chỉnh ở góc dưới phải | [templateRightBottomInput]="myRightTpl" |
| [textAreaEnterNotNewLine] | boolean | false | Enter không xuống dòng trong textarea (dùng Shift+Enter để xuống dòng) | [textAreaEnterNotNewLine]="true" |
| [typeInput] | TYPE_INPUT | 'text' | HTML input type: 'text' | 'number' | 'password' | [typeInput]="'password'" |
| [useColorModeExist] | boolean | false | Áp dụng màu text theo color mode của hệ thống | [useColorModeExist]="true" |
| [(value)] | string \| number | '' | Giá trị input (two-way binding qua model) | [(value)]="myValue" |
| [valueUpDownNumber] | number | undefined | Bước nhảy mỗi lần nhấn nút tăng/giảm | [valueUpDownNumber]="5" |
| [zIndexPopoverContent] | number | 10 | Z-index của popover content | [zIndexPopoverContent]="100" |
@Output()
| Output | Type | Mô tả | Handler TS | Binding HTML |
|---|---|---|---|---|
| (outChange) | string \| number \| undefined | Emit khi value thay đổi, đã được parse theo dataType | handlerChange(value: string \| number): void { /* xử lý */ } | (outChange)="handlerChange($event)" |
| (outChangeValueByButtonUpDown) | void | Emit khi người dùng click nút tăng hoặc giảm | handlerUpDownClick(): void { /* xử lý */ } | (outChangeValueByButtonUpDown)="handlerUpDownClick()" |
| (outEnterEvent) | IEvent | Emit khi nhấn phím Enter (trong textarea nếu textAreaEnterNotNewLine=true thì chỉ Enter không Shift) | handlerEnter(event: IEvent): void { event.stopPropagation(); /* xử lý submit */ } | (outEnterEvent)="handlerEnter($event)" |
| (outFileDrop) | File | Emit từng file riêng lẻ khi drop hoặc paste file vào input | handlerFileDrop(file: File): void { event.stopPropagation(); /* xử lý file */ } | (outFileDrop)="handlerFileDrop($event)" |
| (outFilesDrop) | Array<File> | Emit toàn bộ danh sách file khi drop hoặc paste vào input | handlerFilesDrop(files: File[]): void { event.stopPropagation(); /* xử lý danh sách */ } | (outFilesDrop)="handlerFilesDrop($event)" |
| (outFocusAndBlurEvent) | IFocusAndBlurEvent | Emit khi input được focus hoặc blur, kèm name: 'focus' \| 'blur' | handlerFocusBlur(e: IFocusAndBlurEvent): void { e.event.stopPropagation(); /* xử lý */ } | (outFocusAndBlurEvent)="handlerFocusBlur($event)" |
| (outFunctionsControl) | IInputFunctionControlEvent | Emit API điều khiển: focus, blur, insert, reset, selectAll | handlerFunctionsControl(ctrl: IInputFunctionControlEvent): void { this.ctrl.set(ctrl); } | (outFunctionsControl)="handlerFunctionsControl($event)" |
| (outHeightAreaChange) | { isChange: boolean; height: number } | Emit khi chiều cao textarea thay đổi do auto-resize | handlerHeightChange(data: { isChange: boolean; height: number }): void { /* xử lý */ } | (outHeightAreaChange)="handlerHeightChange($event)" |
| (outIconLeft) | string | Emit khi click icon trái, giá trị là tên event ('click') | handlerIconLeft(eventName: string): void { eventName; /* 'click' */ } | (outIconLeft)="handlerIconLeft($event)" |
| (outIconRight) | string | Emit khi click icon phải, giá trị là tên event ('click') | handlerIconRight(eventName: string): void { eventName; /* 'click' */ } | (outIconRight)="handlerIconRight($event)" |
| (outInputEvent) | IEvent | Emit native input event gốc (mỗi lần DOM input event xảy ra) | handlerInputEvent(e: IEvent): void { e.stopPropagation(); /* xử lý native event */ } | (outInputEvent)="handlerInputEvent($event)" |
FunctionsControl API
Nhận instance IInputFunctionControlEvent qua output (outFunctionsControl). Tất cả methods là async.
| Method | Signature | Mô tả |
|---|---|---|
| focus | (emitEvent?: boolean) => Promise<void> | Focus vào input. Nếu emitEvent=true thì emit outFocusAndBlurEvent |
| blur | (emitEvent?: boolean) => Promise<void> | Blur khỏi input. Nếu emitEvent=true thì emit outFocusAndBlurEvent |
| insertContent | (data: string \| number) => Promise<void> | Chèn nội dung vào vị trí con trỏ hiện tại |
| resetValue | () => Promise<void> | Reset value về chuỗi rỗng và emit outChange |
| getElementValue | () => Promise<string> \| undefined | Lấy giá trị raw từ DOM element |
| checkAndDisableUpDownButton | (value: number) => Promise<void> | Kiểm tra và cập nhật trạng thái disable của nút tăng/giảm |
| selectAllContent | () => Promise<void> | Chọn toàn bộ nội dung trong input |
Types & Interfaces
import {
TYPE_INPUT,
TYPE_DATA_TYPE_INPUT,
TYPE_TAG_INPUT,
TYPE_MODE_INPUT,
TYPE_INPUT_RESIZE_MODE,
IInputFunctionControlEvent,
IFocusAndBlurEvent,
IIframeTextareaCustomStyle,
} from '@libs-ui/components-inputs-input';
// Loại HTML element
type TYPE_TAG_INPUT = 'input' | 'textarea' | 'iframe-textarea';
// Kiểu dữ liệu
type TYPE_DATA_TYPE_INPUT = 'string' | 'int' | 'float' | 'bigint';
// HTML input type attribute
type TYPE_INPUT = 'text' | 'number' | 'password';
// inputmode attribute (mobile keyboard)
type TYPE_MODE_INPUT = 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal';
// Chế độ resize textarea
type TYPE_INPUT_RESIZE_MODE = 'auto' | 'vertical' | 'horizontal' | 'none' | undefined;
// API điều khiển từ component cha
interface IInputFunctionControlEvent {
focus: (emitEvent?: boolean) => Promise<void>;
blur: (emitEvent?: boolean) => Promise<void>;
insertContent: (data: string | number) => Promise<void>;
resetValue: () => Promise<void>;
getElementValue: () => Promise<string> | undefined;
checkAndDisableUpDownButton: (value: number) => Promise<void>;
selectAllContent: () => Promise<void>;
}
// Event focus/blur
interface IFocusAndBlurEvent {
name: 'focus' | 'blur';
event: Event;
}
// Custom style cho iframe-textarea
interface IIframeTextareaCustomStyle {
borderRadius?: string;
borderColor?: string;
padding?: string;
lineHeight?: string;
fontSize?: string;
height?: string;
color?: string;
backgroundColor?: string;
}Lưu ý quan trọng
⚠️ Number Formatting theo Locale: Component tự động format number theo ngôn ngữ hiện tại. Tiếng Việt (VI) dùng dấu . phân cách nghìn và , phân cách thập phân. Tiếng Anh (EN) dùng , phân cách nghìn và . phân cách thập phân.
⚠️ Giới hạn độ chính xác số: int tối đa 16-17 chữ số, float tối đa 15 chữ số phần nguyên + phần thập phân, bigint cấu hình được qua maxLengthNumberCount (default 19) và fixedFloat (default 4).
⚠️ value là model (two-way binding): Dùng [(value)]="mySignal" để binding hai chiều. Output (outChange) emit giá trị đã được parse theo dataType — với bigint trả về string, các loại khác trả về number hoặc string.
⚠️ iframe-textarea CSS isolation: Khi dùng tagInput="iframe-textarea", content được render bên trong <iframe> — CSS của trang cha không áp dụng vào bên trong. Dùng [iframeTextareaCustomStyle] để tùy chỉnh giao diện bên trong iframe (fontSize, padding, color, borderColor...).
⚠️ FunctionsControl timing: Output (outFunctionsControl) emit sau khi DOM element được khởi tạo xong. Lưu instance vào signal để dùng sau: private ctrl = signal<IInputFunctionControlEvent | null>(null).
⚠️ textAreaEnterNotNewLine với Shift+Enter: Khi [textAreaEnterNotNewLine]="true", nhấn Enter không xuống dòng và emit outEnterEvent. Dùng Shift+Enter để xuống dòng bình thường.
⚠️ emitEmptyInDataTypeNumber: Mặc định khi xóa hết giá trị number, component emit 0. Bật [emitEmptyInDataTypeNumber]="true" để emit undefined thay vì 0.
Demo
npx nx serve core-uiTruy cập: http://localhost:4500/inputs/input
