import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy, Optional, Output, Self, TemplateRef, ViewChild, ViewEncapsulation, } from '@angular/core'; import { AbstractControl, ControlValueAccessor, NgControl, Validators, } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { CommonModule } from '@angular/common'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { Subject, takeUntil } from 'rxjs'; // models import { ICaInput } from './config'; import { InputChangeValue, InputSvgRoutes } from './utils'; import { CommandsEvent, LabelColor } from './models'; // emums import { InputCommandsType, InputStringEnum } from './enums'; import { DropdownTemplateTypeEnum } from '../ca-input-dropdown/enums'; // pipes import { InputClassPipe, InputContainerClassPipe, InputDropdownArrowClassPipe, InputErrorPipe, InputPlaceholderIconRightClassPipe, InputPlaceholderTextClassPipe, InputTypePipe, LabelClassPipe, ShowClearPipe, ShowDropdownArrowPipe, ShowPlaceholderTextPipe, ShowValidCheckPipe, } from './pipes'; import { CaSvgPipe, LoadStatusColorPipe, ThousandSeparatorPipe, } from '../../pipes'; // mask import { NgxMaskModule } from 'ngx-mask'; import { NgxMaskService } from 'ngx-mask'; // components import { CaInputPasswordComponent } from './components/ca-input-password/ca-input-password.component'; import { CaInputPlaceholderIconComponent } from './components/ca-input-placeholder-icon/ca-input-placeholder-icon.component'; import { CaInputClearComponent } from './components/ca-input-clear/ca-input-clear.component'; import { CaAppTooltipV2Component } from '../ca-app-tooltip-v2/ca-app-tooltip-v2.component'; import { CaInputCommandsComponent } from './components/ca-input-commands/ca-input-commands.component'; import { CaSpinnerComponent } from '../ca-spinner/ca-spinner.component'; // directives import { CapsLockDirective } from './directives'; import { MaxValueDirective } from './directives'; import { RestrictInputDirective } from './directives'; import { PriceFormatDirective } from './directives'; import { CommandVisibleDirective } from './directives/command-visible.directive'; // class import { EventInputManager } from './base-classes/ca-input-event-manager'; // mixins import { InputCommandMixin } from './mixins/input-command.mixin'; import { InputHelperMixin } from './mixins/input-helper.mixin'; import { OptionModel } from '../ca-input-dropdown/models'; class InputBase { public inputElement!: ElementRef; public _inputConfig!: ICaInput; public isVisibleCommands: boolean = false; public maskApplier!: NgxMaskService; public thousandSeparatorPipe!: ThousandSeparatorPipe; public isFocusInput: boolean = false; public isEditInput: boolean = false; public isDropdownToggler: boolean = false; public isTouchedInput: boolean = false; public setCommandEvent!: EventInputManager; public handleToggleDropdownOptions!: EventEmitter; public onTouched = () => {}; public onChange(_: any): void {} public handleChangeInputAndUpdateControl(_: InputChangeValue): void {} } @Component({ selector: 'ca-input-test', imports: [ // Modules CommonModule, NgbModule, AngularSvgIconModule, NgxMaskModule, // Pipes InputErrorPipe, InputContainerClassPipe, InputClassPipe, InputPlaceholderTextClassPipe, ShowPlaceholderTextPipe, LabelClassPipe, CaSvgPipe, LoadStatusColorPipe, InputPlaceholderIconRightClassPipe, MaxValueDirective, InputTypePipe, ShowValidCheckPipe, ShowDropdownArrowPipe, InputDropdownArrowClassPipe, // Directives RestrictInputDirective, PriceFormatDirective, ShowClearPipe, CapsLockDirective, CommandVisibleDirective, // Components CaInputClearComponent, CaAppTooltipV2Component, CaInputPlaceholderIconComponent, CaInputPasswordComponent, CaInputCommandsComponent, CaSpinnerComponent, ], templateUrl: './input-test.component.html', styleUrl: './input-test.component.scss', encapsulation: ViewEncapsulation.None }) export class InputTestComponent extends InputCommandMixin(InputHelperMixin(InputBase)) implements ControlValueAccessor, AfterViewInit, OnDestroy { public inputValue: InputChangeValue = ''; public override _inputConfig!: ICaInput; private destroy$ = new Subject(); public inputSvgRoutes = InputSvgRoutes; @Input() dateTimePopover!: TemplateRef; @ViewChild('input', { static: true }) override inputElement!: ElementRef; @ViewChild(CaInputPlaceholderIconComponent) caInputPlaceholderIconComponent!: CaInputPlaceholderIconComponent; @Input() parentControl!: AbstractControl | null; @Input() selectedDropdownLabelColor!: LabelColor | null; @Input() template!: string; @Input() incorrectValue!: boolean; @Input() set inputConfig(config: ICaInput) { this._inputConfig = config; } @Input() activeItem!: OptionModel | null; @Output('handleToggleDropdownOptions') override handleToggleDropdownOptions: EventEmitter = new EventEmitter(); @Output('incorrectEvent') incorrectInput: EventEmitter = new EventEmitter(); @Output('blurInput') blurInput: EventEmitter = new EventEmitter(); @Output('focusInput') focusInputEvent: EventEmitter = new EventEmitter(); @Output('change') changeInput: EventEmitter = new EventEmitter(); @Output('commandEvent') commandEvent: EventEmitter = new EventEmitter(); @Output('clear') clearInputEvent: EventEmitter = new EventEmitter(); // Dropdown @Output('showHideDropdown') showHideDropdownEvent: EventEmitter = new EventEmitter(); @Output('dropDownKeyNavigation') dropDownKeyNavigationEvent: EventEmitter<{ keyCode: number; data: ICaInput | null; }> = new EventEmitter<{ keyCode: number; data: ICaInput | null; }>(); // events public override setCommandEvent = new EventInputManager( null, this.commandEvent ); public isCapsLockOn = false; public inputCommandsType = InputCommandsType; public inputStringEnum = InputStringEnum; public dropdownTemplateTypeEnum = DropdownTemplateTypeEnum; // Password public isTogglePassword: boolean = false; public get inputConfig() { return this._inputConfig; } constructor( @Optional() @Self() public ngControl: NgControl, public chdet: ChangeDetectorRef, public override maskApplier: NgxMaskService, public override thousandSeparatorPipe: ThousandSeparatorPipe ) { super(); if (this.ngControl) { this.ngControl.valueAccessor = this; } } ngAfterViewInit(): void { Promise.resolve().then(() => { this.handleChangeInputAndUpdateControl(this.inputValue); if (this._inputConfig.autoFocus) this.inputElement.nativeElement.focus(); if (this.parentControl) { this.parentControl.statusChanges .pipe(takeUntil(this.destroy$)) .subscribe(() => { if ( this.parentControl?.hasValidator( Validators.required ) ) { this.control?.addValidators(Validators.required); this.control?.updateValueAndValidity(); } else { this.control?.removeValidators(Validators.required); this.control?.updateValueAndValidity(); } setTimeout(() => { if (this.parentControl?.disabled) { this.control?.disable(); this.control?.updateValueAndValidity({ emitEvent: true, }); } else { this.control?.enable(); this.control?.updateValueAndValidity({ emitEvent: true, }); } }, 0); }); this.control?.setValidators(this.parentControl.validator); if (this.parentControl?.hasValidator(Validators.required)) { this.control?.addValidators(Validators.required); this.control?.updateValueAndValidity(); } if (this.parentControl?.disabled) { this.control?.disable(); this.control?.updateValueAndValidity({ emitEvent: true, }); } else { this.control?.enable(); this.control?.updateValueAndValidity({ emitEvent: true, }); } } }); } get control() { return this.ngControl?.control; } public registerOnChange(fn: any): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } public writeValue(obj: InputChangeValue): void { const value = this.transformValue(this._inputConfig, obj); // ✅ Apply mask this.inputValue = value; if (this._inputConfig.priceParseFormat && value) { this.onChange(value.replace(/[^\d]/g, '')); // Notify Angular form about the change } else { this.onChange(value); // Notify Angular form about the change } if (value && this._inputConfig.updateAsTouchedOnEdit) this.onTouched(); // Mark as touched if (!this._inputConfig.isDropdown && !this._inputConfig.isAddress) this.inputElement.nativeElement.value = value; this.chdet.detectChanges(); } handleInput(event: Event): void { const value = (event.target as HTMLInputElement).value; if (this._inputConfig.priceParseFormat && value) { this.onChange(value.replace(/[^\d]/g, '')); // Notify Angular form about the change } else { this.onChange(value); // Notify Angular form about the change } if (value) this.onTouched(); // Mark as touched if (this.inputValue === value) { this.control?.markAsUntouched(); } } public clearInput(event: Event): void { event.preventDefault(); event.stopPropagation(); this.handleChangeInputAndUpdateControl(''); this.clearInputEvent.emit(true); } public override handleChangeInputAndUpdateControl(value: InputChangeValue) { this.onChange(value); if (!this._inputConfig.isDropdown && !this._inputConfig.isAddress) { this.inputElement.nativeElement.value = value; // Trigger all inputs directives that listen for input Event const event = new Event('input', { bubbles: true }); this.inputElement.nativeElement.dispatchEvent(event); } } public handleInputFocus(event: FocusEvent) { this.focusInputEvent.emit(event); this.isFocusInput = true; } public handleInputBlur(event: FocusEvent) { this.blurInput.emit(true); this.isFocusInput = false; } public onPopoverShown(): void { if (!this._inputConfig.dropdownLabel) { this.isFocusInput = true; } } public onPopoverHidden(): void { this.blurInput.emit(true); if (!this._inputConfig.dropdownLabel) { this.isFocusInput = false; } } public onTogglePassword(event: Event): void { event.preventDefault(); this.isTogglePassword = !this.isTogglePassword; } public handleCapsLock(status: boolean) { this.isCapsLockOn = status; } public handleCommandVisible(status: boolean) { this.isVisibleCommands = status; } public identify(index: number): number { return index; } public handleKeyDown(event: KeyboardEvent) { this.dropDownKeyNavigationEvent.emit({ keyCode: event.keyCode, data: this.inputConfig, }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }