import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, Output, Self, ViewChild, } from '@angular/core'; import { ControlValueAccessor, FormControl, FormsModule, NgControl, ReactiveFormsModule, } from '@angular/forms'; import { CommonModule } from '@angular/common'; // Imports import moment from 'moment'; import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { Subject, take, takeUntil } from 'rxjs'; // Models import { ICaInput } from '../ca-input/config'; import { InputChangeValue, InputSvgRoutes } from '../ca-input/utils'; // Pipe import { InputContainerClassPipe, InputDatetimePickerClassPipe, } from '../ca-input/pipes'; // Components import { CaCustomDatetimePickersComponent } from '../ca-custom-datetime-pickers/ca-custom-datetime-pickers.component'; import { CaInputPlaceholderIconComponent } from '../ca-input/components/ca-input-placeholder-icon/ca-input-placeholder-icon.component'; // Enums import { InputConfigNameStringEnum } from '../ca-input/enums'; // Services import { CalendarDateTimePickerService } from '../ca-custom-datetime-pickers/services/calendar-datetime-picker.service'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { FormControlPipe } from '../ca-input/pipes/form-control.pipe'; import { InputTestComponent } from '../ca-input-test/input-test.component'; @Component({ selector: 'ca-input-datetime-picker', templateUrl: './ca-input-datetime-picker.component.html', styleUrls: [ './ca-input-datetime-picker.component.scss', '../ca-input/scss//ca-input.datepicker.scss', ], imports: [ // modules CommonModule, FormsModule, ReactiveFormsModule, AngularSvgIconModule, // components CaCustomDatetimePickersComponent, InputTestComponent, // pipes InputDatetimePickerClassPipe, InputContainerClassPipe, FormControlPipe, ], providers: [CalendarDateTimePickerService], }) export class CaInputDatetimePickerComponent implements AfterViewInit, ControlValueAccessor, OnDestroy { @Input() inputConfig!: ICaInput; @Input() formFormat?: string; @Output('clear') clearInputEvent: EventEmitter = new EventEmitter(); @Output('blurInput') blurInputEvent: EventEmitter = new EventEmitter(); @ViewChild(CaInputPlaceholderIconComponent) caInputPlaceholderIconComponent!: CaInputPlaceholderIconComponent; @ViewChild('dateTimePopover') dateTimePopover!: NgbPopover; @ViewChild(InputTestComponent, { static: true }) caInputComponent!: InputTestComponent; public inputFormControl: FormControl = new FormControl(null); public showDateInput: boolean = false; public dateTimeInputDate: Date = new Date(); public dateTimeIntDate: Date = new Date(); public selectionInput: number = -1; private dateTimeMainTimer!: ReturnType | number; private focusBlur!: ReturnType | number; private timeoutCleaner!: ReturnType | number; public inputSvgRoutes = InputSvgRoutes; public isTouchedInput: boolean = false; public preventBlur: boolean = false; // Date public newInputChanged: boolean = false; @Output() selectLastOneForSelectionEmitter = new EventEmitter(); @Output() selectLastOneAfterMouseUpEmitter = new EventEmitter(); @Output() onDatePasteEmitter = new EventEmitter(); @Output() onFocusEmitter = new EventEmitter(); @Output() changeSelectionEmmiter = new EventEmitter<{ e: KeyboardEvent; noPreventDefault: boolean; }>(); @Output() setSelectionEmmiter = new EventEmitter<{ preventDefault: () => void; stopPropagation: () => void; target: any; }>(); @ViewChild('span1', { static: true }) span1!: ElementRef; @ViewChild('span2', { static: true }) span2!: ElementRef; @ViewChild('span3', { static: true }) span3!: ElementRef; @ViewChild('holder1', { static: true }) holder1!: ElementRef; private destroy$: Subject = new Subject(); constructor( @Self() public superControl: NgControl, private calendarService: CalendarDateTimePickerService ) { this.superControl.valueAccessor = this; } valueAfterWriteValue!: string; ngOnInit(): void { if (this.superControl?.control) { this.superControl.control.valueChanges .pipe(take(1)) // 👈 only listen once .subscribe((_) => { if (this.valueAfterWriteValue) { setTimeout(() => { this.onChange(this.valueAfterWriteValue); }); } }); } } ngAfterViewInit(): void { if (this.valueAfterWriteValue) { this.setTimeDateInput(this.valueAfterWriteValue); } else { this.setTimePickerTime(); } this.calendarService.dateChanged .pipe(takeUntil(this.destroy$)) .subscribe((date) => { if (!this.inputConfig.isDisabled) { this.setTimeDateInput(date); this.caInputComponent.caInputPlaceholderIconComponent.t2?.close(); } }); } get getSuperControl() { return this.superControl.control; } public selectLastOneForSelection(): void { let range, selection; this.showDateInput = true; this.selectionInput = -1; this.caInputComponent.isFocusInput = true; if (window.getSelection && document.createRange) { selection = window.getSelection(); range = document.createRange(); if ( this.inputConfig.name !== InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { range.setStart(this.span3.nativeElement, 1); range.setEnd(this.span3.nativeElement, 1); } else { range.setStart(this.span2.nativeElement, 1); range.setEnd(this.span2.nativeElement, 1); } selection?.removeAllRanges(); selection?.addRange(range); } this.timeoutCleaner = setTimeout(() => { clearTimeout(this.dateTimeMainTimer); clearTimeout(this.focusBlur); }, 90); } public selectLastOneAfterMouseUp(): void { if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { this.selectionInput = 1; this.span2.nativeElement.focus(); this.setSpanSelection(this.span2.nativeElement); } else { this.selectionInput = 2; this.span3.nativeElement.focus(); this.setSpanSelection(this.span3.nativeElement); } this.showDateInput = true; this.timeoutCleaner = setTimeout(() => { clearTimeout(this.dateTimeMainTimer); clearTimeout(this.focusBlur); }, 90); } // 05/05/2025 public onDatePaste(event: ClipboardEvent): void { event.preventDefault(); const pasteText = event.clipboardData?.getData('text') as string; const pastedDate = new Date(pasteText); if (!isNaN(pastedDate.getTime())) { this.setTimeDateInput(pastedDate); this.selectSpanByTabIndex(this.selectionInput); } } changeSelection(e: any, noPreventDefault = false): void { const isCtrlV = (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'v'; if (isCtrlV) { // Allow paste return; } if ( e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40 || e.keyCode == 8 || e.keyCode == 9 || e.keyCode == 46 || (this.selectionInput == 3 && this.inputConfig.name === 'timepicker') ) { !e.noPrevent && e.preventDefault(); if (e.keyCode == 37) { if (this.selectionInput != 0) { this.selectionInput = this.selectionInput - 1; this.selectSpanByTabIndex(this.selectionInput, true); } } else if (e.keyCode == 39 || e.keyCode == 9) { if ( ((this.inputConfig.name === 'datepickerBankCard' && this.selectionInput != 1) || (this.inputConfig.name !== 'datepickerBankCard' && this.selectionInput != 2)) && !e.shiftKey ) { this.selectionInput = this.selectionInput + 1; this.selectSpanByTabIndex(this.selectionInput, true); } else if (e.keyCode == 9 && !e.shiftKey) { let allInputs = document.querySelectorAll( 'input.input-control' ) as NodeListOf; [...(allInputs as any)].map((item, indx) => { if ( item === this.caInputComponent.inputElement.nativeElement ) { if (allInputs[indx + 1]) { allInputs[indx + 1].focus(); } else { this.blurOnDateTime(); } this.selectionInput = -1; return; } }); } else if ( e.shiftKey && e.keyCode == 9 && this.selectionInput != 0 ) { if (this.selectionInput == -1) { this.selectionInput = 3; } this.selectionInput = this.selectionInput - 1; this.selectSpanByTabIndex(this.selectionInput); } else if ( e.shiftKey && e.keyCode == 9 && this.selectionInput == 0 ) { let allInputs = document.querySelectorAll('input'); [...(allInputs as any)].map((item, indx) => { if ( item === this.caInputComponent.inputElement.nativeElement ) { if (allInputs[indx - 1]) { allInputs[indx - 1].focus(); } this.selectionInput = -1; return; } }); } } else if (e.keyCode == 38) { this.setDateTimeModel('up'); } else if (e.keyCode == 40) { this.setDateTimeModel('down'); } else if (e.keyCode == 8 || e.keyCode == 46) { this.handleKeyboardInputs(e, true); } } else if (!this.isNumber(e)) { if (!noPreventDefault) { e.preventDefault(); } } else { if (!noPreventDefault) { e.preventDefault(); } if (this.selectionInput == -1) { this.selectionInput = 0; } this.handleKeyboardInputs(e); } } public changeSelectionTwo( event: KeyboardEvent, noPreventDefault = false ): void { if (!noPreventDefault) { event.preventDefault(); event.stopPropagation(); } const isNavigationKey = this.isNavigationKey(event); const isSpecialTimePickerKey = this.selectionInput === 3 && this.inputConfig.name === InputConfigNameStringEnum.TIME_PICKER; if (isNavigationKey || isSpecialTimePickerKey) { event.preventDefault(); this.handleNavigationKey(event); } else if (!this.isNumber(event)) { if (!noPreventDefault) event.preventDefault(); else { if (!noPreventDefault) event.preventDefault(); if (this.selectionInput === -1) this.selectionInput = 0; this.handleKeyboardInputs(event); } } } private isNumber(evt: KeyboardEvent): boolean { evt = evt ? evt : (window.event as KeyboardEvent); let charCode = evt.which ? evt.which : evt.keyCode; return ( (charCode >= 48 && charCode <= 57) || (charCode >= 96 && charCode <= 105) ); } private isNavigationKey(e: KeyboardEvent): boolean { const navigationKeys = [37, 38, 39, 40, 8, 9, 46]; // Arrow keys, Backspace, Tab, Delete return navigationKeys.includes(e.keyCode); } public onFocus(event?: FocusEvent): void { this.onFocusEmitter.emit(event); } public closePopover(): void { if (this.caInputComponent.caInputPlaceholderIconComponent.t2) this.caInputComponent.caInputPlaceholderIconComponent.t2.close(); } public toggleDropdownOptions() { if (this.caInputComponent?.caInputPlaceholderIconComponent?.t2) { if ( !this.caInputComponent.caInputPlaceholderIconComponent.t2.isOpen() ) { clearTimeout(this.dateTimeMainTimer); clearTimeout(this.focusBlur); this.holder1.nativeElement.focus(); this.selectionInput = -1; this.setSpanSelection(this.holder1.nativeElement); this.caInputComponent.caInputPlaceholderIconComponent.t2.open(); } else { this.holder1.nativeElement.blur(); // this.setIsFocusInput(false); let selection = window.getSelection(); selection?.removeAllRanges(); } return; } } public handleFocusInput(e: Event) { const event = e as FocusEvent; if (!this.inputConfig.isDisabled) { clearTimeout(this.dateTimeMainTimer); clearTimeout(this.focusBlur); this.showDateInput = true; if ( (this.selectionInput === -1 && (event?.target as HTMLElement)?.nodeName === 'INPUT') || (event?.relatedTarget as HTMLElement)?.nodeName === 'INPUT' || event?.relatedTarget === null ) { this.preventBlur = true; this.holder1.nativeElement.focus(); this.setSpanSelection(this.holder1.nativeElement); this.selectionInput = -1; } this.caInputComponent.caInputPlaceholderIconComponent.t2.toggle(); } } public setSpanSelection(element: Node): void { let range: Range; let selection: Selection | null; if (window.getSelection && document.createRange) { selection = window.getSelection(); range = document.createRange(); range.selectNodeContents(element); selection?.removeAllRanges(); selection?.addRange(range); } } public setSelection(event: { preventDefault: () => void; stopPropagation: () => void; target: any; }): void { const element = event.target; const selectionInput = parseInt(element.getAttribute('tabindex')); const currentSelection = window.getSelection(); if (currentSelection && currentSelection.toString().length > 10) { this.holder1.nativeElement.focus(); this.selectionInput = 0; this.setSpanSelection(this.holder1.nativeElement); clearTimeout(this.dateTimeMainTimer); clearTimeout(this.focusBlur); return; } clearTimeout(this.dateTimeMainTimer); if (element.classList.contains('main')) { this.selectionInput = selectionInput; this.setSpanSelection(element); } else { if (this.selectionInput === -1) { this.span1.nativeElement.focus(); this.selectionInput = 0; this.setSpanSelection(this.span1.nativeElement); } else { event.preventDefault(); this.selectSpanByTabIndex(this.selectionInput); } } } public registerOnChange(fn: any): void { this.onChange = fn; } public onChange(_: InputChangeValue): void {} public writeValue(obj: InputChangeValue): void { if (obj) this.setTimeDateInput(obj, true); } public registerOnTouched(): void {} // BLUR ON INPUT public blurOnDateTime(): void { clearTimeout(this.dateTimeMainTimer); this.dateTimeMainTimer = setTimeout(() => { if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER ) { if ( !isNaN(this.span1?.nativeElement.innerHTML) && !isNaN(this.span2?.nativeElement.innerHTML) && !isNaN(this.span3?.nativeElement.innerHTML) ) { if ( this.inputConfig.isFutureDateDisabled && moment(this.dateTimeInputDate).isAfter(moment()) ) this.superControl.control?.setErrors({ invalid: true, }); // don't accept future dates else { if ( this.inputConfig.expiredDateInvalid && moment(this.dateTimeInputDate).isBefore(moment()) ) { this.superControl.control?.setErrors({ invalid: true, }); // don't accept expired dates } else { this.calendarService.dateChanged.next( this.dateTimeInputDate ); } } } else { this.span1.nativeElement.innerHTML = 'mm'; this.span2.nativeElement.innerHTML = 'dd'; this.span3.nativeElement.innerHTML = 'yy'; this.dateTimeInputDate = new Date(); this.showDateInput = false; } } else if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { if ( !isNaN(this.span1.nativeElement.innerHTML) && !isNaN(this.span2.nativeElement.innerHTML) ) { if ( this.inputConfig.expiredDateInvalid && moment(this.dateTimeInputDate).isBefore(moment()) ) { this.superControl.control?.setErrors({ invalid: true }); // don't accept expired dates } else { this.calendarService.dateChanged.next( this.dateTimeInputDate ); } } else { this.span1.nativeElement.innerHTML = 'mm'; this.span2.nativeElement.innerHTML = 'yy'; this.dateTimeInputDate = new Date(); this.showDateInput = false; } } else { if ( !isNaN(this.span1.nativeElement.innerHTML) && !isNaN(this.span2.nativeElement.innerHTML) ) { this.calendarService.dateChanged.next( this.dateTimeInputDate ); } else { this.span1.nativeElement.innerHTML = 'HH'; this.span2.nativeElement.innerHTML = 'MM'; this.setTimePickerTime(); this.showDateInput = false; } } clearTimeout(this.dateTimeMainTimer); this.selectionInput = -1; this.newInputChanged = true; }, 100); } public onPopoverShown(): void { this.showDateInput = true; this.holder1.nativeElement.focus(); } public onPopoverHidden(): void { this.blurOnDateTime(); } public setTimeDateInput(date: InputChangeValue, hasValue?: boolean): void { let text, dateFormat, timeFormat; if (this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER) { text = moment(new Date(date)).format('MM/DD/YY'); dateFormat = text.split('/'); } else if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { text = moment(new Date(date)).format('MM/DD/YY'); dateFormat = text.split('/'); } else { date = date instanceof Date ? date : new Date(moment().format('MM/DD/YYYY') + ' ' + date); text = moment(new Date(date)).format('HH:mm'); timeFormat = moment(new Date(date)).format('hh/mm/A'); dateFormat = timeFormat.split('/'); } if (this.formFormat) { const newFormFormat = moment(new Date(date)).format( this.formFormat ); this.valueAfterWriteValue = newFormFormat; text = newFormFormat; } if (!hasValue) this.onChange(text); this.setIsFocusInput(false); // HAS TO BE IN TIMEOUT BECAUSE OF ANGULAR STACK setTimeout(() => { if (this.span1) this.span1.nativeElement.innerHTML = dateFormat[0]; if (this.span2) if ( this.inputConfig.name !== InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { this.span2.nativeElement.innerHTML = dateFormat[1]; this.span3.nativeElement.innerHTML = dateFormat[2]; } else { this.span2.nativeElement.innerHTML = dateFormat[2]; } }, 0); if (date) { this.dateTimeInputDate = new Date(date); } this.showDateInput = true; } public handleKeyboardInputs(e: KeyboardEvent, isRestart?: boolean): void { const spanOneValue: number | undefined = isNaN(this.span1.nativeElement.innerHTML) || this.newInputChanged ? undefined : parseInt(this.span1.nativeElement.innerHTML); const span2Value: number | undefined = isNaN(this.span2.nativeElement.innerHTML) || this.newInputChanged ? undefined : parseInt(this.span2.nativeElement.innerHTML); let span3Value: string | number | undefined; if ( this.inputConfig.name !== InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { span3Value = isNaN(this.span3.nativeElement.innerHTML) || this.newInputChanged ? '' : parseInt(this.span3.nativeElement.innerHTML); } this.newInputChanged = false; if (this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER) { if (this.selectionInput === 0) { if (isRestart) { this.span1.nativeElement.innerHTML = 'mm'; this.selectionInput = 0; this.selectSpanByTabIndex(0); } else if (spanOneValue !== undefined) { let final_value = parseInt(`${spanOneValue}${e.key}`); if (final_value > 12) { this.span1.nativeElement.innerHTML = ( '0' + parseInt(e.key) ).slice(-2); this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } else { this.dateTimeInputDate = new Date( this.dateTimeInputDate.setMonth( parseInt( this.span1.nativeElement.innerHTML + parseInt(e.key) ) - 1 ) ); if (!final_value) this.span1.nativeElement.innerHTML = 'mm'; else this.span1.nativeElement.innerHTML = ( this.span1.nativeElement.innerHTML + parseInt(e.key) ).slice(-2); this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } } else { if (parseInt(e.key)) this.dateTimeInputDate = new Date( this.dateTimeInputDate.setMonth(parseInt(e.key) - 1) ); const final_value = ('0' + parseInt(e.key)).slice(-2); this.span1.nativeElement.innerHTML = final_value; if (parseInt(`1${e.key}`) > 12) { this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } else this.selectSpanByTabIndex(0); } } else if (this.selectionInput === 1) { if (isRestart) { this.span2.nativeElement.innerHTML = 'dd'; this.selectionInput = 0; this.selectSpanByTabIndex(0, true); } else if (span2Value !== undefined) { let final_value = parseInt(`${span2Value}${e.key}`); if (final_value > 31) { this.span2.nativeElement.innerHTML = ( '0' + parseInt(e.key) ).slice(-2); this.selectionInput = 2; this.selectSpanByTabIndex(2, true); } else { this.dateTimeInputDate = new Date( this.dateTimeInputDate.setDate( parseInt( this.span2.nativeElement.innerHTML + parseInt(e.key) ) ) ); if (!final_value) { this.span2.nativeElement.innerHTML = 'dd'; } else { this.span2.nativeElement.innerHTML = ( this.span2.nativeElement.innerHTML + parseInt(e.key) ).slice(-2); } this.selectionInput = 2; this.selectSpanByTabIndex(2, true); } } else { if (parseInt(e.key)) this.dateTimeInputDate = new Date( this.dateTimeInputDate.setDate(parseInt(e.key)) ); this.span2.nativeElement.innerHTML = ( '0' + parseInt(e.key) ).slice(-2); if (parseInt(`1${e.key}`) > 31) { this.selectionInput = 2; this.selectSpanByTabIndex(2, true); } else { this.selectSpanByTabIndex(1); } } } else { if (isRestart) { this.span3.nativeElement.innerHTML = 'yy'; this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } else if (!span3Value || span3Value.toString().length === 2) { this.span3.nativeElement.innerHTML = ( '0' + parseInt(e.key) ).slice(-2); this.dateTimeInputDate = new Date( this.dateTimeInputDate.setFullYear( parseInt(`200${parseInt(e.key)}`) ) ); this.selectSpanByTabIndex(2); } else { const finalYear = parseInt( this.span3.nativeElement.innerHTML + parseInt(e.key) ); const finalShowYear = finalYear > 31 ? parseInt(`19${finalYear}`) : parseInt(`20${finalYear}`); this.dateTimeInputDate = new Date( this.dateTimeInputDate.setFullYear(finalShowYear) ); this.span3.nativeElement.innerHTML = ( this.span3.nativeElement.innerHTML + parseInt(e.key) ).slice(-2); this.selectSpanByTabIndex(2); } } } else if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { if (!this.selectionInput) { if (isRestart) { this.span1.nativeElement.innerHTML = 'mm'; this.selectionInput = 0; this.selectSpanByTabIndex(0); } else if (spanOneValue) { const final_value = parseInt(`${spanOneValue}${e.key}`); if (final_value > 12) { this.span1.nativeElement.innerHTML = ( '0' + parseInt(e.key) ).slice(-2); this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } else { this.dateTimeInputDate = new Date( this.dateTimeInputDate.setMonth( parseInt( this.span1.nativeElement.innerHTML + parseInt(e.key) ) - 1 ) ); if (!final_value) { this.span1.nativeElement.innerHTML = 'mm'; } else { this.span1.nativeElement.innerHTML = ( this.span1.nativeElement.innerHTML + parseInt(e.key) ).slice(-2); } this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } } else { if (parseInt(e.key)) this.dateTimeInputDate = new Date( this.dateTimeInputDate.setMonth(parseInt(e.key) - 1) ); const final_value = ('0' + parseInt(e.key)).slice(-2); this.span1.nativeElement.innerHTML = final_value; if (parseInt(`1${e.key}`) > 12) { this.selectionInput = 1; this.selectSpanByTabIndex(1, true); } else this.selectSpanByTabIndex(0); } } else { if (isRestart) { this.span2.nativeElement.innerHTML = 'yy'; this.selectionInput = 2; this.selectSpanByTabIndex(2, true); } else if (!span2Value || span2Value.toString().length === 2) { this.span2.nativeElement.innerHTML = ( '0' + parseInt(e.key) ).slice(-2); this.dateTimeInputDate = new Date( this.dateTimeInputDate.setFullYear( parseInt(`200${parseInt(e.key)}`) ) ); this.selectSpanByTabIndex(1); } else { const finalYear = parseInt( this.span2.nativeElement.innerHTML + parseInt(e.key) ); const finalShowYear = finalYear > 31 ? parseInt(`19${finalYear}`) : parseInt(`20${finalYear}`); this.dateTimeInputDate = new Date( this.dateTimeInputDate.setFullYear(finalShowYear) ); this.span2.nativeElement.innerHTML = ( this.span2.nativeElement.innerHTML + parseInt(e.key) ).slice(-2); this.selectSpanByTabIndex(1); } } } } selectSpanByTabIndex(indx: number, changeTab?: boolean): void { switch (indx) { case 0: this.setSpanSelection(this.span1.nativeElement); break; case 1: this.setSpanSelection(this.span2.nativeElement); break; case 2: this.setSpanSelection(this.span3.nativeElement); break; default: this.setSpanSelection(this.holder1.nativeElement); } if (changeTab) this.newInputChanged = true; } private handleNavigationKey(event: KeyboardEvent): void { switch (event.keyCode) { case 37: // Left arrow this.moveSelectionLeft(); break; case 39: // Right arrow case 9: // Tab this.moveSelectionRightOrTab(event); break; case 38: // Up arrow this.setDateTimeModel('up'); break; case 40: // Down arrow this.setDateTimeModel('down'); break; case 8: // Backspace case 46: // Delete this.handleKeyboardInputs(event, true); break; } } private handleDatePickerInput(isUp: boolean, isDatePicker: boolean): void { if (this.selectionInput === 0) this.updateMonth(isUp); else if (this.selectionInput === 1) this.updateDate(isUp, isDatePicker); else this.updateYear(isUp, isDatePicker); } private setDateTimeModel(direction: string): void { if (this.selectionInput === -1) this.selectionInput = 0; const isUp = direction === 'up'; const isDatePicker = this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER; const isBankCardPicker = this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD; if (isDatePicker || isBankCardPicker) this.handleDatePickerInput(isUp, isDatePicker); else this.handleTimePickerInput(isUp); } private moveSelectionRightOrTab(e: KeyboardEvent): void { const isDatepickerBankCard = this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD; const maxIndex = isDatepickerBankCard ? 1 : 2; if (this.selectionInput < maxIndex && !e.shiftKey) { this.selectionInput += 1; this.selectSpanByTabIndex(this.selectionInput, true); } else if (e.keyCode === 9 && !e.shiftKey) { this.focusNextInput(); } else if (e.shiftKey && e.keyCode === 9) { this.focusPreviousInput(); } } private focusPreviousInput(): void { const allInputs = Array.from( document.querySelectorAll('input.input-control') ) as HTMLInputElement[]; const currentIndex = allInputs.findIndex( (input) => input === this.caInputComponent.inputElement.nativeElement ); if (currentIndex === -1) this.selectionInput = 3; else if (currentIndex === 0) this.selectionInput = 0; else { this.selectionInput -= 1; this.selectSpanByTabIndex(this.selectionInput); } if (currentIndex > 0) allInputs[currentIndex - 1].focus(); this.selectionInput = -1; } private focusNextInput(): void { const allInputs = Array.from( document.querySelectorAll('input.input-control') ) as HTMLInputElement[]; const currentIndex = allInputs.findIndex( (input) => input === this.caInputComponent.inputElement.nativeElement ); if (allInputs[currentIndex + 1]) allInputs[currentIndex + 1].focus(); else { this.setIsFocusInput(false); this.blurOnDateTime(); } this.selectionInput = -1; } public setIsFocusInput(value: boolean): void { this.caInputComponent.isFocusInput = value; } private moveSelectionLeft(): void { if (this.selectionInput > 0) { this.selectionInput -= 1; this.selectSpanByTabIndex(this.selectionInput, true); } } private updateMonth(isUp: boolean): void { const monthDelta = isUp ? 1 : -1; this.dateTimeInputDate = new Date( this.dateTimeInputDate.setMonth( this.dateTimeInputDate.getMonth() + monthDelta ) ); this.span1.nativeElement.innerHTML = this.padValue( this.dateTimeInputDate.getMonth() + 1 ); this.setSpanSelection(this.span1.nativeElement); } private updateDate(isUp: boolean, isDatePicker: boolean): void { const dateDelta = isUp ? 1 : -1; this.dateTimeInputDate = new Date( this.dateTimeInputDate.setDate( this.dateTimeInputDate.getDate() + dateDelta ) ); const spanElement = isDatePicker ? this.span2.nativeElement : this.span1.nativeElement; spanElement.innerHTML = this.padValue(this.dateTimeInputDate.getDate()); this.setSpanSelection(spanElement); } private updateYear(isUp: boolean, isDatePicker: boolean): void { const yearDelta = isUp ? 1 : -1; this.dateTimeInputDate = new Date( this.dateTimeInputDate.setFullYear( this.dateTimeInputDate.getFullYear() + yearDelta ) ); const spanElement = isDatePicker ? this.span3.nativeElement : this.span2.nativeElement; spanElement.innerHTML = this.dateTimeInputDate .getFullYear() .toString() .slice(-2); this.setSpanSelection(spanElement); } private padValue(value: number): string { return ('0' + value).slice(-2); } private handleTimePickerInput(isUp: boolean): void { if (this.selectionInput === 0) this.updateHours(isUp); else if (this.selectionInput === 1) this.updateMinutes(isUp); else this.toggleAMPM(); } private updateHours(isUp: boolean): void { const hoursDelta = isUp ? 1 : -1; let selectedHours = this.dateTimeInputDate.getHours() + hoursDelta; if (selectedHours === 0) selectedHours = 24; if (selectedHours === -1) selectedHours = 23; this.dateTimeInputDate = new Date( this.dateTimeInputDate.setHours(selectedHours) ); this.span1.nativeElement.innerHTML = this.padValue( selectedHours > 12 ? selectedHours - 12 : selectedHours ); this.setSpanSelection(this.span1.nativeElement); this.span3.nativeElement.innerHTML = selectedHours > 11 && selectedHours < 24 ? 'PM' : 'AM'; } private updateMinutes(isUp: boolean): void { const minutesDelta = isUp ? 1 : -1; this.dateTimeInputDate = new Date( this.dateTimeInputDate.setMinutes( this.dateTimeInputDate.getMinutes() + minutesDelta ) ); this.span2.nativeElement.innerHTML = this.padValue( this.dateTimeInputDate.getMinutes() ); this.setSpanSelection(this.span2.nativeElement); } private toggleAMPM(): void { this.span3.nativeElement.innerHTML = this.span3.nativeElement.innerHTML === 'AM' ? 'PM' : 'AM'; const hoursToAdd = this.span3.nativeElement.innerHTML === 'AM' ? -12 : 12; this.dateTimeInputDate = new Date( this.dateTimeInputDate.setHours( this.dateTimeInputDate.getHours() + hoursToAdd ) ); this.setSpanSelection(this.span3.nativeElement); } public clearInput(event: boolean): void { this.clearInputEvent.emit(event); this.setIsFocusInput(false); this.resetDateTimeInputs(); this.onChange(''); } public onBlurInput(event: any): void { this.blurInputEvent.emit(event); // Datepicker this.focusBlur = setTimeout(() => { this.blurOnDateTime(); }, 100); } public resetDateTimeInputs(): void { if (this.span1) if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER ) { this.span1.nativeElement.innerHTML = 'mm'; this.span2.nativeElement.innerHTML = 'dd'; this.span3.nativeElement.innerHTML = 'yy'; } else if ( this.inputConfig.name === InputConfigNameStringEnum.DATE_PICKER_BANK_CARD ) { this.span1.nativeElement.innerHTML = 'mm'; this.span2.nativeElement.innerHTML = 'yy'; } else if ( this.inputConfig.name === InputConfigNameStringEnum.TIME_PICKER ) { this.span1.nativeElement.innerHTML = 'HH'; this.span2.nativeElement.innerHTML = 'MM'; this.span3.nativeElement.innerHTML = 'AM'; } this.setTimePickerTime(); this.newInputChanged = true; this.setIsFocusInput(false); this.showDateInput = false; } public setTimePickerTime(): void { if (this.inputConfig.name === InputConfigNameStringEnum.TIME_PICKER) this.dateTimeInputDate = new Date( moment().format('MM/DD/YYYY') + (this.inputConfig?.isFromDate ? ' 12:15' : ' 12:00') ); } public ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }