Skip to content

Commit 194a4cf

Browse files
hanastasovzdrawku
authored andcommitted
Calendar multi view mode (#5666)
* feat(calendar): calendar multi view #4282
1 parent cb16744 commit 194a4cf

18 files changed

+719
-201
lines changed

projects/igniteui-angular/src/lib/calendar/README.md

+16-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ A multiple selection calendar with different locale and templating for the subhe
5555
</igx-calendar>
5656
```
5757

58+
A calendar displaying more than one month in the view and hiding the days that are outside of the current month
59+
```html
60+
<igx-calendar monthsViewNumber="2" [hideOutsideDays]="'true'">
61+
</igx-calendar>
62+
```
63+
5864
The **igxCalendar** implements the `ControlValueAccessor` interface, providing two-way data-binding
5965
and the expected behavior when used both in Template-driven or Reactive Forms.
6066

@@ -65,8 +71,8 @@ When the **igxCalendar** component is focused:
6571
- `PageDown` will move to the next month.
6672
- `Shift + PageUp` will move to the previous year.
6773
- `Shift + PageDown` will move to the next year.
68-
- `Home` will focus the first day of the current month that is into view.
69-
- `End` will focus the last day of the current month that is into view.
74+
- `Home` will focus the first day of the current month (or first month if more months are displayed) hat is into view.
75+
- `End` will focus the last day of the current month ((or last month if more months are displayed)) that is into view.
7076
- `Tab` will navigate through the subheader buttons;
7177

7278
When `prev` or `next` month buttons (in the subheader) are focused:
@@ -82,6 +88,7 @@ When a day inside the current month is focused:
8288
- Arrow keys will navigate through the days.
8389
- Arrow keys will allow navigation to previous/next month as well.
8490
- `Enter` will select the currently focused day.
91+
- When more than one month view is displayed, navigating with the arrow keys should move to next/previous month after navigating from first/last day in current month.
8592

8693
When a month inside the months view is focused:
8794
- Arrow keys will navigate through the months.
@@ -154,6 +161,13 @@ The default values are listed below.
154161
{ day: false, month: true, year: false }
155162
```
156163

164+
- `monthViewsNumber: number`
165+
Controls the number of month views displayed. Default is 1.
166+
167+
- `hideOusideDays: boolean`
168+
Controls the visibility of the dates that do not belong to the current month.
169+
170+
157171
### Outputs
158172

159173
- `onSelection(): Date | Date[]`

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

+27-2
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

@@ -210,6 +214,20 @@ export class IgxCalendarBase implements ControlValueAccessor {
210214
this._specialDates = value;
211215
}
212216

217+
/**
218+
* Sets/gets whether the outside dates (dates that are out of the current month) will be hidden.
219+
* Default value is `false`.
220+
* ```html
221+
* <igx-calendar [hideOutsideDays] = "true"></igx-calendar>
222+
* ```
223+
* ```typescript
224+
* let hideOutsideDays = this.calendar.hideOutsideDays;
225+
* ```
226+
*/
227+
228+
@Input()
229+
public hideOutsideDays = false;
230+
213231
/**
214232
* Emits an event when a date is selected.
215233
* Provides reference the `selectedDates` property.
@@ -379,7 +397,14 @@ export class IgxCalendarBase implements ControlValueAccessor {
379397
*/
380398
private selectMultiple(value: Date | Date[]) {
381399
if (Array.isArray(value)) {
382-
this.selectedDates = this.selectedDates.concat(value.map(v => this.getDateOnly(v)));
400+
const newDates = value.map(v => this.getDateOnly(v).getTime());
401+
const selDates = this.selectedDates.map(v => this.getDateOnly(v).getTime());
402+
403+
if (JSON.stringify(newDates) === JSON.stringify(selDates)) {
404+
return;
405+
}
406+
407+
this.selectedDates = Array.from(new Set([...newDates, ...selDates])).map(v => new Date(v));
383408
} else {
384409
const valueDateOnly = this.getDateOnly(value);
385410
const newSelection = [];
@@ -395,7 +420,7 @@ export class IgxCalendarBase implements ControlValueAccessor {
395420
this.selectedDates = this.selectedDates.concat(newSelection);
396421
}
397422
}
398-
423+
this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf());
399424
this._onChangeCallback(this.selectedDates);
400425
}
401426

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

+31-22
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
<span>{{ getFormattedDate().monthday }}</span>
44
</ng-template>
55

6-
<ng-template let-result #defaultMonth>
6+
<ng-template let-result #defaultMonth let-obj>
77
<span tabindex="0" #monthsBtn (keydown)="activeViewYearKB($event)" (click)="activeViewYear()"
88
class="igx-calendar-picker__date">
9-
{{ formattedMonth(viewDate) }}
9+
{{ formattedMonth(getViewDate(obj.index)) }}
1010
</span>
1111
<span tabindex="0" #yearsBtn (keydown)="activeViewDecadeKB($event)" (click)="activeViewDecade()"
1212
class="igx-calendar-picker__date">
13-
{{ formattedYear(viewDate) }}
13+
{{ formattedYear(getViewDate(obj.index)) }}
1414
</span>
1515
</ng-template>
1616

@@ -26,33 +26,42 @@ <h2 class="igx-calendar__header-date">
2626
(swipeleft)="nextMonth()">
2727
<div class="igx-calendar-picker">
2828
<div tabindex="0" class="igx-calendar-picker__prev" #prevMonthBtn
29-
igxCalendarScrollMonth [startScroll]="startPrevMonthScroll" [stopScroll]="stopMonthScroll">
29+
igxCalendarScrollMonth [startScroll]="startPrevMonthScroll" [stopScroll]="stopMonthScroll" [ngStyle]="{
30+
'min-width.%': 100/(monthsViewNumber*7),
31+
'left': 0
32+
}">
3033
<igx-icon fontSet="material">keyboard_arrow_left</igx-icon>
3134
</div>
32-
<div>
33-
<ng-container *ngTemplateOutlet="subheaderTemplate ? subheaderTemplate : defaultMonth; context: context">
35+
<div *ngFor="let view of dayViews; index as i;" [style.width.%]="100/monthsViewNumber">
36+
<ng-container *ngTemplateOutlet="subheaderTemplate ? subheaderTemplate : defaultMonth; context: getContext(i)">
3437
</ng-container>
3538
</div>
36-
<div tabindex="0" class="igx-calendar-picker__next" #nextMonthBtn
37-
igxCalendarScrollMonth [startScroll]="startNextMonthScroll" [stopScroll]="stopMonthScroll">
39+
<div tabindex="0" class="igx-calendar-picker__next" #nextMonthBtn
40+
igxCalendarScrollMonth [startScroll]="startNextMonthScroll" [stopScroll]="stopMonthScroll" [ngStyle]="{
41+
'min-width.%': 100/(monthsViewNumber*7),
42+
'right': 0
43+
}">
3844
<igx-icon fontSet="material">keyboard_arrow_right</igx-icon>
3945
</div>
4046
</div>
4147

42-
<igx-days-view [changeDaysView]="true" #days
43-
[animationAction]="monthAction"
44-
[locale]="locale"
45-
[value]="value"
46-
[viewDate]="viewDate"
47-
[weekStart]="weekStart"
48-
[formatOptions]="formatOptions"
49-
[formatViews]="formatViews"
50-
[selection]="selection"
51-
[disabledDates]="disabledDates"
52-
[specialDates]="specialDates"
53-
(onViewChanged)="viewChanged($event)"
54-
(onDateSelection)="childClicked($event)">
55-
</igx-days-view>
48+
<div style="display: flex">
49+
<igx-days-view *ngFor="let view of dayViews; index as i;" [changeDaysView]="true" #days
50+
[animationAction]="monthAction"
51+
[locale]="locale"
52+
[value]="value"
53+
[viewDate]="getViewDate(i)"
54+
[weekStart]="weekStart"
55+
[formatOptions]="formatOptions"
56+
[formatViews]="formatViews"
57+
[selection]="selection"
58+
[disabledDates]="disabledDates"
59+
[specialDates]="specialDates"
60+
[hideOutsideDays]="hideOutsideDays"
61+
(onViewChanged)="viewChanged($event)"
62+
(onDateSelection)="childClicked($event)">
63+
</igx-days-view>
64+
</div>
5665
</div>
5766

5867
<igx-months-view *ngIf="isYearView" [@animateView]="activeView" #months

projects/igniteui-angular/src/lib/calendar/calendar.component.spec.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,6 @@ describe('IgxCalendar', () => {
10401040

10411041
selectedDates.forEach(d => {
10421042
expect(d.selected).toBe(true);
1043-
expect(d.isSelectedCSS).toBe(true);
10441043
});
10451044

10461045
const notSelectedDates = calendar.daysView.dates.toArray().filter(d => {
@@ -1051,7 +1050,6 @@ describe('IgxCalendar', () => {
10511050

10521051
notSelectedDates.forEach(d => {
10531052
expect(d.selected).toBe(false);
1054-
expect(d.isSelectedCSS).toBe(false);
10551053
});
10561054
});
10571055
});
@@ -1070,7 +1068,6 @@ describe('IgxCalendar', () => {
10701068

10711069
specialDates.forEach(d => {
10721070
expect(d.isSpecial).toBe(true);
1073-
expect(d.isSpecialCSS).toBe(true);
10741071
});
10751072

10761073
let disabledDates = calendar.daysView.dates.toArray().filter(d => {
@@ -1098,7 +1095,6 @@ describe('IgxCalendar', () => {
10981095

10991096
specialDates.forEach(d => {
11001097
expect(d.isSpecial).toBe(true);
1101-
expect(d.isSpecialCSS).toBe(true);
11021098
});
11031099

11041100
disabledDates = calendar.daysView.dates.toArray().filter(d => {
@@ -1804,6 +1800,8 @@ describe('IgxCalendar', () => {
18041800
fixture.detectChanges();
18051801

18061802
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft');
1803+
fixture.detectChanges();
1804+
await wait(400);
18071805
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft');
18081806
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft');
18091807
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft');
@@ -1818,6 +1816,8 @@ describe('IgxCalendar', () => {
18181816
fixture.detectChanges();
18191817

18201818
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft');
1819+
fixture.detectChanges();
1820+
await wait(400);
18211821
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft');
18221822
fixture.detectChanges();
18231823
await wait(400);
@@ -1851,6 +1851,8 @@ describe('IgxCalendar', () => {
18511851
fixture.detectChanges();
18521852

18531853
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowDown');
1854+
fixture.detectChanges();
1855+
await wait(400);
18541856
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowDown');
18551857
fixture.detectChanges();
18561858
await wait(400);
@@ -1859,6 +1861,8 @@ describe('IgxCalendar', () => {
18591861
expect(date.nativeElement).toBe(document.activeElement);
18601862

18611863
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowDown');
1864+
fixture.detectChanges();
1865+
await wait(400);
18621866
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowDown');
18631867
fixture.detectChanges();
18641868
await wait(400);
@@ -1892,6 +1896,8 @@ describe('IgxCalendar', () => {
18921896
fixture.detectChanges();
18931897

18941898
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowDown');
1899+
fixture.detectChanges();
1900+
await wait(400);
18951901
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowRight');
18961902
UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowRight');
18971903
fixture.detectChanges();
@@ -2060,7 +2066,6 @@ class DateTester {
20602066
static testDatesSpeciality(dates: IgxDayItemComponent[], special: boolean): void {
20612067
for (const date of dates) {
20622068
expect(date.isSpecial).toBe(special);
2063-
expect(date.isSpecialCSS).toBe(special);
20642069
}
20652070
}
20662071
}

0 commit comments

Comments
 (0)