diff --git a/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts b/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts index 7539c76b77d..83dfbd5836a 100644 --- a/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts +++ b/projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts @@ -61,14 +61,6 @@ const enum DateParts { Year = 'year' } -/** @hidden */ -const enum TimeParts { - Hour = 'hour', - Minute = 'minute', - Second = 'second', - AmPm = 'ampm' -} - /** * @hidden1 */ @@ -90,7 +82,7 @@ export abstract class DatePickerUtil { }); if (parts[DatePart.Month] < 1 || 12 < parts[DatePart.Month]) { - return { state: DateState.Invalid, value: null }; + return { state: DateState.Invalid, value: new Date(NaN) }; } // TODO: Century threshold @@ -99,11 +91,11 @@ export abstract class DatePickerUtil { } if (parts[DatePart.Date] > DatePickerUtil.daysInMonth(parts[DatePart.Year], parts[DatePart.Month])) { - return { state: DateState.Invalid, value: null }; + return { state: DateState.Invalid, value: new Date(NaN) }; } if (parts[DatePart.Hours] > 23 || parts[DatePart.Minutes] > 59 || parts[DatePart.Seconds] > 59) { - return { state: DateState.Invalid, value: null }; + return { state: DateState.Invalid, value: new Date(NaN) }; } return { @@ -180,7 +172,7 @@ export abstract class DatePickerUtil { newDate = new Date(newDate.setDate(newDate.getDate() + delta)); if (isSpinLoop) { if (currentDate.getMonth() > newDate.getMonth()) { - return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); + return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); // add delta instead of 1? } else if (currentDate.getMonth() < newDate.getMonth()) { return new Date(currentDate.setDate(1)); } @@ -261,6 +253,17 @@ export abstract class DatePickerUtil { return currentDate; } + public static calculateAmPmOnSpin(delta: number, newDate: Date, currentDate: Date) { + newDate = delta > 0 + ? new Date(newDate.setHours(newDate.getHours() + 12)) + : new Date(newDate.setHours(newDate.getHours() - 12)); + if (newDate.getDate() !== currentDate.getDate()) { + return currentDate; + } + + return newDate; + } + private static getCleanVal(inputData: string, datePart: DatePartInfo): string { return DatePickerUtil.trimUnderlines(inputData.substring(datePart.start, datePart.end)); } diff --git a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts index ecdc002ca7c..4231dfd9be3 100644 --- a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts @@ -276,7 +276,7 @@ fdescribe('IgxDateTimeEditor', () => { it('Should revert to empty mask on clear()', () => { // TODO // should clear inner value and emit valueChanged - }) + }); it('Should not block the user from typing/pasting/dragging dates outside of min/max range', () => { // TODO diff --git a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts index 76e2670c0a0..ad5b12fd61f 100644 --- a/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts @@ -100,27 +100,22 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn public clear(): void { this.showMask(''); - this.value = null; - this.valueChanged.emit({ oldValue: this._oldValue, newValue: this.value }); + this.updateValue(null); } public increment(datePart?: DatePart): void { const newValue = datePart ? this.calculateValueOnSpin(datePart, 1) : this.calculateValueOnSpin(this.targetDatePart, 1); if (newValue && this.value && newValue !== this.value) { this.updateValue(newValue); + this.updateMask(); } - - // TODO: update mask - // this.updateMask(); } public decrement(datePart?: DatePart): void { const newValue = datePart ? this.calculateValueOnSpin(datePart, -1) : this.calculateValueOnSpin(this.targetDatePart, -1); if (newValue && this.value && newValue !== this.value) { this.updateValue(newValue); - - // TODO: update mask - // this.updateMask(true); + this.updateMask(); } } @@ -148,9 +143,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn } if (event.ctrlKey && event.key === KEYS.SEMICOLON) { - // TODO: emit success & update mask? - this.value = new Date(); - this.valueChanged.emit({ oldValue: this._oldValue, newValue: this.value }); + this.updateValue(new Date()); this.updateMask(); } @@ -172,7 +165,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn } // TODO: display value pipe - // this.updateMask(); TODO: fill in any empty date parts + // this.updateMask(); this.onTouchCallback(); super.onBlur(event); } @@ -181,9 +174,18 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn protected handleInputChanged(): void { // the mask must be updated before any date operations super.handleInputChanged(); + if (this.inputValue === this.maskParser.applyMask('', this.maskOptions)) { + this.updateValue(null); + return; + } + const parsedDate = this.parseDate(this.inputValue); - if (parsedDate.state === DateState.Valid && this.inputValue.indexOf(this.promptChar) === -1) { - this.updateValue(parsedDate.value); + if (this.inputValue.indexOf(this.promptChar) === -1) { // better way to check for filled input? + if (parsedDate.state === DateState.Valid) { + this.updateValue(parsedDate.value); + } else { + this.validationFailed.emit({ oldValue: this.value, newValue: parsedDate.value }); + } } super.afterInput(); @@ -198,6 +200,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn } private valueInRange(value: Date): boolean { + if (!value) { return; } const maxValueAsDate = this.isDate(this.maxValue) ? this.maxValue : this.parseDate(this.maxValue).value; const minValueAsDate = this.isDate(this.minValue) ? this.minValue : this.parseDate(this.minValue).value; if (maxValueAsDate && minValueAsDate) { @@ -228,6 +231,8 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn return DatePickerUtil.calculateMinutesOnSpin(delta, newDate, currentDate, this.isSpinLoop); case DatePart.Seconds: return DatePickerUtil.calculateSecondsOnSpin(delta, newDate, currentDate, this.isSpinLoop); + case DatePart.AmPm: + return DatePickerUtil.calculateAmPmOnSpin(delta, newDate, currentDate); } } @@ -244,47 +249,54 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn private updateMask() { const cursor = this.selectionEnd; this._dateTimeFormatParts.forEach(p => { - // TODO: cycle through the parts and update the mask based on their indices - let value: number = this.breakUpDate(p.type); - // TODO: append all date parts from the date object to one another - // if a part of that date object's length is less than the expected length (taken from the format) -> prepend prompt chars - value = p.type === DatePart.Month ? value + 1 : value; - this.inputValue = this.maskParser.replaceInMask(this.inputValue, `${value}`, this.maskOptions, p.start, p.end).value; + const partLength = p.end - p.start; + let targetValue: string = this.getMaskedValue(p.type, partLength); + + if (p.type === DatePart.Month) { + targetValue = this.prependPromptChars( + parseInt(targetValue.replace(new RegExp(this.promptChar, 'g'), '0'), 10) + 1, partLength); + } + + this.inputValue = this.maskParser.replaceInMask(this.inputValue, targetValue, this.maskOptions, p.start, p.end).value; }); this.setSelectionRange(cursor); } - private breakUpDate(datePart: DatePart): number { - const valueAsDate = this.value as Date; + private getMaskedValue(datePart: DatePart, partLength: number): string { + let maskedValue; switch (datePart) { case DatePart.Date: - return valueAsDate.getDate(); + maskedValue = this.value.getDate(); + break; case DatePart.Month: - return valueAsDate.getMonth(); + maskedValue = this.value.getMonth(); + break; case DatePart.Year: - return valueAsDate.getFullYear(); + maskedValue = this.value.getFullYear(); + break; case DatePart.Hours: - return valueAsDate.getHours(); + maskedValue = this.value.getHours(); + break; case DatePart.Minutes: - return valueAsDate.getMinutes(); + maskedValue = this.value.getMinutes(); + break; case DatePart.Seconds: - return valueAsDate.getSeconds(); + maskedValue = this.value.getSeconds(); + break; + case DatePart.AmPm: + maskedValue = this.value.getHours() >= 12 ? 'PM' : 'AM'; + break; } - } - private updateMaskOnSpin(parsedValue: number, editedParts: DatePartInfo[], addPromptChar?: boolean) { - let start = editedParts[0].start; - const end = editedParts[editedParts.length - 1].start; - if (parsedValue.toString().length < editedParts.length) { - start += editedParts.length - parsedValue.toString().length; - if (addPromptChar) { - this.inputValue = this.maskParser.replaceCharAt(this.inputValue, start - 1, this.promptChar); - } + if (datePart !== DatePart.AmPm) { + return this.prependPromptChars(maskedValue, partLength); } - this.inputValue = this.maskParser.replaceInMask( - this.inputValue, `${parsedValue} `, this.maskOptions, start, end).value; - this.setSelectionRange(this.end); + return maskedValue; + } + + private prependPromptChars(value: number, partLength: number): string { + return (this.promptChar + value.toString()).slice(-partLength); } private spin(event: KeyboardEvent): void { diff --git a/projects/igniteui-angular/src/lib/directives/mask/mask-parsing.service.ts b/projects/igniteui-angular/src/lib/directives/mask/mask-parsing.service.ts index 22fb4d56b3e..8632fcb12cc 100644 --- a/projects/igniteui-angular/src/lib/directives/mask/mask-parsing.service.ts +++ b/projects/igniteui-angular/src/lib/directives/mask/mask-parsing.service.ts @@ -100,7 +100,9 @@ export class MaskParsingService { } continue; } - if (chars[0] && !this.validateCharOnPosition(chars[0], i, maskOptions.format)) { + if (chars[0] + && !this.validateCharOnPosition(chars[0], i, maskOptions.format) + && chars[0] !== maskOptions.promptChar) { break; }