Skip to content

Commit 85ab5c9

Browse files
feat(date-time-editor): display & input format #6271
1 parent 17466ae commit 85ab5c9

File tree

4 files changed

+110
-83
lines changed

4 files changed

+110
-83
lines changed

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

+18-11
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,20 @@ export abstract class DatePickerUtil {
112112
}
113113

114114
public static parseDateTimeFormat(mask: string, locale: string = DatePickerUtil.DEFAULT_LOCALE): DatePartInfo[] {
115+
let format = DatePickerUtil.setInputFormat(mask);
115116
let dateTimeData: DatePartInfo[] = [];
116-
if ((mask === undefined || mask === '') && !isIE()) {
117+
if ((format === '') && !isIE()) {
117118
dateTimeData = DatePickerUtil.getDefaultLocaleMask(locale);
118119
} else {
119-
const format = (mask) ? mask : DatePickerUtil.SHORT_DATE_MASK;
120+
format = (format) ? format : DatePickerUtil.SHORT_DATE_MASK;
120121
const formatArray = Array.from(format);
121122
for (let i = 0; i < formatArray.length; i++) {
122123
const datePartRange = this.getDatePartInfoRange(formatArray[i], format, i);
123124
const dateTimeInfo = {
124125
type: DatePickerUtil.determineDatePart(formatArray[i]),
125126
start: datePartRange.start,
126127
end: datePartRange.end,
127-
format: formatArray[i],
128+
format: mask.match(new RegExp(`${format[i]}+`, 'g'))[0],
128129
};
129130
while (DatePickerUtil.isDateOrTimeChar(formatArray[i])) {
130131
if (dateTimeData.indexOf(dateTimeInfo) === -1) {
@@ -138,16 +139,17 @@ export abstract class DatePickerUtil {
138139
return dateTimeData;
139140
}
140141

141-
public static setInputFormat(format: string) {
142+
public static setInputFormat(format: string): string {
143+
if (!format) { return ''; }
142144
let chars = '';
143145
let newFormat = '';
144146
for (let i = 0; ; i++) {
145147
while (DatePickerUtil.isDateOrTimeChar(format[i])) {
146148
chars += format[i];
147149
i++;
148150
}
149-
150-
if (chars.length === 1 || chars.length === 3) {
151+
const datePartType = DatePickerUtil.determineDatePart(chars[0]);
152+
if (datePartType !== DatePart.Year) {
151153
newFormat += chars[0].repeat(2);
152154
} else {
153155
newFormat += chars;
@@ -165,7 +167,7 @@ export abstract class DatePickerUtil {
165167
}
166168

167169
public static isDateOrTimeChar(char: string): boolean {
168-
return TimeCharsArr.includes(char) || DateCharsArr.includes(char);
170+
return TimeCharsArr.indexOf(char) !== -1 || DateCharsArr.indexOf(char) !== -1;
169171
}
170172

171173
public static calculateDateOnSpin(delta: number, newDate: Date, currentDate: Date, isSpinLoop: boolean): Date {
@@ -253,10 +255,15 @@ export abstract class DatePickerUtil {
253255
return currentDate;
254256
}
255257

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));
258+
public static calculateAmPmOnSpin(newDate: Date, currentDate: Date, amPmFromMask: string) {
259+
switch (amPmFromMask) {
260+
case 'AM':
261+
newDate = new Date(newDate.setHours(newDate.getHours() + 12 * 1));
262+
break;
263+
case 'PM':
264+
newDate = new Date(newDate.setHours(newDate.getHours() + 12 * -1));
265+
break;
266+
}
260267
if (newDate.getDate() !== currentDate.getDate()) {
261268
return currentDate;
262269
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ fdescribe('IgxDateTimeEditor', () => {
8787
fdescribe('Should be able to spin the date portions.', () => {
8888
it('Should correctly increment / decrement date portions with passed in DatePart', () => {
8989
dateTimeEditor = new IgxDateTimeEditorDirective(elementRef, maskParsingService, renderer2, DOCUMENT);
90-
dateTimeEditor.format = 'dd/M/yy';
90+
dateTimeEditor.inputFormat = 'dd/M/yy';
9191
dateTimeEditor.ngOnInit();
9292
dateTimeEditor.value = new Date();
9393
const date = dateTimeEditor.value.getDate();
@@ -145,7 +145,7 @@ fdescribe('IgxDateTimeEditor', () => {
145145

146146
it('Should prioritize Date for spinning, if it is set in format', () => {
147147
dateTimeEditor = new IgxDateTimeEditorDirective(elementRef, maskParsingService, renderer2, DOCUMENT);
148-
dateTimeEditor.format = 'dd/M/yy HH:mm:ss tt';
148+
dateTimeEditor.inputFormat = 'dd/M/yy HH:mm:ss tt';
149149
dateTimeEditor.ngOnInit();
150150
dateTimeEditor.value = new Date(2020, 2, 11);
151151

@@ -205,7 +205,7 @@ fdescribe('IgxDateTimeEditor', () => {
205205
* format must be set because the editor will prioritize Date if Hours is not set
206206
* and no DatePart is provided to increment / decrement
207207
*/
208-
dateTimeEditor.format = 'HH:mm:ss tt';
208+
dateTimeEditor.inputFormat = 'HH:mm:ss tt';
209209
dateTimeEditor.ngOnInit();
210210
dateTimeEditor.value = new Date();
211211
const hours = dateTimeEditor.value.getHours();

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

+51-24
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
2121
@Input()
2222
public value: Date;
2323

24-
// TODO
25-
// locale date formats should be the same sa igxdatepicker's - shortDate, longDate, etc
2624
@Input()
27-
public locale: string;
25+
public locale = 'en';
2826

2927
@Input()
3028
public minValue: string | Date;
@@ -44,14 +42,17 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
4442
@Output()
4543
public validationFailed = new EventEmitter<IgxDateTimeEditorEventArgs>();
4644

47-
public get format(): string {
45+
@Input()
46+
public displayFormat: string;
47+
48+
public get inputFormat(): string {
4849
return this._format;
4950
}
5051

5152
@Input(`igxDateTimeEditor`)
52-
public set format(value: string) {
53+
public set inputFormat(value: string) {
5354
this._format = value;
54-
const mask = this.buildMask(this.format);
55+
const mask = this.buildMask(this.inputFormat);
5556
this.mask = value.indexOf('tt') !== -1 ? mask.substring(0, mask.length - 2) + 'LL' : mask;
5657
}
5758

@@ -71,6 +72,10 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
7172
return literals;
7273
}
7374

75+
private get emptyInput() {
76+
return this.maskParser.applyMask('', this.maskOptions);
77+
}
78+
7479
private get targetDatePart(): DatePart {
7580
if (this._document.activeElement === this.nativeElement) {
7681
return this._dateTimeFormatParts.find(p => p.start <= this.selectionStart && this.selectionStart <= p.end).type;
@@ -94,8 +99,8 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
9499

95100
/** @hidden */
96101
public ngOnInit(): void {
97-
this._dateTimeFormatParts = DatePickerUtil.parseDateTimeFormat(DatePickerUtil.setInputFormat(this.format));
98-
this.renderer.setAttribute(this.nativeElement, 'placeholder', this.format);
102+
this._dateTimeFormatParts = DatePickerUtil.parseDateTimeFormat(this.inputFormat);
103+
this.renderer.setAttribute(this.nativeElement, 'placeholder', this.inputFormat);
99104
}
100105

101106
public clear(): void {
@@ -105,15 +110,15 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
105110

106111
public increment(datePart?: DatePart): void {
107112
const newValue = datePart ? this.calculateValueOnSpin(datePart, 1) : this.calculateValueOnSpin(this.targetDatePart, 1);
108-
if (newValue && this.value && newValue !== this.value) {
113+
if (newValue) {
109114
this.updateValue(newValue);
110115
this.updateMask();
111116
}
112117
}
113118

114119
public decrement(datePart?: DatePart): void {
115120
const newValue = datePart ? this.calculateValueOnSpin(datePart, -1) : this.calculateValueOnSpin(this.targetDatePart, -1);
116-
if (newValue && this.value && newValue !== this.value) {
121+
if (newValue) {
117122
this.updateValue(newValue);
118123
this.updateMask();
119124
}
@@ -152,6 +157,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
152157

153158
/** @hidden */
154159
public onFocus(): void {
160+
// TODO: apply mask on focus & focused flag
155161
this.onTouchCallback();
156162
super.onFocus();
157163
}
@@ -161,19 +167,21 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
161167
// if inputted string does not fit in the editor, show as many chars as possible followed by "..." ?
162168
if (!this.valueInRange(this.value)) {
163169
this.validationFailed.emit({ oldValue: this._oldValue, newValue: this.value });
164-
// this.updateMask(); TODO: set empty mask
170+
this.inputValue = this.emptyInput;
171+
this.updateValue(null);
172+
return;
165173
}
166174

167-
// TODO: display value pipe
168-
// this.updateMask();
175+
const format = this.displayFormat ? this.displayFormat : this.inputFormat;
176+
this.inputValue = formatDate(this.value, format, this.locale);
169177
this.onTouchCallback();
170178
super.onBlur(event);
171179
}
172180

173181
/** @hidden */
174-
protected handleInputChanged(): void {
182+
protected onInputChanged(): void {
175183
// the mask must be updated before any date operations
176-
super.handleInputChanged();
184+
super.onInputChanged();
177185
if (this.inputValue === this.maskParser.applyMask('', this.maskOptions)) {
178186
this.updateValue(null);
179187
return;
@@ -187,8 +195,6 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
187195
this.validationFailed.emit({ oldValue: this.value, newValue: parsedDate.value });
188196
}
189197
}
190-
191-
super.afterInput();
192198
}
193199

194200
private buildMask(format: string): string {
@@ -213,7 +219,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
213219
}
214220

215221
private calculateValueOnSpin(datePart: DatePart, delta: number): Date {
216-
if (!this.value) { return; }
222+
if (!this.value) { return null; }
217223
const currentDate = this.value as Date;
218224
const newDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(),
219225
currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds());
@@ -232,7 +238,9 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
232238
case DatePart.Seconds:
233239
return DatePickerUtil.calculateSecondsOnSpin(delta, newDate, currentDate, this.isSpinLoop);
234240
case DatePart.AmPm:
235-
return DatePickerUtil.calculateAmPmOnSpin(delta, newDate, currentDate);
241+
const formatPart = this._dateTimeFormatParts.find(dp => dp.type === DatePart.AmPm);
242+
const amPmFromMask = this.inputValue.substring(formatPart.start, formatPart.end);
243+
return DatePickerUtil.calculateAmPmOnSpin(newDate, currentDate, amPmFromMask);
236244
}
237245
}
238246

@@ -253,15 +261,34 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
253261
let targetValue: string = this.getMaskedValue(p.type, partLength);
254262

255263
if (p.type === DatePart.Month) {
256-
targetValue = this.prependPromptChars(
257-
parseInt(targetValue.replace(new RegExp(this.promptChar, 'g'), '0'), 10) + 1, partLength);
264+
targetValue = this.prependValue(
265+
parseInt(targetValue.replace(new RegExp(this.promptChar, 'g'), '0'), 10) + 1, partLength, '0');
266+
}
267+
268+
if (p.type === DatePart.Hours && p.format.indexOf('h') !== -1) {
269+
targetValue = this.prependValue(this.toTwelveHourFormat(targetValue), partLength, '0');
270+
}
271+
272+
if (p.type === DatePart.Year && p.format.length === 2) {
273+
targetValue = this.prependValue(parseInt(targetValue.slice(-2), 10), partLength, '0');
258274
}
259275

260276
this.inputValue = this.maskParser.replaceInMask(this.inputValue, targetValue, this.maskOptions, p.start, p.end).value;
261277
});
262278
this.setSelectionRange(cursor);
263279
}
264280

281+
private toTwelveHourFormat(value: string): number {
282+
let hour = parseInt(value.replace(new RegExp(this.promptChar, 'g'), '0'), 10);
283+
if (hour > 12) {
284+
hour -= 12;
285+
} else if (hour === 0) {
286+
hour = 12;
287+
}
288+
289+
return hour;
290+
}
291+
265292
private getMaskedValue(datePart: DatePart, partLength: number): string {
266293
let maskedValue;
267294
switch (datePart) {
@@ -289,14 +316,14 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnIn
289316
}
290317

291318
if (datePart !== DatePart.AmPm) {
292-
return this.prependPromptChars(maskedValue, partLength);
319+
return this.prependValue(maskedValue, partLength, '0');
293320
}
294321

295322
return maskedValue;
296323
}
297324

298-
private prependPromptChars(value: number, partLength: number): string {
299-
return (this.promptChar + value.toString()).slice(-partLength);
325+
private prependValue(value: number, partLength: number, prependChar: string): string {
326+
return (prependChar + value.toString()).slice(-partLength);
300327
}
301328

302329
private spin(event: KeyboardEvent): void {

projects/igniteui-angular/src/lib/directives/mask/mask.directive.ts

+38-45
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,44 @@ export class IgxMaskDirective implements OnInit, AfterViewChecked, ControlValueA
187187
/** @hidden */
188188
@HostListener('input')
189189
public onInputChanged(): void {
190-
this.handleInputChanged(true);
190+
if (isIE() && this._stopPropagation) {
191+
this._stopPropagation = false;
192+
return;
193+
}
194+
195+
if (this._hasDropAction) {
196+
this._start = this.selectionStart;
197+
}
198+
if (this.inputValue.length < this._oldText.length && this._key === KEYCODES.INPUT_METHOD) {
199+
// software keyboard input delete
200+
this._key = KEYCODES.BACKSPACE;
201+
}
202+
203+
let valueToParse = '';
204+
switch (this._key) {
205+
case KEYCODES.DELETE:
206+
this._end = this._start === this._end ? ++this._end : this._end;
207+
break;
208+
case KEYCODES.BACKSPACE:
209+
this._start = this.selectionStart;
210+
break;
211+
default:
212+
valueToParse = this.inputValue.substring(this._start, this.selectionEnd);
213+
break;
214+
}
215+
216+
const replacedData = this.maskParser.replaceInMask(this._oldText, valueToParse, this.maskOptions, this._start, this._end);
217+
this.inputValue = replacedData.value;
218+
if (this._key === KEYCODES.BACKSPACE) { replacedData.end = this._start; }
219+
this.setSelectionRange(replacedData.end);
220+
221+
const rawVal = this.maskParser.parseValueFromMask(this.inputValue, this.maskOptions);
222+
this._dataValue = this.includeLiterals ? this.inputValue : rawVal;
223+
this._onChangeCallback(this._dataValue);
224+
225+
this.onValueChange.emit({ rawValue: rawVal, formattedValue: this.inputValue });
226+
227+
this.afterInput();
191228
}
192229

193230
/** @hidden */
@@ -235,50 +272,6 @@ export class IgxMaskDirective implements OnInit, AfterViewChecked, ControlValueA
235272
this._droppedData = event.dataTransfer.getData('text');
236273
}
237274

238-
/** @hidden */
239-
protected handleInputChanged(reset?: boolean) {
240-
if (isIE() && this._stopPropagation) {
241-
this._stopPropagation = false;
242-
return;
243-
}
244-
245-
if (this._hasDropAction) {
246-
this._start = this.selectionStart;
247-
}
248-
if (this.inputValue.length < this._oldText.length && this._key === KEYCODES.INPUT_METHOD) {
249-
// software keyboard input delete
250-
this._key = KEYCODES.BACKSPACE;
251-
}
252-
253-
let valueToParse = '';
254-
switch (this._key) {
255-
case KEYCODES.DELETE:
256-
this._end = this._start === this._end ? ++this._end : this._end;
257-
break;
258-
case KEYCODES.BACKSPACE:
259-
this._start = this.selectionStart;
260-
break;
261-
default:
262-
valueToParse = this.inputValue.substring(this._start, this.selectionEnd);
263-
break;
264-
}
265-
266-
const replacedData = this.maskParser.replaceInMask(this._oldText, valueToParse, this.maskOptions, this._start, this._end);
267-
this.inputValue = replacedData.value;
268-
if (this._key === KEYCODES.BACKSPACE) { replacedData.end = this._start; }
269-
this.setSelectionRange(replacedData.end);
270-
271-
const rawVal = this.maskParser.parseValueFromMask(this.inputValue, this.maskOptions);
272-
this._dataValue = this.includeLiterals ? this.inputValue : rawVal;
273-
this._onChangeCallback(this._dataValue);
274-
275-
this.onValueChange.emit({ rawValue: rawVal, formattedValue: this.inputValue });
276-
277-
if (reset) {
278-
this.afterInput();
279-
}
280-
}
281-
282275
/** @hidden */
283276
protected showMask(value: string) {
284277
if (this.focusedValuePipe) {

0 commit comments

Comments
 (0)