Skip to content

Commit 17466ae

Browse files
feat(date-time-editor): mask input on spinning #6271
1 parent 0dee1ca commit 17466ae

File tree

4 files changed

+71
-54
lines changed

4 files changed

+71
-54
lines changed

projects/igniteui-angular/src/lib/date-picker/date-picker.utils.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,6 @@ const enum DateParts {
6161
Year = 'year'
6262
}
6363

64-
/** @hidden */
65-
const enum TimeParts {
66-
Hour = 'hour',
67-
Minute = 'minute',
68-
Second = 'second',
69-
AmPm = 'ampm'
70-
}
71-
7264
/**
7365
* @hidden1
7466
*/
@@ -90,7 +82,7 @@ export abstract class DatePickerUtil {
9082
});
9183

9284
if (parts[DatePart.Month] < 1 || 12 < parts[DatePart.Month]) {
93-
return { state: DateState.Invalid, value: null };
85+
return { state: DateState.Invalid, value: new Date(NaN) };
9486
}
9587

9688
// TODO: Century threshold
@@ -99,11 +91,11 @@ export abstract class DatePickerUtil {
9991
}
10092

10193
if (parts[DatePart.Date] > DatePickerUtil.daysInMonth(parts[DatePart.Year], parts[DatePart.Month])) {
102-
return { state: DateState.Invalid, value: null };
94+
return { state: DateState.Invalid, value: new Date(NaN) };
10395
}
10496

10597
if (parts[DatePart.Hours] > 23 || parts[DatePart.Minutes] > 59 || parts[DatePart.Seconds] > 59) {
106-
return { state: DateState.Invalid, value: null };
98+
return { state: DateState.Invalid, value: new Date(NaN) };
10799
}
108100

109101
return {
@@ -180,7 +172,7 @@ export abstract class DatePickerUtil {
180172
newDate = new Date(newDate.setDate(newDate.getDate() + delta));
181173
if (isSpinLoop) {
182174
if (currentDate.getMonth() > newDate.getMonth()) {
183-
return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
175+
return new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); // add delta instead of 1?
184176
} else if (currentDate.getMonth() < newDate.getMonth()) {
185177
return new Date(currentDate.setDate(1));
186178
}
@@ -261,6 +253,17 @@ export abstract class DatePickerUtil {
261253
return currentDate;
262254
}
263255

256+
public static calculateAmPmOnSpin(delta: number, newDate: Date, currentDate: Date) {
257+
newDate = delta > 0
258+
? new Date(newDate.setHours(newDate.getHours() + 12))
259+
: new Date(newDate.setHours(newDate.getHours() - 12));
260+
if (newDate.getDate() !== currentDate.getDate()) {
261+
return currentDate;
262+
}
263+
264+
return newDate;
265+
}
266+
264267
private static getCleanVal(inputData: string, datePart: DatePartInfo): string {
265268
return DatePickerUtil.trimUnderlines(inputData.substring(datePart.start, datePart.end));
266269
}

projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ fdescribe('IgxDateTimeEditor', () => {
276276
it('Should revert to empty mask on clear()', () => {
277277
// TODO
278278
// should clear inner value and emit valueChanged
279-
})
279+
});
280280

281281
it('Should not block the user from typing/pasting/dragging dates outside of min/max range', () => {
282282
// TODO

projects/igniteui-angular/src/lib/directives/date-time-editor/date-time-editor.directive.ts

+52-40
Original file line numberDiff line numberDiff line change
@@ -100,27 +100,22 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
100100

101101
public clear(): void {
102102
this.showMask('');
103-
this.value = null;
104-
this.valueChanged.emit({ oldValue: this._oldValue, newValue: this.value });
103+
this.updateValue(null);
105104
}
106105

107106
public increment(datePart?: DatePart): void {
108107
const newValue = datePart ? this.calculateValueOnSpin(datePart, 1) : this.calculateValueOnSpin(this.targetDatePart, 1);
109108
if (newValue && this.value && newValue !== this.value) {
110109
this.updateValue(newValue);
110+
this.updateMask();
111111
}
112-
113-
// TODO: update mask
114-
// this.updateMask();
115112
}
116113

117114
public decrement(datePart?: DatePart): void {
118115
const newValue = datePart ? this.calculateValueOnSpin(datePart, -1) : this.calculateValueOnSpin(this.targetDatePart, -1);
119116
if (newValue && this.value && newValue !== this.value) {
120117
this.updateValue(newValue);
121-
122-
// TODO: update mask
123-
// this.updateMask(true);
118+
this.updateMask();
124119
}
125120
}
126121

@@ -148,9 +143,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
148143
}
149144

150145
if (event.ctrlKey && event.key === KEYS.SEMICOLON) {
151-
// TODO: emit success & update mask?
152-
this.value = new Date();
153-
this.valueChanged.emit({ oldValue: this._oldValue, newValue: this.value });
146+
this.updateValue(new Date());
154147
this.updateMask();
155148
}
156149

@@ -172,7 +165,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
172165
}
173166

174167
// TODO: display value pipe
175-
// this.updateMask(); TODO: fill in any empty date parts
168+
// this.updateMask();
176169
this.onTouchCallback();
177170
super.onBlur(event);
178171
}
@@ -181,9 +174,18 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
181174
protected handleInputChanged(): void {
182175
// the mask must be updated before any date operations
183176
super.handleInputChanged();
177+
if (this.inputValue === this.maskParser.applyMask('', this.maskOptions)) {
178+
this.updateValue(null);
179+
return;
180+
}
181+
184182
const parsedDate = this.parseDate(this.inputValue);
185-
if (parsedDate.state === DateState.Valid && this.inputValue.indexOf(this.promptChar) === -1) {
186-
this.updateValue(parsedDate.value);
183+
if (this.inputValue.indexOf(this.promptChar) === -1) { // better way to check for filled input?
184+
if (parsedDate.state === DateState.Valid) {
185+
this.updateValue(parsedDate.value);
186+
} else {
187+
this.validationFailed.emit({ oldValue: this.value, newValue: parsedDate.value });
188+
}
187189
}
188190

189191
super.afterInput();
@@ -198,6 +200,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
198200
}
199201

200202
private valueInRange(value: Date): boolean {
203+
if (!value) { return; }
201204
const maxValueAsDate = this.isDate(this.maxValue) ? this.maxValue : this.parseDate(this.maxValue).value;
202205
const minValueAsDate = this.isDate(this.minValue) ? this.minValue : this.parseDate(this.minValue).value;
203206
if (maxValueAsDate && minValueAsDate) {
@@ -228,6 +231,8 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
228231
return DatePickerUtil.calculateMinutesOnSpin(delta, newDate, currentDate, this.isSpinLoop);
229232
case DatePart.Seconds:
230233
return DatePickerUtil.calculateSecondsOnSpin(delta, newDate, currentDate, this.isSpinLoop);
234+
case DatePart.AmPm:
235+
return DatePickerUtil.calculateAmPmOnSpin(delta, newDate, currentDate);
231236
}
232237
}
233238

@@ -244,47 +249,54 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
244249
private updateMask() {
245250
const cursor = this.selectionEnd;
246251
this._dateTimeFormatParts.forEach(p => {
247-
// TODO: cycle through the parts and update the mask based on their indices
248-
let value: number = this.breakUpDate(p.type);
249-
// TODO: append all date parts from the date object to one another
250-
// if a part of that date object's length is less than the expected length (taken from the format) -> prepend prompt chars
251-
value = p.type === DatePart.Month ? value + 1 : value;
252-
this.inputValue = this.maskParser.replaceInMask(this.inputValue, `${value}`, this.maskOptions, p.start, p.end).value;
252+
const partLength = p.end - p.start;
253+
let targetValue: string = this.getMaskedValue(p.type, partLength);
254+
255+
if (p.type === DatePart.Month) {
256+
targetValue = this.prependPromptChars(
257+
parseInt(targetValue.replace(new RegExp(this.promptChar, 'g'), '0'), 10) + 1, partLength);
258+
}
259+
260+
this.inputValue = this.maskParser.replaceInMask(this.inputValue, targetValue, this.maskOptions, p.start, p.end).value;
253261
});
254262
this.setSelectionRange(cursor);
255263
}
256264

257-
private breakUpDate(datePart: DatePart): number {
258-
const valueAsDate = this.value as Date;
265+
private getMaskedValue(datePart: DatePart, partLength: number): string {
266+
let maskedValue;
259267
switch (datePart) {
260268
case DatePart.Date:
261-
return valueAsDate.getDate();
269+
maskedValue = this.value.getDate();
270+
break;
262271
case DatePart.Month:
263-
return valueAsDate.getMonth();
272+
maskedValue = this.value.getMonth();
273+
break;
264274
case DatePart.Year:
265-
return valueAsDate.getFullYear();
275+
maskedValue = this.value.getFullYear();
276+
break;
266277
case DatePart.Hours:
267-
return valueAsDate.getHours();
278+
maskedValue = this.value.getHours();
279+
break;
268280
case DatePart.Minutes:
269-
return valueAsDate.getMinutes();
281+
maskedValue = this.value.getMinutes();
282+
break;
270283
case DatePart.Seconds:
271-
return valueAsDate.getSeconds();
284+
maskedValue = this.value.getSeconds();
285+
break;
286+
case DatePart.AmPm:
287+
maskedValue = this.value.getHours() >= 12 ? 'PM' : 'AM';
288+
break;
272289
}
273-
}
274290

275-
private updateMaskOnSpin(parsedValue: number, editedParts: DatePartInfo[], addPromptChar?: boolean) {
276-
let start = editedParts[0].start;
277-
const end = editedParts[editedParts.length - 1].start;
278-
if (parsedValue.toString().length < editedParts.length) {
279-
start += editedParts.length - parsedValue.toString().length;
280-
if (addPromptChar) {
281-
this.inputValue = this.maskParser.replaceCharAt(this.inputValue, start - 1, this.promptChar);
282-
}
291+
if (datePart !== DatePart.AmPm) {
292+
return this.prependPromptChars(maskedValue, partLength);
283293
}
284294

285-
this.inputValue = this.maskParser.replaceInMask(
286-
this.inputValue, `${parsedValue} `, this.maskOptions, start, end).value;
287-
this.setSelectionRange(this.end);
295+
return maskedValue;
296+
}
297+
298+
private prependPromptChars(value: number, partLength: number): string {
299+
return (this.promptChar + value.toString()).slice(-partLength);
288300
}
289301

290302
private spin(event: KeyboardEvent): void {

projects/igniteui-angular/src/lib/directives/mask/mask-parsing.service.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ export class MaskParsingService {
100100
}
101101
continue;
102102
}
103-
if (chars[0] && !this.validateCharOnPosition(chars[0], i, maskOptions.format)) {
103+
if (chars[0]
104+
&& !this.validateCharOnPosition(chars[0], i, maskOptions.format)
105+
&& chars[0] !== maskOptions.promptChar) {
104106
break;
105107
}
106108

0 commit comments

Comments
 (0)