Skip to content

Commit fc407c6

Browse files
authored
Calendar Date Range Selection (#1054)
* fix: date range selection should be accurate when selecting am/pm * feat: validation for time in date range selection * fix: possible bug when selecting am / pm could produce incorrect time * fix: return undefined startDate and endDate to prior state * fix: tests should reflect internal changes
1 parent a924fac commit fc407c6

File tree

4 files changed

+129
-97
lines changed

4 files changed

+129
-97
lines changed

projects/swimlane/ngx-ui/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## HEAD (unreleased)
44

5+
- Enhancement (`ngx-calendar`): Should initialize with Date when `range` Input is used
6+
- Enhancement (`ngx-calendar`): Validation for time in date range selection
7+
- Fix (`ngx-calendar`): Possible bug when emitting date range selection and selecting AM/PM
8+
- Fix (`ngx-calendar`): Possible bug where time could appear `0` when selecting a date range
9+
510
## 48.0.4 (2024-09-18)
611

712
- Enhancement (`ngx-toggle`): Added `timeStamp` when emiting `change`

projects/swimlane/ngx-ui/src/lib/components/calendar/calendar.component.html

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,23 +163,25 @@
163163
<div class="time-row" *ngIf="range.startDate">
164164
<div>
165165
<ngx-input
166-
type="number"
166+
type="text"
167167
hint="Hour"
168168
label="{{ formatDate(range.startDate) }}"
169169
[ngModel]="startHour"
170-
min="1"
171-
max="12"
170+
[minlength]="1"
171+
[maxlength]="2"
172+
[pattern]="'^([0-9]|0[0-9]|1[0-2])$'"
172173
(change)="hourChanged($event, 'start')"
173174
>
174175
</ngx-input>
175176
</div>
176177
<div>
177178
<ngx-input
178-
type="number"
179+
type="text"
179180
hint="Minute"
180181
[ngModel]="startMinute"
181-
min="0"
182-
max="59"
182+
[minlength]="2"
183+
[maxlength]="2"
184+
[pattern]="'^[0-5]?[0-9]$'"
183185
(change)="minuteChanged($event, 'start')"
184186
>
185187
</ngx-input>
@@ -208,23 +210,25 @@
208210
<div class="time-row" *ngIf="range.endDate">
209211
<div>
210212
<ngx-input
211-
type="number"
213+
type="text"
212214
hint="Hour"
213215
label="{{ formatDate(range.endDate) }}"
214216
[ngModel]="endHour"
215-
min="1"
216-
max="12"
217+
[minlength]="1"
218+
[maxlength]="2"
219+
[pattern]="'^([0-9]|0[0-9]|1[0-2])$'"
217220
(change)="hourChanged($event, 'end')"
218221
>
219222
</ngx-input>
220223
</div>
221224
<div>
222225
<ngx-input
223-
type="number"
226+
type="text"
224227
hint="Minute"
225228
[ngModel]="endMinute"
226-
min="0"
227-
max="59"
229+
[minlength]="2"
230+
[maxlength]="2"
231+
[pattern]="'^[0-5]?[0-9]$'"
228232
(change)="minuteChanged($event, 'end')"
229233
>
230234
</ngx-input>

projects/swimlane/ngx-ui/src/lib/components/calendar/calendar.component.ts

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ export interface CalendarDateRange {
3434
endDate: Date | undefined;
3535
}
3636

37+
interface CalendarDateRangeSelection {
38+
startDateSelection: boolean;
39+
endDateSelection: boolean;
40+
}
41+
3742
@Component({
3843
selector: 'ngx-calendar',
3944
exportAs: 'ngxCalendar',
@@ -61,7 +66,10 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
6166
@Input() inputFormats: Array<string | MomentBuiltinFormat> = ['L', 'LT', 'L LT', moment.ISO_8601];
6267
@Input() selectType: string = CalendarSelect.Single;
6368
@Input() dateLabelFormat: string = 'MMM D YYYY';
64-
@Input() range: CalendarDateRange = { startDate: undefined, endDate: undefined };
69+
@Input() range: CalendarDateRange = {
70+
startDate: undefined,
71+
endDate: undefined
72+
};
6573

6674
@Input('minView')
6775
get minView() {
@@ -114,12 +122,16 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
114122
monthsList = moment.monthsShort();
115123
startYear: number;
116124

117-
startHour: number;
118-
endHour: number;
119-
startMinute: number;
120-
endMinute: number;
121-
startAmPmVal: string;
122-
endAmPmVal: string;
125+
startHour: string;
126+
endHour: string;
127+
startMinute: string;
128+
endMinute: string;
129+
startAmPmVal = 'AM';
130+
endAmPmVal = 'AM';
131+
dateRangeSelection: CalendarDateRangeSelection = {
132+
startDateSelection: false,
133+
endDateSelection: false
134+
};
123135

124136
readonly CalendarView = CalendarView;
125137
readonly CalendarSelect = CalendarSelect;
@@ -153,7 +165,6 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
153165
} else {
154166
this.currentView = this.minView;
155167
}
156-
157168
this.weeks = getMonth(this.focusDate);
158169
}
159170

@@ -177,27 +188,23 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
177188
* Initializes the values for initial time
178189
*/
179190
initializeTime(): void {
180-
this.startHour = this.range.startDate
181-
? this.range.startDate.getHours() % 12
182-
: +moment('2001-01-01T00:00:00').format('hh') % 12;
183-
this.endHour = this.range.endDate
184-
? this.range.endDate.getHours() % 12
185-
: +moment('2001-01-01T00:00:00').format('hh') % 12;
186-
187-
this.startMinute = this.range.startDate
188-
? this.range.startDate.getMinutes()
189-
: +moment('2001-01-01T00:00:00').format('mm');
190-
this.endMinute = this.range.endDate ? this.range.endDate.getMinutes() : +moment('2001-01-01T00:00:00').format('mm');
191-
this.startAmPmVal = this.range.startDate
192-
? this.range.startDate.getHours() >= 12
193-
? 'PM'
194-
: 'AM'
195-
: moment('2001-01-01T00:00:00').format('A');
196-
this.endAmPmVal = this.range.endDate
197-
? this.range.endDate.getHours() >= 12
198-
? 'PM'
199-
: 'AM'
200-
: moment('2001-01-01T00:00:00').format('A');
191+
let startDate: Date | undefined, endDate: Date | undefined;
192+
if (!this.range.startDate) {
193+
startDate = new Date(new Date().setHours(0, 0, 0, 0));
194+
} else {
195+
startDate = this.range.startDate;
196+
}
197+
if (!this.range.endDate) {
198+
endDate = new Date(new Date().setHours(0, 0, 0, 0));
199+
} else {
200+
endDate = this.range.endDate;
201+
}
202+
this.startHour = moment(startDate).format('hh');
203+
this.startMinute = moment(startDate).format('mm');
204+
this.startAmPmVal = moment(startDate).format('a').toUpperCase();
205+
this.endHour = moment(endDate).format('hh');
206+
this.endMinute = moment(endDate).format('mm');
207+
this.endAmPmVal = moment(endDate).format('a').toUpperCase();
201208
}
202209

203210
/**
@@ -324,24 +331,35 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
324331

325332
if (this.range.startDate === undefined && this.range.endDate === undefined) {
326333
this.range.startDate = this.focusDate.toDate();
327-
this.range.startDate.setHours(this.startHour);
328-
this.range.startDate.setMinutes(+this.startMinute);
334+
this.range.startDate.setHours(Number(this.startHour));
335+
this.range.startDate.setMinutes(Number(this.startMinute));
336+
this.dateRangeSelection.startDateSelection = true;
329337
} else if (this.range.endDate === undefined) {
330338
if (this.focusDate.toDate() > this.range.startDate) {
331339
this.range.endDate = this.focusDate.toDate();
332-
this.range.endDate.setHours(this.endHour);
333-
this.range.endDate.setMinutes(+this.endMinute);
340+
this.range.endDate.setHours(Number(this.endHour));
341+
this.range.endDate.setMinutes(Number(this.endMinute));
334342
} else {
335343
this.range.startDate = this.focusDate.toDate();
336-
this.range.startDate.setHours(this.startHour);
337-
this.range.startDate.setMinutes(+this.startMinute);
344+
this.range.startDate.setHours(Number(this.startHour));
345+
this.range.startDate.setMinutes(Number(this.startMinute));
346+
this.dateRangeSelection.startDateSelection = true;
338347
}
339348
} else {
340349
this.range.startDate = this.focusDate.toDate();
341-
this.range.startDate.setHours(this.startHour);
342-
this.range.startDate.setMinutes(+this.startMinute);
350+
this.range.startDate.setHours(Number(this.startHour));
351+
this.range.startDate.setMinutes(Number(this.startMinute));
352+
this.dateRangeSelection.endDateSelection = false;
353+
this.range.endDate = undefined;
354+
}
355+
356+
if (this.dateRangeSelection.startDateSelection && this.dateRangeSelection.endDateSelection) {
357+
this.dateRangeSelection.startDateSelection = false;
358+
this.dateRangeSelection.endDateSelection = false;
359+
this.range.startDate = undefined;
343360
this.range.endDate = undefined;
344361
}
362+
345363
this.onRangeSelect.emit({ startDate: this.range.startDate, endDate: this.range.endDate });
346364

347365
if (day.prevMonth || day.nextMonth) {
@@ -375,52 +393,59 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
375393
}
376394
}
377395

378-
hourChanged(newVal: number, type: string) {
379-
newVal = +newVal % 12;
396+
hourChanged(newVal: string, type: string) {
380397
if (type === 'start') {
381398
if (this.range.startDate) {
382-
if (this.startAmPmVal === 'PM') newVal = 12 + newVal;
383-
this.range.startDate.setHours(newVal);
399+
if (this.endAmPmVal === 'PM') {
400+
this.range.startDate.setHours(12 + Number(newVal));
401+
} else {
402+
this.range.startDate.setHours(Number(newVal));
403+
}
384404
}
385-
this.startHour = newVal % 12;
405+
this.startHour = newVal;
386406
} else {
387407
if (this.range.endDate) {
388-
if (this.endAmPmVal === 'PM') newVal = 12 + newVal;
389-
this.range.endDate.setHours(newVal);
408+
if (this.endAmPmVal === 'PM') {
409+
this.range.endDate.setHours(12 + Number(newVal));
410+
} else {
411+
this.range.endDate.setHours(Number(newVal));
412+
}
390413
}
391-
this.endHour = newVal % 12;
414+
this.endHour = newVal;
392415
}
393416
this.onRangeSelect.emit({ startDate: this.range.startDate, endDate: this.range.endDate });
394417
}
395-
minuteChanged(newVal: number, type: string) {
418+
minuteChanged(newVal: string, type: string) {
396419
if (type === 'start') {
397-
if (this.range.startDate) this.range.startDate.setMinutes(newVal);
420+
if (this.range.startDate) {
421+
this.range.startDate.setMinutes(Number(newVal));
422+
}
398423
this.startMinute = newVal;
399424
} else {
400-
if (this.range.endDate) this.range.endDate.setMinutes(newVal);
425+
if (this.range.endDate) {
426+
this.range.endDate.setMinutes(Number(newVal));
427+
}
401428
this.endMinute = newVal;
402429
}
403430
this.onRangeSelect.emit({ startDate: this.range.startDate, endDate: this.range.endDate });
404431
}
405432
onAmPmChange(newVal, type) {
406433
if (type === 'start') {
407-
if (this.range.startDate) {
408-
const hourClone = this.range.startDate.getHours();
409-
if (newVal === 'AM' && this.startAmPmVal === 'PM') {
410-
this.range.startDate.setHours(hourClone - 12);
411-
} else if (newVal === 'PM' && this.startAmPmVal === 'AM') {
412-
this.range.startDate.setHours(hourClone + 12);
413-
}
434+
const hourClone = this.range.startDate.getHours();
435+
if (hourClone >= 12 && newVal === 'AM') {
436+
this.range.startDate.setHours(hourClone - 12);
437+
}
438+
if (hourClone >= 0 && hourClone < 12 && newVal === 'PM') {
439+
this.range.startDate.setHours(hourClone + 12);
414440
}
415441
this.startAmPmVal = newVal;
416442
} else {
417-
if (this.range.endDate) {
418-
const hourClone = this.range.endDate.getHours();
419-
if (newVal === 'AM' && this.endAmPmVal === 'PM') {
420-
this.range.endDate.setHours(hourClone - 12);
421-
} else if (newVal === 'PM' && this.endAmPmVal === 'AM') {
422-
this.range.endDate.setHours(hourClone + 12);
423-
}
443+
const hourClone = this.range.endDate.getHours();
444+
if (hourClone >= 12 && newVal === 'AM') {
445+
this.range.endDate.setHours(hourClone - 12);
446+
}
447+
if (hourClone >= 0 && hourClone < 12 && newVal === 'PM') {
448+
this.range.endDate.setHours(hourClone + 12);
424449
}
425450
this.endAmPmVal = newVal;
426451
}
@@ -508,8 +533,6 @@ export class CalendarComponent implements OnInit, AfterViewInit, ControlValueAcc
508533
}
509534

510535
onDayDown(event: KeyboardEvent) {
511-
// console.log(event.code);
512-
513536
let stop = false;
514537

515538
if (this.currentView === CalendarView.Date) {

0 commit comments

Comments
 (0)