Skip to content

Commit 1a16ff7

Browse files
committed
perf(calendar): multi and range selection #4282
1 parent d4fa2c9 commit 1a16ff7

File tree

4 files changed

+165
-86
lines changed

4 files changed

+165
-86
lines changed

projects/igniteui-angular/src/lib/calendar/calendar-base.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ export class IgxCalendarBase implements ControlValueAccessor {
143143
* Otherwise it is an array of `Date` objects.
144144
*/
145145
public set value(value: Date | Date[]) {
146+
if (!value || !!value && (value as Date[]).length === 0) {
147+
return;
148+
}
149+
146150
this.selectDate(value);
147151
}
148152

@@ -416,7 +420,7 @@ export class IgxCalendarBase implements ControlValueAccessor {
416420
this.selectedDates = this.selectedDates.concat(newSelection);
417421
}
418422
}
419-
423+
this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf());
420424
this._onChangeCallback(this.selectedDates);
421425
}
422426

projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.ts

+35-82
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, Input, Output, EventEmitter, HostBinding, ElementRef, HostListener } from '@angular/core';
22
import { ICalendarDate, isDateInRanges } from '../calendar';
3-
import { DateRangeDescriptor, DateRangeType } from '../../core/dates';
3+
import { DateRangeDescriptor } from '../../core/dates';
44
import { CalendarSelection } from '../calendar-base';
55

66
/**
@@ -17,8 +17,21 @@ export class IgxDayItemComponent {
1717
@Input()
1818
public selection: string;
1919

20+
/**
21+
* Returns boolean indicating if the day is selected
22+
*
23+
*/
2024
@Input()
21-
public value: Date | Date[];
25+
public get selected(): any {
26+
return this._selected;
27+
}
28+
29+
/**
30+
* Selects the day
31+
*/
32+
public set selected(value: any) {
33+
this._selected = value;
34+
}
2235

2336
@Input()
2437
public disabledDates: DateRangeDescriptor[];
@@ -32,32 +45,29 @@ export class IgxDayItemComponent {
3245
@Input()
3346
public hideOutsideDays = false;
3447

35-
@Output()
36-
public onDateSelection = new EventEmitter<ICalendarDate>();
48+
@Input()
49+
@HostBinding('class.igx-calendar__date--last')
50+
public isLastInRange = false;
3751

38-
public get selected(): boolean {
39-
const date = this.date.date;
52+
@Input()
53+
@HostBinding('class.igx-calendar__date--first')
54+
public isFirstInRange = false;
4055

41-
if (!this.value) {
42-
return;
43-
}
56+
@Input()
57+
@HostBinding('class.igx-calendar__date--firstday')
58+
public isFirstInMonth = false;
4459

45-
if (this.selection === CalendarSelection.SINGLE) {
46-
this._selected = (this.value as Date).getTime() === date.getTime();
47-
} else {
48-
const selectedDates = (this.value as Date[]);
49-
const currentDate = selectedDates.find(element => element.getTime() === date.getTime());
60+
@Input()
61+
@HostBinding('class.igx-calendar__date--lastday')
62+
public isLastInMonth = false;
5063

51-
this._index = selectedDates.indexOf(currentDate) + 1;
52-
this._selected = !!this._index;
53-
}
64+
@Input()
65+
@HostBinding('class.igx-calendar__date--range')
66+
public isWithinRange = false;
5467

55-
return this._selected;
56-
}
68+
@Output()
69+
public onDateSelection = new EventEmitter<ICalendarDate>();
5770

58-
public set selected(value: boolean) {
59-
this._selected = value;
60-
}
6171

6272
public get isCurrentMonth(): boolean {
6373
return this.date.isCurrentMonth;
@@ -146,12 +156,12 @@ export class IgxDayItemComponent {
146156

147157
@HostBinding('class.igx-calendar__date--selected')
148158
public get isSelectedCSS(): boolean {
149-
return this.selected && this.isCurrentMonth;
159+
return this.isCurrentMonth && this.selected;
150160
}
151161

152162
@HostBinding('class.igx-calendar__date--selected-dimmed')
153163
public get isSelectedDimmedCSS(): boolean {
154-
return this.isSingleSelection && this.selected && !this.isCurrentMonth;
164+
return !this.isCurrentMonth && this.isSingleSelection && this.selected;
155165
}
156166

157167
@HostBinding('class.igx-calendar__date--weekend')
@@ -161,25 +171,9 @@ export class IgxDayItemComponent {
161171

162172
@HostBinding('class.igx-calendar__date--disabled')
163173
public get isDisabledCSS(): boolean {
164-
return this.isDisabled || this.isOutOfRange || this.isHidden;
174+
return this.isHidden || this.isDisabled || this.isOutOfRange;
165175
}
166176

167-
@HostBinding('class.igx-calendar__date--range')
168-
public get isWithinRange() {
169-
if (Array.isArray(this.value) && this.value.length > 1) {
170-
171-
return isDateInRanges(this.date.date,
172-
[
173-
{
174-
type: DateRangeType.Between,
175-
dateRange: [this.value[0], this.value[this.value.length - 1]]
176-
}
177-
]
178-
);
179-
}
180-
181-
return false;
182-
}
183177

184178
@HostBinding('class.igx-calendar__date--special')
185179
public get isSpecialCSS(): boolean {
@@ -191,52 +185,11 @@ export class IgxDayItemComponent {
191185
return this.selection !== CalendarSelection.RANGE;
192186
}
193187

194-
@HostBinding('class.igx-calendar__date--first')
195-
public get isFirstInRange(): boolean {
196-
if (this.isSingleSelection) {
197-
return false;
198-
}
199-
200-
return this._index === 1;
201-
}
202-
203-
@HostBinding('class.igx-calendar__date--last')
204-
public get isLastInRange(): boolean {
205-
if (this.isSingleSelection) {
206-
return false;
207-
}
208-
209-
return (this.value as Date[]).length === this._index;
210-
}
211-
212-
@HostBinding('class.igx-calendar__date--lastday')
213-
public get isLastInMonth(): boolean {
214-
const checkLast = true;
215-
return this.isFirstLastInMonth(checkLast);
216-
}
217-
218-
@HostBinding('class.igx-calendar__date--firstday')
219-
public get isFirstInMonth(): boolean {
220-
const checkLast = false;
221-
return this.isFirstLastInMonth(checkLast);
222-
}
223188

224-
private _index: Number;
225189
private _selected = false;
226190

227191
constructor(private elementRef: ElementRef) { }
228192

229-
private isFirstLastInMonth(checkLast: boolean): boolean {
230-
const inc = checkLast ? 1 : -1;
231-
if (!this.isSingleSelection && this.isCurrentMonth && this.isWithinRange) {
232-
const nextDay = new Date(this.date.date);
233-
nextDay.setDate(nextDay.getDate() + inc);
234-
if (this.date.date.getMonth() + inc === nextDay.getMonth()) {
235-
return true;
236-
}
237-
}
238-
return false;
239-
}
240193

241194
@HostListener('click')
242195
@HostListener('keydown.enter')

projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.html

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
*ngFor="let day of week; trackBy: dateTracker"
1212
[date]="day"
1313
[selection]="selection"
14-
[value]="value"
14+
[selected]="isSelected(day)"
15+
[isLastInRange]="isLastInRange(day)"
16+
[isFirstInRange]="isFirstInRange(day)"
17+
[isFirstInMonth]="isFirstInMonth(day)"
18+
[isLastInMonth]="isLastInMonth(day)"
19+
[isWithinRange]="isWithinRange(day.date, true)"
1520
[disabledDates]="disabledDates"
1621
[specialDates]="specialDates"
1722
[outOfRangeDates]="outOfRangeDates"

projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.ts

+119-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import {
99
HostBinding,
1010
DoCheck
1111
} from '@angular/core';
12-
import { ICalendarDate } from '../../calendar';
12+
import { ICalendarDate, isDateInRanges } from '../../calendar';
1313
import { trigger, transition, useAnimation } from '@angular/animations';
1414
import { slideInLeft, slideInRight } from '../../animations/main';
1515
import { NG_VALUE_ACCESSOR } from '@angular/forms';
1616
import { IgxDayItemComponent } from './day-item.component';
1717
import { DateRangeDescriptor, DateRangeType } from '../../core/dates';
18-
import { IgxCalendarBase, ScrollMonth } from '../calendar-base';
18+
import { IgxCalendarBase, ScrollMonth, CalendarSelection } from '../calendar-base';
19+
import { isEqual } from '../../core/utils';
1920

2021
let NEXT_ID = 0;
2122

@@ -202,6 +203,100 @@ export class IgxDaysViewComponent extends IgxCalendarBase implements DoCheck {
202203
return this.viewDate.getFullYear() === value.getFullYear();
203204
}
204205

206+
/**
207+
* @hidden
208+
*/
209+
public isSelected(date: ICalendarDate): boolean {
210+
const selectedDates = (this.value as Date[]);
211+
if (!date.isCurrentMonth) {
212+
return;
213+
}
214+
215+
if (!this.value || selectedDates.length === 0) {
216+
return;
217+
}
218+
219+
if (selectedDates.length === 1) {
220+
return this.value[0].getTime() === date.date.getTime();
221+
}
222+
223+
if (this.selection === CalendarSelection.MULTI) {
224+
const start = this.getDateOnly(selectedDates[0]);
225+
const end = this.getDateOnly(selectedDates[selectedDates.length - 1]);
226+
227+
if (this.isWithinRange(date.date, false, start, end)) {
228+
const currentDate = selectedDates.find(element => element.getTime() === date.date.getTime());
229+
return !!currentDate;
230+
} else {
231+
return false;
232+
}
233+
234+
} else {
235+
return this.isWithinRange(date.date, false);
236+
}
237+
}
238+
239+
/**
240+
* @hidden
241+
*/
242+
public isLastInRange(date: ICalendarDate): boolean {
243+
if (this.isSingleSelection || !this.value) {
244+
return false;
245+
}
246+
247+
const dates = this.value as Date[];
248+
const lastDate = dates[dates.length - 1];
249+
return isEqual(lastDate, date.date);
250+
}
251+
252+
/**
253+
* @hidden
254+
*/
255+
public isFirstInRange(date: ICalendarDate): boolean {
256+
if (this.isSingleSelection || !this.value) {
257+
return false;
258+
}
259+
260+
return isEqual((this.value as Date[])[0], date.date);
261+
}
262+
263+
/**
264+
* @hidden
265+
*/
266+
public isFirstInMonth(date: ICalendarDate): boolean {
267+
const checkLast = false;
268+
return this.isFirstLastInMonth(checkLast, date);
269+
}
270+
271+
/**
272+
* @hidden
273+
*/
274+
public isLastInMonth(date: ICalendarDate): boolean {
275+
const checkLast = false;
276+
return this.isFirstLastInMonth(checkLast, date);
277+
}
278+
279+
/**
280+
* @hidden
281+
*/
282+
public isWithinRange(date: Date, checkForRange: boolean, min?: Date, max?: Date): boolean {
283+
if (checkForRange && !(Array.isArray(this.value) && this.value.length > 1)) {
284+
return;
285+
}
286+
287+
min = min ? min : this.value[0];
288+
max = max ? max : this.value[(this.value as Date[]).length - 1];
289+
290+
return isDateInRanges(date,
291+
[
292+
{
293+
type: DateRangeType.Between,
294+
dateRange: [min, max]
295+
}
296+
]
297+
);
298+
}
299+
205300
/**
206301
*@hidden
207302
*/
@@ -509,6 +604,28 @@ export class IgxDaysViewComponent extends IgxCalendarBase implements DoCheck {
509604
return !!day && day.isCurrentMonth && !day.isHidden && !day.isDisabled && !day.isOutOfRange;
510605
}
511606

607+
/**
608+
* @hidden
609+
*/
610+
private isFirstLastInMonth(checkLast: boolean, date: ICalendarDate): boolean {
611+
const inc = checkLast ? 1 : -1;
612+
if (date.isCurrentMonth && !this.isSingleSelection && this.isWithinRange(date.date, true)) {
613+
const nextDay = new Date(date.date);
614+
nextDay.setDate(nextDay.getDate() + inc);
615+
if (this.isWithinRange(nextDay, false) && date.date.getMonth() + inc === nextDay.getMonth()) {
616+
return true;
617+
}
618+
}
619+
return false;
620+
}
621+
622+
/**
623+
* @hidden
624+
*/
625+
private get isSingleSelection(): boolean {
626+
return this.selection !== CalendarSelection.RANGE;
627+
}
628+
512629
/**
513630
* @hidden
514631
*/

0 commit comments

Comments
 (0)