From 9ba7050ee0c17744c4a1fd6496ce820c5d7b2ef0 Mon Sep 17 00:00:00 2001 From: lskramarov Date: Thu, 18 Jul 2019 10:15:12 +0300 Subject: [PATCH] fix(timepicker): need keep source date (#UIM-79) (#176) --- packages/mosaic-dev/timepicker/module.ts | 2 +- packages/mosaic-dev/timepicker/template.html | 12 +- .../adapter/moment-date-adapter.ts | 5 +- packages/mosaic/timepicker/timepicker.ts | 195 ++++++++++-------- 4 files changed, 120 insertions(+), 94 deletions(-) diff --git a/packages/mosaic-dev/timepicker/module.ts b/packages/mosaic-dev/timepicker/module.ts index 37ce91fa3..d2590bcdc 100644 --- a/packages/mosaic-dev/timepicker/module.ts +++ b/packages/mosaic-dev/timepicker/module.ts @@ -33,7 +33,7 @@ const moment = _rollupMoment || _moment; encapsulation: ViewEncapsulation.None }) export class TimepickerDemoComponent { - timeValue1: Moment = moment(); + timeValue: Moment = moment('2000-10-01 12:00:00'); isDisabled: boolean = false; toggleDisable() { diff --git a/packages/mosaic-dev/timepicker/template.html b/packages/mosaic-dev/timepicker/template.html index a08e558f2..ab5ed9e48 100644 --- a/packages/mosaic-dev/timepicker/template.html +++ b/packages/mosaic-dev/timepicker/template.html @@ -20,7 +20,7 @@

Example 1

Example 1
-

+

Current time value: - {{timeValue1.hours()}}: - {{timeValue1.minutes()}}: - {{timeValue1.seconds()}} + {{timeValue.hours()}}: + {{timeValue.minutes()}}: + {{timeValue.seconds()}}

-

Incorrect input - null result

+

Incorrect input - null result


diff --git a/packages/mosaic-moment-adapter/adapter/moment-date-adapter.ts b/packages/mosaic-moment-adapter/adapter/moment-date-adapter.ts index ad0f53ef6..30076f79e 100644 --- a/packages/mosaic-moment-adapter/adapter/moment-date-adapter.ts +++ b/packages/mosaic-moment-adapter/adapter/moment-date-adapter.ts @@ -227,8 +227,9 @@ export class MomentDateAdapter extends DateAdapter { return result; } - createDateTime(year: number, month: number, date: number, - hours: number, minutes: number, seconds: number, milliseconds: number): Moment { + createDateTime( + year: number, month: number, date: number, hours: number, minutes: number, seconds: number, milliseconds: number + ): Moment { const newDate = this.createDate(year, month, date); newDate.hours(hours); diff --git a/packages/mosaic/timepicker/timepicker.ts b/packages/mosaic/timepicker/timepicker.ts index cc07844f3..4c21d84a6 100644 --- a/packages/mosaic/timepicker/timepicker.ts +++ b/packages/mosaic/timepicker/timepicker.ts @@ -6,7 +6,6 @@ import { forwardRef, Inject, Input, - OnChanges, OnDestroy, Optional, Renderer2, @@ -64,8 +63,7 @@ export class McTimepickerBase { // tslint:disable-next-line naming-convention export const McTimepickerMixinBase: - CanUpdateErrorStateCtor & - typeof McTimepickerBase = mixinErrorState(McTimepickerBase); + CanUpdateErrorStateCtor & typeof McTimepickerBase = mixinErrorState(McTimepickerBase); @Directive({ selector: 'input[mcTimepicker]', @@ -83,10 +81,13 @@ export const McTimepickerMixinBase: '[attr.max-time]': 'maxTime', '[attr.value]': 'value', '[attr.aria-invalid]': 'errorState', + '(blur)': 'onBlur()', '(focus)': 'focusChanged(true)', + '(input)': 'onInput()', '(paste)': 'onPaste($event)', + '(keydown)': 'onKeyDown($event)' }, providers: [ @@ -97,15 +98,13 @@ export const McTimepickerMixinBase: ] }) export class McTimepicker extends McTimepickerMixinBase - implements McFormFieldControl, - OnChanges, - OnDestroy, - DoCheck, - CanUpdateErrorState, - ControlValueAccessor { + implements McFormFieldControl, OnDestroy, DoCheck, CanUpdateErrorState, ControlValueAccessor { - /** An object used to control when error messages are shown. */ - @Input() errorStateMatcher: ErrorStateMatcher; + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + readonly stateChanges: Subject = new Subject(); /** * Implemented as part of McFormFieldControl. @@ -117,13 +116,16 @@ export class McTimepicker extends McTimepickerMixinBase * Implemented as part of McFormFieldControl. * @docs-private */ - readonly stateChanges: Subject = new Subject(); + controlType: string = 'mc-timepicker'; + + /** An object used to control when error messages are shown. */ + @Input() errorStateMatcher: ErrorStateMatcher; /** * Implemented as part of McFormFieldControl. * @docs-private */ - controlType: string = 'mc-timepicker'; + @Input() placeholder: string; @Input() get disabled(): boolean { @@ -144,33 +146,42 @@ export class McTimepicker extends McTimepickerMixinBase this.stateChanges.next(); } - @Input() - get id(): string { return this._id; } + private _disabled: boolean; - set id(value: string) { this._id = value || this.uid; } + @Input() + get id(): string { + return this._id; + } + set id(value: string) { + this._id = value || this.uid; + } - /** - * Implemented as part of McFormFieldControl. - * @docs-private - */ - @Input() placeholder: string; + private _id: string; /** * Implemented as part of McFormFieldControl. * @docs-private */ @Input() - get required(): boolean { return this._required; } + get required(): boolean { + return this._required; + } + + set required(value: boolean) { + this._required = coerceBooleanProperty(value); + } - set required(value: boolean) { this._required = coerceBooleanProperty(value); } + private _required: boolean; /** * Implemented as part of McFormFieldControl. * @docs-private */ @Input() - get value(): string { return this.inputValueAccessor.value; } + get value(): string { + return this.inputValueAccessor.value; + } set value(value: string) { if (value !== this.value) { @@ -180,7 +191,9 @@ export class McTimepicker extends McTimepickerMixinBase } @Input('time-format') - get timeFormat(): TimeFormats { return this._timeFormat; } + get timeFormat(): TimeFormats { + return this._timeFormat; + } set timeFormat(formatValue: TimeFormats) { this._timeFormat = Object @@ -192,47 +205,52 @@ export class McTimepicker extends McTimepickerMixinBase this.placeholder = TIMEFORMAT_PLACEHOLDERS[this._timeFormat]; } + private _timeFormat: TimeFormats; + @Input('min-time') - get minTime(): string | null { return this._minTime; } + get minTime(): string | null { + return this._minTime; + } - set minTime(minValue: string | null) { - this._minTime = minValue; - this.minDateTime = minValue !== null ? this.getDateFromTimeString(minValue) : undefined; + set minTime(value: string | null) { + this._minTime = value; (this.ngControl.control as FormControl).updateValueAndValidity(); } + private _minTime: string | null = null; + @Input('max-time') - get maxTime(): string | null { return this._maxTime; } + get maxTime(): string | null { + return this._maxTime; + } set maxTime(maxValue: string | null) { this._maxTime = maxValue; - this.maxDateTime = maxValue !== null ? this.getDateFromTimeString(maxValue) : undefined; (this.ngControl.control as FormControl).updateValueAndValidity(); } - private _id: string; + private _maxTime: string | null = null; + private readonly uid = `mc-timepicker-${uniqueComponentIdSuffix++}`; - private _disabled: boolean; - private _required: boolean; - private previousNativeValue: any; private readonly inputValueAccessor: { value: any }; + + private originalValue: any; + private previousNativeValue: any; + private currentDateTimeInput: D | undefined; + private onChange: (value: any) => void; private onTouched: () => void; - private _timeFormat: TimeFormats; - private _minTime: string | null = null; - private minDateTime: D | undefined; - private _maxTime: string | null = null; - private maxDateTime: D | undefined; - private currentDateTimeInput: D | undefined; - constructor(private readonly elementRef: ElementRef, - @Optional() @Self() public ngControl: NgControl, - @Optional() parentForm: NgForm, - @Optional() parentFormGroup: FormGroupDirective, - defaultErrorStateMatcher: ErrorStateMatcher, - @Optional() @Self() @Inject(MC_INPUT_VALUE_ACCESSOR) inputValueAccessor: any, - private readonly renderer: Renderer2, - @Optional() private dateAdapter: DateAdapter) { + constructor( + private readonly elementRef: ElementRef, + @Optional() @Self() public ngControl: NgControl, + @Optional() parentForm: NgForm, + @Optional() parentFormGroup: FormGroupDirective, + defaultErrorStateMatcher: ErrorStateMatcher, + @Optional() @Self() @Inject(MC_INPUT_VALUE_ACCESSOR) inputValueAccessor: any, + private readonly renderer: Renderer2, + @Optional() private dateAdapter: DateAdapter + ) { super(defaultErrorStateMatcher, parentForm, parentFormGroup, ngControl); if (!this.dateAdapter) { @@ -272,10 +290,6 @@ export class McTimepicker extends McTimepickerMixinBase } } - ngOnChanges(): void { - this.stateChanges.next(); - } - ngOnDestroy(): void { this.stateChanges.complete(); } @@ -379,17 +393,19 @@ export class McTimepicker extends McTimepickerMixinBase writeValue(value: D | null): void { if (value !== null) { - this.renderer.setProperty(this.elementRef.nativeElement, + this.saveOriginalValue(value); + + this.renderer.setProperty( + this.elementRef.nativeElement, 'value', this.getTimeStringFromDate(value, this.timeFormat) ); + + this.applyInputChanges(); } - this.onChange(value || null); - this.applyInputChanges(); } onKeyDown(event: KeyboardEvent): void { - const keyCode: string = this.getKeyCode(event); if (keyCode === ARROW_UP_KEYCODE || keyCode === ARROW_DOWN_KEYCODE) { @@ -408,6 +424,12 @@ export class McTimepicker extends McTimepickerMixinBase this.onTouched = fn; } + saveOriginalValue(value: D): void { + if (this.dateAdapter.isValid(value)) { + this.originalValue = value; + } + } + /** Does some manual dirty checking on the native input `value` property. */ private dirtyCheckNativeValue() { const newValue = this.value; @@ -425,10 +447,7 @@ export class McTimepicker extends McTimepickerMixinBase return validity && validity.badInput; } - private applyInputChanges(applyParams: { - changedTime?: D; - doTimestringReformat?: boolean; - } = {}): void { + private applyInputChanges(applyParams: { changedTime?: D; doTimestringReformat?: boolean } = {}): void { const { changedTime, doTimestringReformat = true } = applyParams; const timeToApply: D | undefined = changedTime || this.getDateFromTimeString(this.elementRef.nativeElement.value); @@ -499,8 +518,7 @@ export class McTimepicker extends McTimepickerMixinBase }); } - private incrementTime(dateVal: D, - whatToIncrement: TimeParts = TimeParts.seconds): D { + private incrementTime(dateVal: D, whatToIncrement: TimeParts = TimeParts.seconds): D { let { hours, minutes, seconds } = this.getTimeDigitsFromDate(dateVal); switch (whatToIncrement) { @@ -528,8 +546,7 @@ export class McTimepicker extends McTimepickerMixinBase /** * @description Decrement part of time */ - private decrementTime(dateVal: D, - whatToDecrement: TimeParts = TimeParts.seconds): D { + private decrementTime(dateVal: D, whatToDecrement: TimeParts = TimeParts.seconds): D { let { hours, minutes, seconds } = this.getTimeDigitsFromDate(dateVal); switch (whatToDecrement) { @@ -558,9 +575,9 @@ export class McTimepicker extends McTimepickerMixinBase return cursorPos === 0 ? timeString.length : cursorPos - 1; } - private getCursorPositionOfNextTimePartStart(cursorPos: number, - timeString: string, - timeDevider: string = ':'): number { + private getCursorPositionOfNextTimePartStart( + cursorPos: number, timeString: string, timeDevider: string = ':' + ): number { const nextDividerPos: number = timeString.indexOf(timeDevider, cursorPos); return nextDividerPos !== undefined ? nextDividerPos + 1 : 0; @@ -607,8 +624,7 @@ export class McTimepicker extends McTimepickerMixinBase /** * @description Create time string for displaying inside input element of UI */ - private getTimeStringFromDate(value: D, - timeFormat: TimeFormats = DEFAULT_TIME_FORMAT): string { + private getTimeStringFromDate(value: D, timeFormat: TimeFormats = DEFAULT_TIME_FORMAT): string { if (value === undefined || value === null) { return ''; } @@ -650,7 +666,7 @@ export class McTimepicker extends McTimepickerMixinBase return this.getDateFromTimeString(`${hours}:${minutes}:${seconds}`); } - private getDateFromTimeString(timeString: string | undefined): D | undefined { + private getDateFromTimeString(timeString: string): D | undefined { if (timeString === undefined) { return; } const { @@ -680,8 +696,15 @@ export class McTimepicker extends McTimepickerMixinBase seconds = Number(hoursAndMinutesAndSeconds[3]); } - const resultDate: D = this.dateAdapter.createDateTime(1970, 0, 1, hours, minutes, seconds, 0); - // tslint:enable no-magic-numbers + const resultDate: D = this.dateAdapter.createDateTime( + this.dateAdapter.getYear(this.originalValue), + this.dateAdapter.getMonth(this.originalValue), + this.dateAdapter.getDate(this.originalValue), + hours, + minutes, + seconds, + 0 + ); return this.dateAdapter.isValid(resultDate) ? resultDate : undefined; } @@ -701,10 +724,11 @@ export class McTimepicker extends McTimepickerMixinBase } private minTimeValidator(): ValidationErrors | null { - if (this.currentDateTimeInput !== undefined && - this.minDateTime !== undefined && - this.minDateTime !== null && - this.isTimeLowerThenMin(this.currentDateTimeInput)) { + if ( + this.minTime && + this.currentDateTimeInput !== undefined && + this.isTimeLowerThenMin(this.currentDateTimeInput) + ) { return { mcTimepickerLowerThenMintime: { text: this.elementRef.nativeElement.value } }; } @@ -712,10 +736,11 @@ export class McTimepicker extends McTimepickerMixinBase } private maxTimeValidator(): ValidationErrors | null { - if (this.currentDateTimeInput !== undefined && - this.maxDateTime !== undefined && - this.maxDateTime !== null && - this.isTimeGreaterThenMax(this.currentDateTimeInput)) { + if ( + this.maxTime && + this.currentDateTimeInput !== undefined && + this.isTimeGreaterThenMax(this.currentDateTimeInput) + ) { return { mcTimepickerHigherThenMaxtime: { text: this.elementRef.nativeElement.value } }; } @@ -723,18 +748,18 @@ export class McTimepicker extends McTimepickerMixinBase } private isTimeLowerThenMin(timeToCompare: D): boolean { - if (timeToCompare === undefined || timeToCompare === null) { + if (timeToCompare === undefined || timeToCompare === null || this.minTime === null) { return false; } - return this.dateAdapter.compareDateTime(timeToCompare, this.minDateTime) < 0; + return this.dateAdapter.compareDateTime(timeToCompare, this.getDateFromTimeString(this.minTime)) < 0; } private isTimeGreaterThenMax(timeToCompare: D): boolean { - if (timeToCompare === undefined || timeToCompare === null) { + if (timeToCompare === undefined || timeToCompare === null || this.maxTime === null) { return false; } - return this.dateAdapter.compareDateTime(timeToCompare, this.maxDateTime) >= 0; + return this.dateAdapter.compareDateTime(timeToCompare, this.getDateFromTimeString(this.maxTime)) >= 0; } }