From 22896eb01511fb6303d82faba058f7e16a29d911 Mon Sep 17 00:00:00 2001 From: Stefana Andreeva Date: Fri, 15 Feb 2019 10:21:37 +0200 Subject: [PATCH] refactor(month picker): and fix KB navigation performance in IE #3126 --- .../src/lib/calendar/calendar-base.ts | 567 ++++++++++++++ .../src/lib/calendar/calendar.component.html | 2 +- .../lib/calendar/calendar.component.spec.ts | 68 +- .../src/lib/calendar/calendar.component.ts | 189 +---- .../src/lib/calendar/calendar.directives.ts | 3 + .../src/lib/calendar/calendar.module.ts | 6 +- .../src/lib/calendar/calendar.ts | 7 + .../days-view/day-item.component.html | 4 +- .../calendar/days-view/day-item.component.ts | 2 +- .../calendar/days-view/days-view.component.ts | 728 ++---------------- .../src/lib/calendar/index.ts | 1 + .../src/lib/calendar/month-picker-base.ts | 185 +++++ .../lib/{ => calendar}/month-picker/README.md | 19 +- .../month-picker/month-picker.component.html | 0 .../month-picker.component.spec.ts | 30 +- .../month-picker/month-picker.component.ts | 45 +- .../months-view/months-view.component.html | 2 +- .../months-view/months-view.component.ts | 44 +- .../years-view/years-view.component.html | 2 +- .../years-view/years-view.component.ts | 14 +- .../calendar/_calendar-component.scss | 5 + .../components/calendar/_calendar-theme.scss | 6 +- projects/igniteui-angular/src/public_api.ts | 1 - .../calendar-views/calendar-views.sample.html | 1 + .../calendar-views/calendar-views.sample.ts | 14 +- src/app/shared/shared.module.ts | 2 - 26 files changed, 969 insertions(+), 978 deletions(-) create mode 100644 projects/igniteui-angular/src/lib/calendar/calendar-base.ts create mode 100644 projects/igniteui-angular/src/lib/calendar/month-picker-base.ts rename projects/igniteui-angular/src/lib/{ => calendar}/month-picker/README.md (86%) rename projects/igniteui-angular/src/lib/{ => calendar}/month-picker/month-picker.component.html (100%) rename projects/igniteui-angular/src/lib/{ => calendar}/month-picker/month-picker.component.spec.ts (90%) rename projects/igniteui-angular/src/lib/{ => calendar}/month-picker/month-picker.component.ts (83%) diff --git a/projects/igniteui-angular/src/lib/calendar/calendar-base.ts b/projects/igniteui-angular/src/lib/calendar/calendar-base.ts new file mode 100644 index 00000000000..4c78d1ed2a5 --- /dev/null +++ b/projects/igniteui-angular/src/lib/calendar/calendar-base.ts @@ -0,0 +1,567 @@ +import { Input, Output, EventEmitter } from '@angular/core'; +import { WEEKDAYS, Calendar, isDateInRanges, IFormattingOptions } from './calendar'; +import { ControlValueAccessor } from '@angular/forms'; +import { DateRangeDescriptor } from '../core/dates'; + + +/** + * Sets the selction type - single, multi or range. + */ +export enum CalendarSelection { + SINGLE = 'single', + MULTI = 'multi', + RANGE = 'range' +} + +export class IgxCalendarBase implements ControlValueAccessor { + /** + * Gets the start day of the week. + * Can return a numeric or an enum representation of the week day. + * Defaults to `Sunday` / `0`. + */ + @Input() + public get weekStart(): WEEKDAYS | number { + return this.calendarModel.firstWeekDay; + } + + /** + * Sets the start day of the week. + * Can be assigned to a numeric value or to `WEEKDAYS` enum value. + */ + public set weekStart(value: WEEKDAYS | number) { + this.calendarModel.firstWeekDay = value; + } + + /** + * Gets the `locale` of the calendar. + * Default value is `"en"`. + */ + @Input() + public get locale(): string { + return this._locale; + } + + /** + * Sets the `locale` of the calendar. + * Expects a valid BCP 47 language tag. + * Default value is `"en"`. + */ + public set locale(value: string) { + this._locale = value; + this.initFormatters(); + } + + /** + * Gets the date format options of the days view. + */ + @Input() + public get formatOptions(): IFormattingOptions { + return this._formatOptions; + } + + /** + * Sets the date format options of the days view. + * Default is { day: 'numeric', month: 'short', weekday: 'short', year: 'numeric' } + */ + public set formatOptions(formatOptions: IFormattingOptions) { + this._formatOptions = Object.assign(this._formatOptions, formatOptions); + this.initFormatters(); + } + + /** + * + * Gets the selection type. + * Default value is `"single"`. + * Changing the type of selection resets the currently + * selected values if any. + */ + @Input() + public get selection(): string { + return this._selection; + } + + /** + * Sets the selection. + */ + public set selection(value: string) { + switch (value) { + case 'single': + this.selectedDates = null; + break; + case 'multi': + case 'range': + this.selectedDates = []; + break; + default: + throw new Error('Invalid selection value'); + } + this._onChangeCallback(this.selectedDates); + this.rangeStarted = false; + this._selection = value; + } + + /** + * Gets the selected date(s). + * + * When selection is set to `single`, it returns + * a single `Date` object. + * Otherwise it is an array of `Date` objects. + */ + @Input() + public get value(): Date | Date[] { + return this.selectedDates; + } + + /** + * Sets the selected date(s). + * + * When selection is set to `single`, it accepts + * a single `Date` object. + * Otherwise it is an array of `Date` objects. + */ + public set value(value: Date | Date[]) { + this.selectDate(value); + } + + /** + * Gets the date that is presented. + * By default it is the current date. + */ + @Input() + public get viewDate(): Date { + return this._viewDate; + } + + /** + * Sets the date that will be presented in the default view when the component renders. + */ + public set viewDate(value: Date) { + this._viewDate = this.getDateOnly(value); + } + + /** + * Gets the disabled dates descriptors. + */ + @Input() + public get disabledDates(): DateRangeDescriptor[] { + return this._disabledDates; + } + + /** + * Sets the disabled dates' descriptors. + * ```typescript + *@ViewChild("MyCalendar") + *public calendar: IgxCalendarComponent; + *ngOnInit(){ + * this.calendar.disabledDates = [ + * {type: DateRangeType.Between, dateRange: [new Date("2020-1-1"), new Date("2020-1-15")]}, + * {type: DateRangeType.Weekends}]; + *} + *``` + */ + public set disabledDates(value: DateRangeDescriptor[]) { + this._disabledDates = value; + } + + /** + * Gets the special dates descriptors. + */ + @Input() + public get specialDates(): DateRangeDescriptor[] { + return this._specialDates; + } + + /** + * Sets the special dates' descriptors. + * ```typescript + *@ViewChild("MyCalendar") + *public calendar: IgxCalendarComponent; + *ngOnInit(){ + * this.calendar.specialDates = [ + * {type: DateRangeType.Between, dateRange: [new Date("2020-1-1"), new Date("2020-1-15")]}, + * {type: DateRangeType.Weekends}]; + *} + *``` + */ + public set specialDates(value: DateRangeDescriptor[]) { + this._specialDates = value; + } + + /** + * Emits an event when a date is selected. + * Provides reference the `selectedDates` property. + */ + @Output() + public onSelection = new EventEmitter(); + + /** + *@hidden + */ + private _selection: CalendarSelection | string = CalendarSelection.SINGLE; + + /** + *@hidden + */ + private rangeStarted = false; + + /** + *@hidden + */ + private _locale = 'en'; + + /** + *@hidden + */ + private _viewDate: Date; + + /** + *@hidden + */ + private _disabledDates: DateRangeDescriptor[] = null; + + /** + *@hidden + */ + private _specialDates: DateRangeDescriptor[] = null; + + /** + *@hidden + */ + private _formatOptions = { + day: 'numeric', + month: 'short', + weekday: 'short', + year: 'numeric' + }; + + /** + *@hidden + */ + protected formatterWeekday; + + /** + *@hidden + */ + protected formatterDay; + + /** + *@hidden + */ + protected formatterMonth; + + /** + *@hidden + */ + protected formatterYear; + + /** + *@hidden + */ + protected formatterMonthday; + + /** + *@hidden + */ + public calendarModel: Calendar; + + /** + *@hidden + */ + public selectedDates; + + /** + *@hidden + */ + protected _onTouchedCallback: () => void = () => { }; + /** + *@hidden + */ + protected _onChangeCallback: (_: Date) => void = () => { }; + + /** + * @hidden + */ + constructor() { + this.calendarModel = new Calendar(); + + this.viewDate = this.viewDate ? this.viewDate : new Date(); + + this.calendarModel.firstWeekDay = this.weekStart; + this.initFormatters(); + } + + /** + *@hidden + */ + private getDateOnlyInMs(date: Date) { + return this.getDateOnly(date).getTime(); + } + + /** + *@hidden + */ + private generateDateRange(start: Date, end: Date): Date[] { + const result = []; + start = this.getDateOnly(start); + end = this.getDateOnly(end); + while (start.getTime() !== end.getTime()) { + start = this.calendarModel.timedelta(start, 'day', 1); + result.push(start); + } + + return result; + } + + /** + * Performs a single selection. + * @hidden + */ + private selectSingle(value: Date) { + this.selectedDates = this.getDateOnly(value); + this._onChangeCallback(this.selectedDates); + } + + /** + * Performs a multiple selection + * @hidden + */ + private selectMultiple(value: Date | Date[]) { + if (Array.isArray(value)) { + this.selectedDates = this.selectedDates.concat(value.map(v => this.getDateOnly(v))); + } else { + const valueDateOnly = this.getDateOnly(value); + const newSelection = []; + if (this.selectedDates.every((date: Date) => date.getTime() !== valueDateOnly.getTime())) { + newSelection.push(valueDateOnly); + } else { + this.selectedDates = this.selectedDates.filter( + (date: Date) => date.getTime() !== valueDateOnly.getTime() + ); + } + + if (newSelection.length > 0) { + this.selectedDates = this.selectedDates.concat(newSelection); + } + } + + this._onChangeCallback(this.selectedDates); + } + + /** + *@hidden + */ + private selectRange(value: Date | Date[], excludeDisabledDates: boolean = false) { + let start: Date; + let end: Date; + + if (Array.isArray(value)) { + // this.rangeStarted = false; + value.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); + start = this.getDateOnly(value[0]); + end = this.getDateOnly(value[value.length - 1]); + this.selectedDates = [start, ...this.generateDateRange(start, end)]; + } else { + if (!this.rangeStarted) { + this.rangeStarted = true; + this.selectedDates = [value]; + } else { + this.rangeStarted = false; + + if (this.selectedDates[0].getTime() === value.getTime()) { + this.selectedDates = []; + this._onChangeCallback(this.selectedDates); + return; + } + + this.selectedDates.push(value); + this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); + + start = this.selectedDates.shift(); + end = this.selectedDates.pop(); + this.selectedDates = [start, ...this.generateDateRange(start, end)]; + } + } + + if (excludeDisabledDates) { + this.selectedDates = this.selectedDates.filter(d => !this.isDateDisabled(d)); + } + + this._onChangeCallback(this.selectedDates); + } + + /** + * Performs a single deselection. + * @hidden + */ + private deselectSingle(value: Date) { + if (this.selectedDates !== null && + this.getDateOnlyInMs(value as Date) === this.getDateOnlyInMs(this.selectedDates)) { + this.selectedDates = null; + this._onChangeCallback(this.selectedDates); + } + } + + /** + * Performs a multiple deselection. + * @hidden + */ + private deselectMultiple(value: Date[]) { + value = value.filter(v => v !== null); + const selectedDatesCount = this.selectedDates.length; + const datesInMsToDeselect: Set = new Set( + value.map(v => this.getDateOnlyInMs(v))); + + for (let i = this.selectedDates.length - 1; i >= 0; i--) { + if (datesInMsToDeselect.has(this.getDateOnlyInMs(this.selectedDates[i]))) { + this.selectedDates.splice(i, 1); + } + } + + if (this.selectedDates.length !== selectedDatesCount) { + this._onChangeCallback(this.selectedDates); + } + } + + /** + * Performs a range deselection. + * @hidden + */ + private deselectRange(value: Date[]) { + value = value.filter(v => v !== null); + if (value.length < 1) { + return; + } + + value.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); + const valueStart = this.getDateOnlyInMs(value[0]); + const valueEnd = this.getDateOnlyInMs(value[value.length - 1]); + + this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); + const selectedDatesStart = this.getDateOnlyInMs(this.selectedDates[0]); + const selectedDatesEnd = this.getDateOnlyInMs(this.selectedDates[this.selectedDates.length - 1]); + + if (!(valueEnd < selectedDatesStart) && !(valueStart > selectedDatesEnd)) { + this.selectedDates = []; + this.rangeStarted = false; + this._onChangeCallback(this.selectedDates); + } + } + + /** + * @hidden + */ + protected initFormatters() { + this.formatterDay = new Intl.DateTimeFormat(this._locale, { day: this._formatOptions.day }); + this.formatterWeekday = new Intl.DateTimeFormat(this._locale, { weekday: this._formatOptions.weekday }); + this.formatterMonth = new Intl.DateTimeFormat(this._locale, { month: this._formatOptions.month }); + this.formatterYear = new Intl.DateTimeFormat(this._locale, { year: this._formatOptions.year }); + this.formatterMonthday = new Intl.DateTimeFormat(this._locale, { month: this._formatOptions.month, day: this._formatOptions.day }); + } + + /** + *@hidden + */ + protected getDateOnly(date: Date) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()); + } + + /** + * @hidden + */ + public registerOnChange(fn: (v: Date) => void) { + this._onChangeCallback = fn; + } + + /** + * @hidden + */ + public registerOnTouched(fn: () => void) { + this._onTouchedCallback = fn; + } + + /** + * @hidden + */ + public writeValue(value: Date | Date[]) { + this.selectedDates = value; + } + + /** + * Checks whether a date is disabled. + * @hidden + */ + public isDateDisabled(date: Date) { + if (this.disabledDates === null) { + return false; + } + + return isDateInRanges(date, this.disabledDates); + } + + /** + * Selects date(s) (based on the selection type). + */ + public selectDate(value: Date | Date[]) { + if (value === null || value === undefined || (Array.isArray(value) && value.length === 0)) { + return new Date(); + } + + switch (this.selection) { + case 'single': + this.selectSingle(value as Date); + break; + case 'multi': + this.selectMultiple(value); + break; + case 'range': + this.selectRange(value, true); + break; + } + } + + /** + * Deselects date(s) (based on the selection type). + */ + public deselectDate(value?: Date | Date[]) { + if (this.selectedDates === null || this.selectedDates === []) { + return; + } + + if (value === null || value === undefined) { + this.selectedDates = this.selection === 'single' ? null : []; + this.rangeStarted = false; + this._onChangeCallback(this.selectedDates); + return; + } + + switch (this.selection) { + case 'single': + this.deselectSingle(value as Date); + break; + case 'multi': + this.deselectMultiple(value as Date[]); + break; + case 'range': + this.deselectRange(value as Date[]); + break; + } + } + + /** + * @hidden + */ + public selectDateFromClient(value: Date) { + switch (this.selection) { + case 'single': + case 'multi': + if (!this.isDateDisabled(value)) { + this.selectDate(value); + } + + break; + case 'range': + this.selectRange(value, true); + break; + } + } +} diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.component.html b/projects/igniteui-angular/src/lib/calendar/calendar.component.html index ead1b7594e9..86640be1e9b 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.component.html +++ b/projects/igniteui-angular/src/lib/calendar/calendar.component.html @@ -38,9 +38,9 @@

[animationAction]="monthAction" [locale]="locale" [value]="value" + [viewDate]="viewDate" [weekStart]="weekStart" [formatOptions]="formatOptions" - [viewDate]="viewDate" [selection]="selection" [disabledDates]="disabledDates" [specialDates]="specialDates" diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.component.spec.ts b/projects/igniteui-angular/src/lib/calendar/calendar.component.spec.ts index ffcdbb4c4f3..1b6372ab36c 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.component.spec.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.component.spec.ts @@ -403,7 +403,8 @@ describe('IgxCalendar', () => { const target = dom.query(By.css('.igx-calendar__date--selected')); const weekDiv = target.parent; - const weekDays = weekDiv.queryAll(By.css('span')); + const weekDays = weekDiv.queryAll(By.css('.igx-calendar__date')); + const nextDay = new Date(2017, 5, 14); expect((calendar.value as Date).toDateString()).toMatch( @@ -422,7 +423,7 @@ describe('IgxCalendar', () => { nextDay.toDateString() ); expect( - weekDays[3].parent.nativeElement.classList.contains( + weekDays[3].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -441,7 +442,7 @@ describe('IgxCalendar', () => { const parent = dom.query( By.css('.igx-calendar__body-row:last-child') ); - const target = parent.queryAll(By.css('span')).pop(); + const target = parent.childNodes.pop(); target.nativeElement.click(); fixture.detectChanges(); @@ -459,7 +460,7 @@ describe('IgxCalendar', () => { it('Calendar selection - outside of current month - 2', () => { fixture.detectChanges(); const parent = dom.queryAll(By.css('.igx-calendar__body-row'))[1]; - const target = parent.queryAll(By.css('span')).shift(); + const target = parent.queryAll(By.css('.igx-calendar__date--inactive'))[0]; target.nativeElement.click(); fixture.detectChanges(); @@ -479,7 +480,7 @@ describe('IgxCalendar', () => { const target = dom.query(By.css('.igx-calendar__date--selected')); const weekDiv = target.parent; - const weekDays = weekDiv.queryAll(By.css('span')); + const weekDays = weekDiv.queryAll(By.css('.igx-calendar__date')); const nextDay = new Date(2017, 5, 14); expect((calendar.value as Date).toDateString()).toMatch( @@ -493,7 +494,7 @@ describe('IgxCalendar', () => { nextDay.toDateString() ); expect( - weekDays[3].parent.nativeElement.classList.contains( + weekDays[3].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -512,7 +513,7 @@ describe('IgxCalendar', () => { const target = dom.query(By.css('.igx-calendar__date--selected')); const weekDiv = target.parent; - const weekDays = weekDiv.queryAll(By.css('span')); + const weekDays = weekDiv.queryAll(By.css('.igx-calendar__date')); calendar.selection = 'multi'; fixture.detectChanges(); @@ -537,7 +538,7 @@ describe('IgxCalendar', () => { ); weekDays.forEach((el) => { expect( - el.parent.nativeElement.classList.contains( + el.nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -563,7 +564,7 @@ describe('IgxCalendar', () => { const target = dom.query(By.css('.igx-calendar__date--selected')); const weekDiv = target.parent; - const weekDays = weekDiv.queryAll(By.css('span')); + const weekDays = weekDiv.queryAll(By.css('.igx-calendar__date')); calendar.selection = 'multi'; fixture.detectChanges(); @@ -581,7 +582,7 @@ describe('IgxCalendar', () => { lastDay.toDateString() ); expect( - weekDays[weekDays.length - 1].parent.nativeElement.classList.contains( + weekDays[weekDays.length - 1].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -596,13 +597,13 @@ describe('IgxCalendar', () => { expect((calendar.value as Date[]).length).toEqual(3); // 11th June expect( - weekDays[0].parent.nativeElement.classList.contains( + weekDays[0].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); // 12th June expect( - weekDays[1].parent.nativeElement.classList.contains( + weekDays[1].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -612,7 +613,7 @@ describe('IgxCalendar', () => { fixture.detectChanges(); const target = dom.query(By.css('.igx-calendar__date--selected')); const weekDiv = target.parent; - const weekDays = weekDiv.queryAll(By.css('span')); + const weekDays = weekDiv.queryAll(By.css('.igx-calendar__date')); calendar.selection = 'range'; fixture.detectChanges(); @@ -632,7 +633,7 @@ describe('IgxCalendar', () => { (fixture.componentInstance.model as Date[])[0].toDateString() ).toMatch(firstDay.toDateString()); expect( - weekDays[0].parent.nativeElement.classList.contains( + weekDays[0].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -646,7 +647,7 @@ describe('IgxCalendar', () => { ); expect((calendar.value as Date[]).length).toEqual(0); expect( - weekDays[0].parent.nativeElement.classList.contains( + weekDays[0].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(false); @@ -673,7 +674,7 @@ describe('IgxCalendar', () => { ).toMatch(lastDay.toDateString()); weekDays.forEach((el) => { expect( - el.parent.nativeElement.classList.contains( + el.nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -684,7 +685,7 @@ describe('IgxCalendar', () => { fixture.detectChanges(); const target = dom.query(By.css('.igx-calendar__date--selected')); const weekDiv = target.parent; - const weekDays = weekDiv.queryAll(By.css('span')); + const weekDays = weekDiv.queryAll(By.css('.igx-calendar__date')); calendar.selection = 'range'; fixture.detectChanges(); @@ -710,7 +711,7 @@ describe('IgxCalendar', () => { ).toMatch(lastDay.toDateString()); weekDays.forEach((el) => { expect( - el.parent.nativeElement.classList.contains( + el.nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -733,7 +734,7 @@ describe('IgxCalendar', () => { ).toMatch(midDay.toDateString()); for (const i of [0, 1, 2, 3]) { expect( - weekDays[i].parent.nativeElement.classList.contains( + weekDays[i].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -746,7 +747,7 @@ describe('IgxCalendar', () => { expect((calendar.value as Date[]).length).toEqual(1); expect(calendar.value[0].toDateString()).toMatch(lastDay.toDateString()); expect( - weekDays[6].parent.nativeElement.classList.contains( + weekDays[6].nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -769,7 +770,7 @@ describe('IgxCalendar', () => { ).toMatch(lastDay.toDateString()); weekDays.forEach((el) => { expect( - el.parent.nativeElement.classList.contains( + el.nativeElement.classList.contains( 'igx-calendar__date--selected' ) ).toBe(true); @@ -865,7 +866,7 @@ describe('IgxCalendar', () => { it('Calendar date should persist the focus when select date in the (next/prev) month.', async () => { const component = dom.query(By.css('.igx-calendar')); - const calendarMonth = calendar.getCalendarMonth; + const calendarMonth = calendar.daysView.getCalendarMonth; let value = calendarMonth[0][4]; UIInteractions.triggerKeyDownEvtUponElem('Home', component.nativeElement, true); @@ -1703,7 +1704,7 @@ describe('IgxCalendar', () => { fixture.detectChanges(); const months = dom.queryAll(By.css('.igx-calendar__month')); - let currentMonth = dom.query(By.css('.igx-calendar__month--current')); + const currentMonth = dom.query(By.css('.igx-calendar__month--current')); expect(months.length).toEqual(11); expect(currentMonth.nativeElement.textContent.trim()).toMatch('Jun'); @@ -1711,30 +1712,25 @@ describe('IgxCalendar', () => { UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'Home'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - expect(currentMonth.nativeElement.textContent.trim()).toMatch('Jan'); + expect(document.activeElement.textContent.trim()).toMatch('Jan'); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'End'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'End'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - expect(currentMonth.nativeElement.textContent.trim()).toMatch('Dec'); + expect(document.activeElement.textContent.trim()).toMatch('Dec'); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'ArrowLeft'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'ArrowUp'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowUp'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'ArrowRight'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowRight'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - expect(currentMonth.nativeElement.textContent.trim()).toMatch('Sep'); + expect(document.activeElement.textContent.trim()).toMatch('Sep'); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'Enter'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'Enter'); fixture.detectChanges(); expect(calendar.viewDate.getMonth()).toEqual(8); diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.component.ts b/projects/igniteui-angular/src/lib/calendar/calendar.component.ts index bb63c41b7a3..1697d6e31d6 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.component.ts @@ -9,17 +9,16 @@ import { ViewChild, ElementRef } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { fadeIn, scaleInCenter } from '../animations/main'; import { IgxCalendarHeaderTemplateDirective, IgxCalendarSubheaderTemplateDirective } from './calendar.directives'; -import { IgxDaysViewComponent, CalendarView } from './days-view/days-view.component'; -import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { IgxYearsViewComponent } from './years-view/years-view.component'; import { IgxMonthsViewComponent } from './months-view/months-view.component'; import { KEYS } from '../core/utils'; import { ICalendarDate } from './calendar'; +import { CalendarView, IgxMonthPickerBase } from './month-picker-base'; let NEXT_ID = 0; @@ -57,7 +56,7 @@ let NEXT_ID = 0; selector: 'igx-calendar', templateUrl: 'calendar.component.html' }) -export class IgxCalendarComponent extends IgxDaysViewComponent { +export class IgxCalendarComponent extends IgxMonthPickerBase { /** * Sets/gets the `id` of the calendar. * If not set, the `id` will have value `"igx-calendar-0"`. @@ -73,29 +72,6 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { @Input() public id = `igx-calendar-${NEXT_ID++}`; - /** - * Gets whether the `day`, `month` and `year` should be rendered - * according to the locale and formatOptions, if any. - * ```typescript - * let formatViews = this.calendar.formatViews; - * ``` - */ - @Input() - public get formatViews(): object { - return this._formatViews; - } - /** - * Gets whether the `day`, `month` and `year` should be rendered - * according to the locale and formatOptions, if any. - * ```html - * - * ``` - * @memberof IgxCalendarComponent - */ - public set formatViews(formatViews: object) { - this._formatViews = Object.assign(this._formatViews, formatViews); - } - /** * Sets/gets whether the calendar header will be in vertical position. * Default value is `false`. @@ -144,10 +120,12 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { } /** + * The default css class applied to the component. + * * @hidden */ - @ViewChild('decade', {read: IgxYearsViewComponent}) - public dacadeView: IgxYearsViewComponent; + @HostBinding('class.igx-calendar') + public styleClass = true; /** * @hidden @@ -155,62 +133,17 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { @ViewChild('months', {read: IgxMonthsViewComponent}) public monthsView: IgxMonthsViewComponent; - /** - * @hidden - */ - @ViewChild('days', {read: IgxDaysViewComponent}) - public daysView: IgxDaysViewComponent; - /** * @hidden */ @ViewChild('monthsBtn') public monthsBtn: ElementRef; - /** - * @hidden - */ - @ViewChild('yearsBtn') - public yearsBtn: ElementRef; - - /** - * @hidden - */ - get isDefaultView(): boolean { - return this._activeView === CalendarView.DEFAULT; - } - /** * @hidden */ get isYearView(): boolean { - return this._activeView === CalendarView.YEAR; - } - - /** - * @hidden - */ - get isDecadeView(): boolean { - return this._activeView === CalendarView.DECADE; - } - - /** - * Gets the current active view of the calendar. - * ```typescript - * let activeView = this.calendar.activeView; - * ``` - */ - get activeView(): CalendarView { - return this._activeView; - } - /** - * Sets the current active view of the calendar. - * ```typescript - * this.calendar.activeView = activeView; - * ``` - */ - set activeView(val: CalendarView) { - this._activeView = val; + return this.activeView === CalendarView.YEAR; } /** @@ -225,6 +158,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { set monthAction(val: string) { this._monthAction = val; } + /** * Gets the header template. * ```typescript @@ -238,6 +172,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { } return null; } + /** * Sets the header template. * ```html @@ -248,6 +183,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { set headerTemplate(directive: any) { this.headerTemplateDirective = directive; } + /** * Gets the subheader template. * ```typescript @@ -260,6 +196,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { } return null; } + /** * Sets the subheader template. * ```html @@ -314,29 +251,10 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { @ContentChild(forwardRef(() => IgxCalendarSubheaderTemplateDirective), { read: IgxCalendarSubheaderTemplateDirective }) private subheaderTemplateDirective: IgxCalendarSubheaderTemplateDirective; - /** - *@hidden - */ - private _activeView = CalendarView.DEFAULT; /** *@hidden */ private _monthAction = ''; - /** - *@hidden - */ - private _formatViews = { - day: false, - month: true, - year: false - }; - - /** - *@hidden - */ - constructor(public elementRef: ElementRef) { - super(); - } /** * Returns the locale representation of the month in the month view if enabled, @@ -345,25 +263,12 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { * @hidden */ public formattedMonth(value: Date): string { - if (this._formatViews.month) { + if (this.formatViews.month) { return this.formatterMonth.format(value); } return `${value.getMonth()}`; } - /** - * Returns the locale representation of the year in the year view if enabled, - * otherwise returns the default `Date.getFullYear()` value. - * - * @hidden - */ - public formattedYear(value: Date): string { - if (this._formatViews.year) { - return this.formatterYear.format(value); - } - return `${value.getFullYear()}`; - } - /** * @hidden */ @@ -412,20 +317,6 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { } } - /** - * @hidden - */ - public previousYear() { - this.viewDate = this.calendarModel.timedelta(this.viewDate, 'year', -1); - } - - /** - * @hidden - */ - public nextYear() { - this.viewDate = this.calendarModel.timedelta(this.viewDate, 'year', 1); - } - /** * @hidden */ @@ -462,24 +353,12 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { this.viewDate = this.calendarModel.timedelta(event, 'month', 0); } - /** - * @hidden - */ - public changeYear(event: Date) { - this.viewDate = new Date(event.getFullYear(), this.viewDate.getMonth()); - this._activeView = CalendarView.DEFAULT; - - requestAnimationFrame(() => { - this.yearsBtn.nativeElement.focus(); - }); - } - /** * @hidden */ public changeMonth(event: Date) { this.viewDate = new Date(this.viewDate.getFullYear(), event.getMonth()); - this._activeView = CalendarView.DEFAULT; + this.activeView = CalendarView.DEFAULT; requestAnimationFrame(() => { this.monthsBtn.nativeElement.focus(); @@ -490,9 +369,9 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { * @hidden */ public activeViewYear(): void { - this._activeView = CalendarView.YEAR; + this.activeView = CalendarView.YEAR; requestAnimationFrame(() => { - this.monthsView.el.nativeElement.focus(); + this.monthsView.dates.find((date) => date.isCurrentMonth).nativeElement.focus(); }); } @@ -506,26 +385,6 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { } } - /** - * @hidden - */ - public activeViewDecade(): void { - this._activeView = CalendarView.DECADE; - requestAnimationFrame(() => { - this.dacadeView.el.nativeElement.focus(); - }); - } - - /** - * @hidden - */ - public activeViewDecadeKB(event) { - if (event.key === KEYS.SPACE || event.key === KEYS.SPACE_IE || event.key === KEYS.ENTER) { - event.preventDefault(); - this.activeViewDecade(); - } - } - /** * Deselects date(s) (based on the selection type). *```typescript @@ -570,12 +429,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { */ @HostListener('keydown.shift.pageup', ['$event']) public onKeydownShiftPageUp(event: KeyboardEvent) { - event.preventDefault(); - this.previousYear(); - - if (this.daysView) { - this.daysView.isKeydownTrigger = true; - } + this.keydownPageUpHandler(event); } /** @@ -583,12 +437,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { */ @HostListener('keydown.shift.pagedown', ['$event']) public onKeydownShiftPageDown(event: KeyboardEvent) { - event.preventDefault(); - this.nextYear(); - - if (this.daysView) { - this.daysView.isKeydownTrigger = true; - } + this.keydownPageDownHandler(event); } /** @@ -620,7 +469,7 @@ export class IgxCalendarComponent extends IgxDaysViewComponent { const formatObject = { monthView: () => this.activeViewYear(), yearView: () => this.activeViewDecade(), - ...this.calendarModel.formatToParts(value, this.locale, this._formatOptions, + ...this.calendarModel.formatToParts(value, this.locale, this.formatOptions, ['era', 'year', 'month', 'day', 'weekday']) }; return { $implicit: formatObject }; diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.directives.ts b/projects/igniteui-angular/src/lib/calendar/calendar.directives.ts index fa19bd38c97..52bc4e473fc 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.directives.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.directives.ts @@ -69,6 +69,9 @@ export class IgxCalendarMonthDirective { @Output() public onMonthSelection = new EventEmitter(); + @HostBinding('attr.tabindex') + public tabindex = 0; + @HostBinding('class.igx-calendar__month') get defaultCSS(): boolean { return !this.isCurrentMonth; diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.module.ts b/projects/igniteui-angular/src/lib/calendar/calendar.module.ts index a063911a72c..80d8a9068c2 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.module.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.module.ts @@ -13,7 +13,7 @@ import { IgxMonthsViewComponent } from './months-view/months-view.component'; import { IgxYearsViewComponent } from './years-view/years-view.component'; import { IgxDaysViewComponent } from './days-view/days-view.component'; import { IgxDayItemComponent } from './days-view/day-item.component'; - +import { IgxMonthPickerComponent } from './month-picker/month-picker.component'; @NgModule({ declarations: [ @@ -25,13 +25,15 @@ import { IgxDayItemComponent } from './days-view/day-item.component'; IgxCalendarYearDirective, IgxCalendarSubheaderTemplateDirective, IgxMonthsViewComponent, - IgxYearsViewComponent + IgxYearsViewComponent, + IgxMonthPickerComponent ], exports: [ IgxCalendarComponent, IgxDaysViewComponent, IgxMonthsViewComponent, IgxYearsViewComponent, + IgxMonthPickerComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarMonthDirective, IgxCalendarYearDirective, diff --git a/projects/igniteui-angular/src/lib/calendar/calendar.ts b/projects/igniteui-angular/src/lib/calendar/calendar.ts index 5babfeb1ba0..77fd9d15ea7 100644 --- a/projects/igniteui-angular/src/lib/calendar/calendar.ts +++ b/projects/igniteui-angular/src/lib/calendar/calendar.ts @@ -128,6 +128,13 @@ export interface IFormattingOptions { year?: string; } + +export interface IFormattingViews { + day?: boolean; + month?: boolean; + year?: boolean; +} + export enum WEEKDAYS { SUNDAY = 0, MONDAY = 1, diff --git a/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.html b/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.html index b4cc5e00615..6dbc7430638 100644 --- a/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.html +++ b/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.html @@ -1,3 +1 @@ - - - + diff --git a/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.ts b/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.ts index 3981f257e09..f2f8bd26ea7 100644 --- a/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/days-view/day-item.component.ts @@ -4,7 +4,7 @@ import { DateRangeDescriptor } from '../../core/dates'; /** *@hidden - */ +*/ @Component({ selector: 'igx-day-item', templateUrl: 'day-item.component.html' diff --git a/projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.ts b/projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.ts index 6aec4f7c6c2..f4dd9956e12 100644 --- a/projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/days-view/days-view.component.ts @@ -9,33 +9,16 @@ import { HostBinding, DoCheck } from '@angular/core'; -import { ICalendarDate, Calendar, WEEKDAYS, isDateInRanges, IFormattingOptions } from '../../calendar'; +import { ICalendarDate } from '../../calendar'; import { trigger, transition, useAnimation } from '@angular/animations'; import { slideInLeft, slideInRight } from '../../animations/main'; -import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { IgxDayItemComponent } from './day-item.component'; import { DateRangeDescriptor, DateRangeType } from '../../core/dates'; +import { IgxCalendarBase } from '../calendar-base'; let NEXT_ID = 0; -/** - * Sets the calender view - days, months or years. - */ -export enum CalendarView { - DEFAULT, - YEAR, - DECADE -} - -/** - * Sets the selction type - single, multi or range. - */ -export enum CalendarSelection { - SINGLE = 'single', - MULTI = 'multi', - RANGE = 'range' -} - @Component({ providers: [ { @@ -61,7 +44,7 @@ export enum CalendarSelection { selector: 'igx-days-view', templateUrl: 'days-view.component.html' }) -export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { +export class IgxDaysViewComponent extends IgxCalendarBase implements DoCheck { /** * Sets/gets the `id` of the days view. * If not set, the `id` will have value `"igx-days-view-0"`. @@ -71,237 +54,11 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { * ```typescript * let daysViewId = this.daysView.id; * ``` - * @memberof IgxDaysViewComponent */ @HostBinding('attr.id') @Input() public id = `igx-days-view-${NEXT_ID++}`; - /** - * Gets the start day of the week. - * Can return a numeric or an enum representation of the week day. - * Defaults to `Sunday` / `0`. - * ```typescript - * let weekStart = this.calendar.weekStart; - * ``` - * @memberof IgxCalendarComponent - */ - @Input() - public get weekStart(): WEEKDAYS | number { - return this.calendarModel.firstWeekDay; - } - - /** - * Sets the start day of the week. - * Can be assigned to a numeric value or to `WEEKDAYS` enum value. - * ```html - * - * ``` - * @memberof IgxCalendarComponent - */ - public set weekStart(value: WEEKDAYS | number) { - this.calendarModel.firstWeekDay = value; - } - - /** - * Gets the `locale` of the calendar. - * Default value is `"en"`. - * ```typescript - * let locale = this.calendar.locale; - * ``` - * @memberof IgxCalendarComponent - */ - @Input() - public get locale(): string { - return this._locale; - } - - /** - * Sets the `locale` of the calendar. - * Expects a valid BCP 47 language tag. - * Default value is `"en"`. - * ```html - * - * ``` - * @memberof IgxCalendarComponent - */ - public set locale(value: string) { - this._locale = value; - this.initFormatters(); - } - - /** - * - * Gets the selection type of the calendar. - * Default value is `"single"`. - * Changing the type of selection in the calendar resets the currently - * selected values if any. - * ```typescript - * let selectionType = this.calendar.selection; - * ``` - * @memberof IgxCalendarComponent - */ - @Input() - public get selection(): string { - return this._selection; - } - - /** - * Sets the selection type of the calendar. - * ```html - * - * ``` - * @memberof IgxCalendarComponent - */ - public set selection(value: string) { - switch (value) { - case 'single': - this.selectedDates = null; - break; - case 'multi': - case 'range': - this.selectedDates = []; - break; - default: - throw new Error('Invalid selection value'); - } - this._onChangeCallback(this.selectedDates); - this.rangeStarted = false; - this._selection = value; - } - - /** - * Gets the date that is presented in the calendar. - * By default it is the current date. - * ```typescript - * let date = this.calendar.viewDate; - * ``` - * @memberof IgxCalendarComponent - */ - @Input() - public get viewDate(): Date { - return this._viewDate; - } - - /** - * Sets the date that will be presented in the default view when the calendar renders. - * ```html - * - * ``` - * @memberof IgxCalendarComponent - */ - public set viewDate(value: Date) { - this._viewDate = this.getDateOnly(value); - } - - /** - * Gets the selected date(s) of the calendar. - * - * When the calendar selection is set to `single`, it returns - * a single `Date` object. - * Otherwise it is an array of `Date` objects. - * ```typescript - * let selectedDates = this.calendar.value; - * ``` - * @memberof IgxCalendarComponent - */ - @Input() - public get value(): Date | Date[] { - return this.selectedDates; - } - - /** - * Sets the selected date(s) of the calendar. - * - * When the calendar selection is set to `single`, it accepts - * a single `Date` object. - * Otherwise it is an array of `Date` objects. - * ```typescript - * this.calendar.value = new Date(`2016-06-12`); - * ``` - * @memberof IgxCalendarComponent - */ - public set value(value: Date | Date[]) { - this.selectDate(value); - } - - /** - * Gets the date format options of the calendar. - * ```typescript - * let dateFormatOptions = this.calendar.formatOptions. - * ``` - */ - @Input() - public get formatOptions(): IFormattingOptions { - return this._formatOptions; - } - - /** - * Sets the date format options of the calendar. - * ```html - * [formatOptions] = "{ day: '2-digit', month: 'short', weekday: 'long', year: 'numeric' }" - * ``` - * @memberof IgxCalendarComponent - */ - public set formatOptions(formatOptions: IFormattingOptions) { - this._formatOptions = Object.assign(this._formatOptions, formatOptions); - this.initFormatters(); - } - - /** - * Gets the disabled dates descriptors. - * ```typescript - * let disabledDates = this.calendar.disabledDates; - * ``` - */ - @Input() - public get disabledDates(): DateRangeDescriptor[] { - return this._disabledDates; - } - - /** - * Sets the disabled dates' descriptors. - * ```typescript - *@ViewChild("MyCalendar") - *public calendar: IgCalendarComponent; - *ngOnInit(){ - * this.calendar.disabledDates = [ - * {type: DateRangeType.Between, dateRange: [new Date("2020-1-1"), new Date("2020-1-15")]}, - * {type: DateRangeType.Weekends}]; - *} - *``` - */ - public set disabledDates(value: DateRangeDescriptor[]) { - this._disabledDates = value; - } - - /** - * Gets the special dates descriptors. - * ```typescript - * let specialDates = this.calendar.specialDates; - * ``` - */ - @Input() - public get specialDates(): DateRangeDescriptor[] { - return this._specialDates; - } - - /** - * Sets the special dates' descriptors. - * ```typescript - *@ViewChild("MyCalendar") - *public calendar: IgCalendarComponent; - *ngOnInit(){ - * this.calendar.specialDates = [ - * {type: DateRangeType.Between, dateRange: [new Date("2020-1-1"), new Date("2020-1-15")]}, - * {type: DateRangeType.Weekends}]; - *} - *``` - */ - public set specialDates(value: DateRangeDescriptor[]) { - this._specialDates = value; - } - /** * @hidden */ @@ -314,17 +71,6 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @Input() public changeDaysView = false; - /** - * Emits an event when a date is selected. - * Provides reference the `selectedDates` property. - * ```html - * - * ``` - * @memberof IgxCalendarComponent - */ - @Output() - public onSelection = new EventEmitter(); - /** * @hidden */ @@ -343,86 +89,25 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @ViewChildren(IgxDayItemComponent, { read: IgxDayItemComponent }) public dates: QueryList; - - /** - *@hidden - */ - private _viewDate: Date; - /** - *@hidden - */ - private _locale = 'en'; - /** - *@hidden - */ - private _disabledDates: DateRangeDescriptor[] = null; - /** - *@hidden - */ - private _specialDates: DateRangeDescriptor[] = null; - /** - *@hidden - */ - private _selection: CalendarSelection | string = CalendarSelection.SINGLE; - /** - *@hidden - */ - private rangeStarted = false; /** * @hidden */ private _nextDate: Date; + /** * @hidden */ private callback: (dates?, next?) => void; - /** - *@hidden - */ - protected formatterWeekday; - /** - *@hidden - */ - protected formatterMonth; - /** - *@hidden - */ - protected formatterMonthday; - /** - *@hidden - */ - protected formatterYear; - /** - *@hidden - */ - protected formatterDay; - - /** - *@hidden - */ - public calendarModel: Calendar; - /** - *@hidden - */ - public _formatOptions = { - day: 'numeric', - month: 'short', - weekday: 'short', - year: 'numeric' - }; /** * @hidden */ public isKeydownTrigger = false; + /** * @hidden */ public outOfRangeDates: DateRangeDescriptor[]; - /** - *@hidden - */ - public selectedDates; /** * The default css class applied to the component. @@ -447,26 +132,6 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { return this.calendarModel.monthdatescalendar(this.viewDate.getFullYear(), this.viewDate.getMonth(), true); } - /** - *@hidden - */ - protected _onTouchedCallback: () => void = () => { }; - /** - *@hidden - */ - protected _onChangeCallback: (_: Date) => void = () => { }; - - /** - * @hidden - */ - constructor() { - this.calendarModel = new Calendar(); - - this.calendarModel.firstWeekDay = this.weekStart; - this._viewDate = this._viewDate ? this._viewDate : new Date(); - this.initFormatters(); - } - /** * @hidden */ @@ -477,180 +142,92 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { } /** - * Resets the formatters when locale or formatOptions are changed + * Returns the locale representation of the date in the days view. * * @hidden */ - private initFormatters() { - this.formatterMonth = new Intl.DateTimeFormat(this._locale, { month: this._formatOptions.month }); - this.formatterDay = new Intl.DateTimeFormat(this._locale, { day: this._formatOptions.day }); - this.formatterYear = new Intl.DateTimeFormat(this._locale, { year: this._formatOptions.year }); - this.formatterMonthday = new Intl.DateTimeFormat(this._locale, { month: this._formatOptions.month, day: this._formatOptions.day }); - this.formatterWeekday = new Intl.DateTimeFormat(this._locale, { weekday: this._formatOptions.weekday }); + public formattedDate(value: Date): string { + return this.formatterDay.format(value); } /** - *@hidden + * @hidden */ - private getDateOnly(date: Date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); + public generateWeekHeader(): string[] { + const dayNames = []; + const rv = this.calendarModel.monthdatescalendar(this.viewDate.getFullYear(), this.viewDate.getMonth())[0]; + for (const day of rv) { + dayNames.push(this.formatterWeekday.format(day.date)); + } + + return dayNames; } /** - *@hidden + * @hidden */ - private getDateOnlyInMs(date: Date) { - return this.getDateOnly(date).getTime(); + public rowTracker(index, item): string { + return `${item[index].date.getMonth()}${item[index].date.getDate()}`; } /** - *@hidden + * @hidden */ - private generateDateRange(start: Date, end: Date): Date[] { - const result = []; - start = this.getDateOnly(start); - end = this.getDateOnly(end); - while (start.getTime() !== end.getTime()) { - start = this.calendarModel.timedelta(start, 'day', 1); - result.push(start); - } - - return result; + public dateTracker(index, item): string { + return `${item.date.getMonth()}--${item.date.getDate()}`; } /** - * Performs a single selection. * @hidden */ - private selectSingle(value: Date) { - this.selectedDates = this.getDateOnly(value); - this._onChangeCallback(this.selectedDates); + public isCurrentMonth(value: Date): boolean { + return this.viewDate.getMonth() === value.getMonth(); } /** - * Performs a multiple selection * @hidden */ - private selectMultiple(value: Date | Date[]) { - if (Array.isArray(value)) { - this.selectedDates = this.selectedDates.concat(value.map(v => this.getDateOnly(v))); - } else { - const valueDateOnly = this.getDateOnly(value); - const newSelection = []; - if (this.selectedDates.every((date: Date) => date.getTime() !== valueDateOnly.getTime())) { - newSelection.push(valueDateOnly); - } else { - this.selectedDates = this.selectedDates.filter( - (date: Date) => date.getTime() !== valueDateOnly.getTime() - ); - } - - if (newSelection.length > 0) { - this.selectedDates = this.selectedDates.concat(newSelection); - } - } - - this._onChangeCallback(this.selectedDates); + public isCurrentYear(value: Date): boolean { + return this.viewDate.getFullYear() === value.getFullYear(); } /** *@hidden */ - private selectRange(value: Date | Date[], excludeDisabledDates: boolean = false) { - let start: Date; - let end: Date; - - if (Array.isArray(value)) { - // this.rangeStarted = false; - value.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); - start = this.getDateOnly(value[0]); - end = this.getDateOnly(value[value.length - 1]); - this.selectedDates = [start, ...this.generateDateRange(start, end)]; - } else { - if (!this.rangeStarted) { - this.rangeStarted = true; - this.selectedDates = [value]; - } else { - this.rangeStarted = false; - - if (this.selectedDates[0].getTime() === value.getTime()) { - this.selectedDates = []; - this._onChangeCallback(this.selectedDates); - return; - } - - this.selectedDates.push(value); - this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); + public focusActiveDate() { + let date = this.dates.find((d) => d.selected); - start = this.selectedDates.shift(); - end = this.selectedDates.pop(); - this.selectedDates = [start, ...this.generateDateRange(start, end)]; - } + if (!date) { + date = this.dates.find((d) => d.isToday); } - if (excludeDisabledDates) { - this.selectedDates = this.selectedDates.filter(d => !this.isDateDisabled(d)); + if (date) { + date.nativeElement.focus(); } - - this._onChangeCallback(this.selectedDates); } /** - * Performs a single deselection. * @hidden */ - private deselectSingle(value: Date) { - if (this.selectedDates !== null && - this.getDateOnlyInMs(value as Date) === this.getDateOnlyInMs(this.selectedDates)) { - this.selectedDates = null; - this._onChangeCallback(this.selectedDates); - } + public selectDay(event) { + this.selectDateFromClient(event.date); + this.onDateSelection.emit(event); } /** - * Performs a multiple deselection. * @hidden */ - private deselectMultiple(value: Date[]) { - value = value.filter(v => v !== null); - const selectedDatesCount = this.selectedDates.length; - const datesInMsToDeselect: Set = new Set( - value.map(v => this.getDateOnlyInMs(v))); - - for (let i = this.selectedDates.length - 1; i >= 0; i--) { - if (datesInMsToDeselect.has(this.getDateOnlyInMs(this.selectedDates[i]))) { - this.selectedDates.splice(i, 1); + public animationDone(event, isLast: boolean) { + if (isLast) { + const date = this.dates.find((d) => d.selected); + if (date && !this.isKeydownTrigger) { + setTimeout(() => { + date.nativeElement.focus(); + }, parseInt(slideInRight.options.params.duration, 10)); + } else if (this.callback && (event.toState === 'next' || event.toState === 'prev')) { + this.callback(this.dates, this._nextDate); } } - - if (this.selectedDates.length !== selectedDatesCount) { - this._onChangeCallback(this.selectedDates); - } - } - - /** - * Performs a range deselection. - * @hidden - */ - private deselectRange(value: Date[]) { - value = value.filter(v => v !== null); - if (value.length < 1) { - return; - } - - value.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); - const valueStart = this.getDateOnlyInMs(value[0]); - const valueEnd = this.getDateOnlyInMs(value[value.length - 1]); - - this.selectedDates.sort((a: Date, b: Date) => a.valueOf() - b.valueOf()); - const selectedDatesStart = this.getDateOnlyInMs(this.selectedDates[0]); - const selectedDatesEnd = this.getDateOnlyInMs(this.selectedDates[this.selectedDates.length - 1]); - - if (!(valueEnd < selectedDatesStart) && !(valueStart > selectedDatesEnd)) { - this.selectedDates = []; - this.rangeStarted = false; - this._onChangeCallback(this.selectedDates); - } } /** @@ -819,215 +396,14 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { }]; } - - /** - * @hidden - */ - public registerOnChange(fn: (v: Date) => void) { - this._onChangeCallback = fn; - } - - /** - * @hidden - */ - public registerOnTouched(fn: () => void) { - this._onTouchedCallback = fn; - } - - /** - * @hidden - */ - public writeValue(value: Date | Date[]) { - this.selectedDates = value; - } - - /** - * Checks whether a date is disabled. - *```typescript - * this.calendar.isDateDisabled(new Date(`2018-06-12`)); - *``` - * @hidden - */ - public isDateDisabled(date: Date) { - if (this.disabledDates === null) { - return false; - } - - return isDateInRanges(date, this.disabledDates); - } - - /** - * Selects date(s) (based on the selection type). - *```typescript - * this.calendar.selectDate(new Date(`2018-06-12`)); - *``` - */ - public selectDate(value: Date | Date[]) { - if (value === null || value === undefined || (Array.isArray(value) && value.length === 0)) { - return new Date(); - } - - switch (this.selection) { - case 'single': - this.selectSingle(value as Date); - break; - case 'multi': - this.selectMultiple(value); - break; - case 'range': - this.selectRange(value, true); - break; - } - } - - /** - * Deselects date(s) (based on the selection type). - *```typescript - * this.calendar.deselectDate(new Date(`2018-06-12`)); - *```` - */ - public deselectDate(value?: Date | Date[]) { - if (this.selectedDates === null || this.selectedDates === []) { - return; - } - - if (value === null || value === undefined) { - this.selectedDates = this.selection === 'single' ? null : []; - this.rangeStarted = false; - this._onChangeCallback(this.selectedDates); - return; - } - - switch (this.selection) { - case 'single': - this.deselectSingle(value as Date); - break; - case 'multi': - this.deselectMultiple(value as Date[]); - break; - case 'range': - this.deselectRange(value as Date[]); - break; - } - } - - /** - * @hidden - */ - public isCurrentMonth(value: Date): boolean { - return this.viewDate.getMonth() === value.getMonth(); - } - - /** - * @hidden - */ - public isCurrentYear(value: Date): boolean { - return this.viewDate.getFullYear() === value.getFullYear(); - } - - /** - * @hidden - */ - public selectDateFromClient(value: Date) { - switch (this.selection) { - case 'single': - case 'multi': - if (!this.isDateDisabled(value)) { - this.selectDate(value); - } - - break; - case 'range': - this.selectRange(value, true); - break; - } - } - - /** - *@hidden - */ - public focusActiveDate() { - let date = this.dates.find((d) => d.selected); - - if (!date) { - date = this.dates.find((d) => d.isToday); - } - - if (date) { - date.nativeElement.focus(); - } - } - - /** - * @hidden - */ - public generateWeekHeader(): string[] { - const dayNames = []; - const rv = this.calendarModel.monthdatescalendar(this.viewDate.getFullYear(), this.viewDate.getMonth())[0]; - for (const day of rv) { - dayNames.push(this.formatterWeekday.format(day.date)); - } - - return dayNames; - } - - /** - * Returns the locale representation of the date in the days view. - * - * @hidden - */ - public formattedDate(value: Date): string { - return this.formatterDay.format(value); - } - - /** - * @hidden - */ - public rowTracker(index, item): string { - return `${item[index].date.getMonth()}${item[index].date.getDate()}`; - } - - /** - * @hidden - */ - public dateTracker(index, item): string { - return `${item.date.getMonth()}--${item.date.getDate()}`; - } - - /** - * @hidden - */ - public selectDay(event) { - this.selectDateFromClient(event.date); - this.onDateSelection.emit(event); - - this.onSelection.emit(this.selectedDates); - } - - /** - * @hidden - */ - public animationDone(event, isLast: boolean) { - if (isLast) { - const date = this.dates.find((d) => d.selected); - if (date && !this.isKeydownTrigger) { - setTimeout(() => { - date.nativeElement.focus(); - }, parseInt(slideInRight.options.params.duration, 10)); - } else if (this.callback) { - setTimeout(() => { - this.callback(this.dates, this._nextDate); - }, parseInt(slideInRight.options.params.duration, 10)); - } - } - } - /** * @hidden */ @HostListener('keydown.arrowup', ['$event']) public onKeydownArrowUp(event: KeyboardEvent) { event.preventDefault(); + event.stopPropagation(); + this.focusPreviousUpDate(event.target); } @@ -1037,6 +413,8 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @HostListener('keydown.arrowdown', ['$event']) public onKeydownArrowDown(event: KeyboardEvent) { event.preventDefault(); + event.stopPropagation(); + this.focusNextDownDate(event.target); } @@ -1046,6 +424,8 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @HostListener('keydown.arrowleft', ['$event']) public onKeydownArrowLeft(event: KeyboardEvent) { event.preventDefault(); + event.stopPropagation(); + this.focusPreviousDate(event.target); } @@ -1055,6 +435,8 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @HostListener('keydown.arrowright', ['$event']) public onKeydownArrowRight(event: KeyboardEvent) { event.preventDefault(); + event.stopPropagation(); + this.focusNextDate(event.target); } @@ -1064,6 +446,7 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @HostListener('keydown.home', ['$event']) public onKeydownHome(event: KeyboardEvent) { event.preventDefault(); + event.stopPropagation(); const dates = this.dates.filter(d => d.isCurrentMonth); for (let i = 0; i < dates.length; i++) { @@ -1080,6 +463,7 @@ export class IgxDaysViewComponent implements ControlValueAccessor, DoCheck { @HostListener('keydown.end', ['$event']) public onKeydownEnd(event: KeyboardEvent) { event.preventDefault(); + event.stopPropagation(); const dates = this.dates.filter(d => d.isCurrentMonth); for (let i = dates.length - 1; i >= 0; i--) { diff --git a/projects/igniteui-angular/src/lib/calendar/index.ts b/projects/igniteui-angular/src/lib/calendar/index.ts index 86e4e610112..609c0e16934 100644 --- a/projects/igniteui-angular/src/lib/calendar/index.ts +++ b/projects/igniteui-angular/src/lib/calendar/index.ts @@ -3,5 +3,6 @@ export * from './calendar.component'; export * from './days-view/days-view.component'; export * from './months-view/months-view.component'; export * from './years-view/years-view.component'; +export * from './month-picker/month-picker.component'; export * from './calendar.directives'; export * from './calendar.module'; diff --git a/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts b/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts new file mode 100644 index 00000000000..ad737978099 --- /dev/null +++ b/projects/igniteui-angular/src/lib/calendar/month-picker-base.ts @@ -0,0 +1,185 @@ +import { IgxCalendarBase } from './calendar-base'; +import { ViewChild, ElementRef, Input, HostBinding } from '@angular/core'; +import { IgxYearsViewComponent } from './years-view/years-view.component'; +import { IgxDaysViewComponent } from './days-view/days-view.component'; +import { IFormattingViews } from './calendar'; +import { KEYS } from '../core/utils'; + +/** + * Sets the calender view - days, months or years. + */ +export enum CalendarView { + DEFAULT, + YEAR, + DECADE +} +export class IgxMonthPickerBase extends IgxCalendarBase { + + /** + * Gets whether the `day`, `month` and `year` should be rendered + * according to the locale and formatOptions, if any. + */ + @Input() + public get formatViews(): IFormattingViews { + return this._formatViews; + } + + /** + * Gets whether the `day`, `month` and `year` should be rendered + * according to the locale and formatOptions, if any. + */ + public set formatViews(formatViews: IFormattingViews) { + this._formatViews = Object.assign(this._formatViews, formatViews); + } + + /** + * @hidden + */ + @ViewChild('yearsBtn') + public yearsBtn: ElementRef; + + /** + * @hidden + */ + @ViewChild('days', {read: IgxDaysViewComponent}) + public daysView: IgxDaysViewComponent; + + /** + * @hidden + */ + @ViewChild('decade', { read: IgxYearsViewComponent }) + public dacadeView: IgxYearsViewComponent; + + /** + * The default `tabindex` attribute for the component. + * + * @hidden + */ + @HostBinding('attr.tabindex') + public tabindex = 0; + + /** + * Gets the current active view. + */ + get activeView(): CalendarView { + return this._activeView; + } + + /** + * Sets the current active view. + */ + set activeView(val: CalendarView) { + this._activeView = val; + } + + /** + * @hidden + */ + get isDefaultView(): boolean { + return this._activeView === CalendarView.DEFAULT; + } + + /** + * @hidden + */ + get isDecadeView(): boolean { + return this._activeView === CalendarView.DECADE; + } + + /** + *@hidden + */ + private _activeView = CalendarView.DEFAULT; + + /** + *@hidden + */ + private _formatViews: IFormattingViews = { + day: false, + month: true, + year: false + }; + + /** + * @hidden + */ + public changeYear(event: Date) { + this.viewDate = new Date(event.getFullYear(), this.viewDate.getMonth()); + this._activeView = CalendarView.DEFAULT; + + requestAnimationFrame(() => { + this.yearsBtn.nativeElement.focus(); + }); + } + + /** + * @hidden + */ + public activeViewDecade(): void { + this._activeView = CalendarView.DECADE; + requestAnimationFrame(() => { + this.dacadeView.el.nativeElement.focus(); + }); + } + + /** + * @hidden + */ + public activeViewDecadeKB(event) { + if (event.key === KEYS.SPACE || event.key === KEYS.SPACE_IE || event.key === KEYS.ENTER) { + event.preventDefault(); + this.activeViewDecade(); + } + } + + /** + * @hidden + */ + public previousYear() { + this.viewDate = this.calendarModel.timedelta(this.viewDate, 'year', -1); + } + + /** + * @hidden + */ + public nextYear() { + this.viewDate = this.calendarModel.timedelta(this.viewDate, 'year', 1); + } + + /** + * Returns the locale representation of the year in the year view if enabled, + * otherwise returns the default `Date.getFullYear()` value. + * + * @hidden + */ + public formattedYear(value: Date): string { + if (this._formatViews.year) { + return this.formatterYear.format(value); + } + return `${value.getFullYear()}`; + } + + /** + * @hidden + */ + public keydownPageUpHandler(event: KeyboardEvent) { + event.preventDefault(); + this.previousYear(); + + if (this.daysView) { + this.daysView.isKeydownTrigger = true; + } + } + + /** + * @hidden + */ + public keydownPageDownHandler(event: KeyboardEvent) { + event.preventDefault(); + this.nextYear(); + + if (this.daysView) { + this.daysView.isKeydownTrigger = true; + } + } +} diff --git a/projects/igniteui-angular/src/lib/month-picker/README.md b/projects/igniteui-angular/src/lib/calendar/month-picker/README.md similarity index 86% rename from projects/igniteui-angular/src/lib/month-picker/README.md rename to projects/igniteui-angular/src/lib/calendar/month-picker/README.md index d6d3db63240..afc9b262096 100644 --- a/projects/igniteui-angular/src/lib/month-picker/README.md +++ b/projects/igniteui-angular/src/lib/calendar/month-picker/README.md @@ -1,6 +1,6 @@ # igxMonthPicker Component -The **igxMonthPicker** provides a way for the user to select date(s). +The **igxMonthPicker** provides a way for the user to select a month. ## Dependencies @@ -29,7 +29,7 @@ import { IgxMonthPickerComponent } from "igniteui-angular"; Instantiate a month picker component and pass a date object. ```html - + ``` The **igxMonthPicker** implements the `ControlValueAccessor` interface, providing two-way data-binding @@ -38,18 +38,16 @@ and the expected behavior when used both in Template-driven or Reactive Forms. ``` -Customize the format and set the locale +Customize the format, set the views to be formatted and the locale ```typescript - public formatOptions = { - month: 'long', - year: 'numeric' - }; + public formatViews = { month: true, year: true }; + public formatOptions = { month: 'long', year: 'numeric' }; public localeDe = 'de'; ``` ```html - + ``` ### Keyboard navigation @@ -71,6 +69,7 @@ When a month inside the months view is focused: - `Home` will focus the first month inside the months view. - `End` will focus the last month inside the months view. - `Enter` will select the currently focused month and close the view. +- `Tab` will navigate through the months; ## API Summary @@ -103,7 +102,7 @@ for additional information on the available options. The defaul values are listed below. ```typescript -{ day: 'numeric', month: 'short', weekday: 'short', year: 'numeric' } +{ month: 'short', year: 'numeric' } ``` - `formatViews: Object` @@ -113,7 +112,7 @@ Controls whether the date parts in the different month picker views should be fo The default values are listed below. ```typescript -{ day: false, month: true, year: false } +{ month: true, year: false } ``` ### Outputs diff --git a/projects/igniteui-angular/src/lib/month-picker/month-picker.component.html b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.html similarity index 100% rename from projects/igniteui-angular/src/lib/month-picker/month-picker.component.html rename to projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.html diff --git a/projects/igniteui-angular/src/lib/month-picker/month-picker.component.spec.ts b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.spec.ts similarity index 90% rename from projects/igniteui-angular/src/lib/month-picker/month-picker.component.spec.ts rename to projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.spec.ts index 96a9352921c..a8b902c2d50 100644 --- a/projects/igniteui-angular/src/lib/month-picker/month-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.spec.ts @@ -3,9 +3,10 @@ import { TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { UIInteractions } from '../test-utils/ui-interactions.spec'; -import { configureTestSuite } from '../test-utils/configure-suite'; -import { IgxMonthPickerComponent, IgxMonthPickerModule } from './month-picker.component'; +import { UIInteractions } from '../../test-utils/ui-interactions.spec'; +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { IgxMonthPickerComponent } from './month-picker.component'; +import { IgxCalendarModule } from '../calendar.module'; describe('IgxMonthPicker', () => { configureTestSuite(); @@ -13,7 +14,7 @@ describe('IgxMonthPicker', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [IgxMonthPickerSampleComponent], - imports: [IgxMonthPickerModule, FormsModule, NoopAnimationsModule] + imports: [FormsModule, NoopAnimationsModule, IgxCalendarModule] }).compileComponents(); }); @@ -307,7 +308,7 @@ describe('IgxMonthPicker', () => { const monthPicker = fixture.componentInstance.monthPicker; const months = dom.queryAll(By.css('.igx-calendar__month')); - let currentMonth = dom.query(By.css('.igx-calendar__month--current')); + const currentMonth = dom.query(By.css('.igx-calendar__month--current')); expect(months.length).toEqual(11); expect(currentMonth.nativeElement.textContent.trim()).toMatch('Feb'); @@ -315,30 +316,25 @@ describe('IgxMonthPicker', () => { UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'Home'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - expect(currentMonth.nativeElement.textContent.trim()).toMatch('Jan'); + expect(document.activeElement.textContent.trim()).toMatch('Jan'); UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'End'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - expect(currentMonth.nativeElement.textContent.trim()).toMatch('Dec'); + expect(document.activeElement.textContent.trim()).toMatch('Dec'); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'ArrowLeft'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowLeft'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'ArrowUp'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowUp'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'ArrowRight'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'ArrowRight'); fixture.detectChanges(); - currentMonth = dom.query(By.css('.igx-calendar__month--current')); - expect(currentMonth.nativeElement.textContent.trim()).toMatch('Sep'); + expect(document.activeElement.textContent.trim()).toMatch('Sep'); - UIInteractions.simulateKeyDownEvent(currentMonth.nativeElement, 'Enter'); + UIInteractions.simulateKeyDownEvent(document.activeElement, 'Enter'); fixture.detectChanges(); expect(monthPicker.viewDate.getMonth()).toEqual(8); diff --git a/projects/igniteui-angular/src/lib/month-picker/month-picker.component.ts b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts similarity index 83% rename from projects/igniteui-angular/src/lib/month-picker/month-picker.component.ts rename to projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts index 107fb9687cb..bd00159e0e2 100644 --- a/projects/igniteui-angular/src/lib/month-picker/month-picker.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/month-picker/month-picker.component.ts @@ -1,19 +1,16 @@ import { Component, - NgModule, HostListener, - ElementRef, ViewChild, HostBinding, Input } from '@angular/core'; -import { IgxCalendarModule, CalendarView, IgxCalendarComponent, IgxMonthsViewComponent } from '../calendar/index'; -import { IgxIconModule } from '../icon/index'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { CommonModule } from '@angular/common'; import { trigger, transition, useAnimation } from '@angular/animations'; -import { fadeIn, scaleInCenter, slideInLeft, slideInRight } from '../animations/main'; -import { KEYS } from '../core/utils'; +import { fadeIn, scaleInCenter, slideInLeft, slideInRight } from '../../animations/main'; +import { KEYS } from '../../core/utils'; +import { IgxMonthsViewComponent } from '../months-view/months-view.component'; +import { IgxMonthPickerBase, CalendarView } from '../month-picker-base'; let NEXT_ID = 0; @Component({ @@ -50,7 +47,7 @@ let NEXT_ID = 0; selector: 'igx-month-picker', templateUrl: 'month-picker.component.html' }) -export class IgxMonthPickerComponent extends IgxCalendarComponent { +export class IgxMonthPickerComponent extends IgxMonthPickerBase { /** * Sets/gets the `id` of the month picker. * If not set, the `id` will have value `"igx-month-picker-0"`. @@ -60,21 +57,23 @@ export class IgxMonthPickerComponent extends IgxCalendarComponent { public id = `igx-month-picker-${NEXT_ID++}`; /** + * The default css class applied to the component. + * * @hidden */ - public yearAction = ''; + @HostBinding('class.igx-calendar') + public styleClass = true; /** * @hidden */ - @ViewChild('yearsBtn') - public yearsBtn: ElementRef; + @ViewChild('months', {read: IgxMonthsViewComponent}) + public monthsView: IgxMonthsViewComponent; /** * @hidden */ - @ViewChild('months', {read: IgxMonthsViewComponent}) - public monthsView: IgxMonthsViewComponent; + public yearAction = ''; /** * @hidden @@ -177,7 +176,7 @@ export class IgxMonthPickerComponent extends IgxCalendarComponent { */ @HostListener('keydown.pageup', ['$event']) public onKeydownPageUp(event: KeyboardEvent) { - super.onKeydownShiftPageUp(event); + this.keydownPageUpHandler(event); } /** @@ -185,16 +184,7 @@ export class IgxMonthPickerComponent extends IgxCalendarComponent { */ @HostListener('keydown.pagedown', ['$event']) public onKeydownPageDown(event: KeyboardEvent) { - super.onKeydownShiftPageDown(event); - } - - /** - * @hidden - */ - @HostListener('keydown.shift.pageup', ['$event']) - @HostListener('keydown.shift.pagedown', ['$event']) - public onKeydownShiftPageDownUp(event: KeyboardEvent) { - event.stopPropagation(); + this.keydownPageDownHandler(event); } /** @@ -219,10 +209,3 @@ export class IgxMonthPickerComponent extends IgxCalendarComponent { } } } - -@NgModule({ - declarations: [IgxMonthPickerComponent], - exports: [IgxMonthPickerComponent], - imports: [CommonModule, IgxIconModule, IgxCalendarModule] -}) -export class IgxMonthPickerModule { } diff --git a/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.html b/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.html index dfc19af574d..f806ff9b403 100644 --- a/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.html +++ b/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.html @@ -1,6 +1,6 @@
-
+
{{ formattedMonth(month) | titlecase }}
diff --git a/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.ts b/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.ts index eef7b559751..0d435a827b0 100644 --- a/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/months-view/months-view.component.ts @@ -159,14 +159,17 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { *@hidden */ private _formatterMonth: any; + /** *@hidden */ private _locale = 'en'; + /** *@hidden */ private _monthFormat = 'short'; + /** *@hidden */ @@ -228,6 +231,13 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { } } + /** + * @hidden + */ + public monthTracker(index, item): string { + return `${item.getMonth()}}`; + } + /** *@hidden */ @@ -235,7 +245,6 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { this._formatterMonth = new Intl.DateTimeFormat(this._locale, { month: this.monthFormat }); } - /** * @hidden */ @@ -244,7 +253,7 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { event.preventDefault(); event.stopPropagation(); - const node = this.dates.find((date) => date.isCurrentMonth); + const node = this.dates.find((date) => date.nativeElement === event.target); if (!node) { return; } @@ -254,9 +263,6 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { const month = months[months.indexOf(node) - 3]; month.nativeElement.focus(); - - // TO DO: needs refactoring after styling!!!! - this.date = new Date(month.value.getFullYear(), month.value.getMonth(), this.date.getDate()); } } @@ -268,7 +274,7 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { event.preventDefault(); event.stopPropagation(); - const node = this.dates.find((date) => date.isCurrentMonth); + const node = this.dates.find((date) => date.nativeElement === event.target); if (!node) { return; } @@ -278,9 +284,6 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { const month = months[months.indexOf(node) + 3]; month.nativeElement.focus(); - - // TO DO: needs refactoring after styling!!!! - this.date = new Date(month.value.getFullYear(), month.value.getMonth(), this.date.getDate()); } } @@ -292,7 +295,7 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { event.preventDefault(); event.stopPropagation(); - const node = this.dates.find((date) => date.isCurrentMonth); + const node = this.dates.find((date) => date.nativeElement === event.target); if (!node) { return; } const months = this.dates.toArray(); @@ -300,9 +303,6 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { const month = months[months.indexOf(node) + 1]; month.nativeElement.focus(); - - // TO DO: needs refactoring after styling!!!! - this.date = new Date(month.value.getFullYear(), month.value.getMonth(), this.date.getDate()); } } @@ -314,7 +314,7 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { event.preventDefault(); event.stopPropagation(); - const node = this.dates.find((date) => date.isCurrentMonth); + const node = this.dates.find((date) => date.nativeElement === event.target); if (!node) { return; } const months = this.dates.toArray(); @@ -322,9 +322,6 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { const month = months[months.indexOf(node) - 1]; month.nativeElement.focus(); - - // TO DO: needs refactoring after styling!!!! - this.date = new Date(month.value.getFullYear(), month.value.getMonth(), this.date.getDate()); } } @@ -339,9 +336,6 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { const month = this.dates.toArray()[0]; month.nativeElement.focus(); - - // TO DO: needs refactoring after styling!!!! - this.date = new Date(month.value.getFullYear(), month.value.getMonth(), this.date.getDate()); } /** @@ -356,16 +350,16 @@ export class IgxMonthsViewComponent implements ControlValueAccessor { const month = months[months.length - 1]; month.nativeElement.focus(); - - // TO DO: needs refactoring after styling!!!! - this.date = new Date(month.value.getFullYear(), month.value.getMonth(), this.date.getDate()); } /** * @hidden */ - @HostListener('keydown.enter') - public onKeydownEnter() { + @HostListener('keydown.enter', ['$event']) + public onKeydownEnter(event) { + const value = this.dates.find((date) => date.nativeElement === event.target).value; + this.date = new Date(value.getFullYear(), value.getMonth(), this.date.getDate()); + this.onSelection.emit(this.date); this._onChangeCallback(this.date); } diff --git a/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.html b/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.html index 4f67b37afe9..be3ebf28d0e 100644 --- a/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.html +++ b/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.html @@ -1,6 +1,6 @@
- + {{ formattedYear(year) }}
diff --git a/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.ts b/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.ts index 3180b841897..c9bd39c8aad 100644 --- a/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.ts +++ b/projects/igniteui-angular/src/lib/calendar/years-view/years-view.component.ts @@ -159,14 +159,17 @@ export class IgxYearsViewComponent implements ControlValueAccessor { *@hidden */ private _formatterYear: any; + /** *@hidden */ private _locale = 'en'; + /** *@hidden */ private _yearFormat = 'numeric'; + /** *@hidden */ @@ -199,9 +202,9 @@ export class IgxYearsViewComponent implements ControlValueAccessor { *@hidden */ public selectYear(event) { - this.onSelection.emit(event); - this.date = event; + + this.onSelection.emit(this.date); this._onChangeCallback(this.date); } @@ -247,6 +250,13 @@ export class IgxYearsViewComponent implements ControlValueAccessor { } } + /** + * @hidden + */ + public yearTracker(index, item): string { + return `${item.getFullYear()}}`; + } + /** * @hidden */ diff --git a/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-component.scss b/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-component.scss index 947b9be3f16..0ac28e3c00a 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-component.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-component.scss @@ -134,6 +134,11 @@ @extend %cal-value !optional; @extend %cal-value--month !optional; @extend %cal-value--month-current !optional; + + &:hover, + &:focus { + @extend %cal-value--month-hover !optional; + } } @include m(vertical) { diff --git a/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-theme.scss index 4cd399f3ad3..96ecc56bd38 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/calendar/_calendar-theme.scss @@ -291,8 +291,10 @@ display: inline-flex; color: --var($theme, 'picker-arrow-color'); user-select: none; + outline: none; cursor: pointer; + &:focus, &:hover { color: --var($theme, 'picker-arrow-hover-color'); } @@ -305,6 +307,7 @@ %cal-picker-date { color: --var($theme, 'picker-text-color'); text-align: center; + outline: none; &:hover, &:focus { @@ -392,7 +395,8 @@ } %cal-value--month-hover { - color: --var($theme, 'month-hover-text-color'); + // color: --var($theme, 'month-hover-text-color'); + background-color: --var($theme, 'date-hover-background'); } %cal-value--month { diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index d4d33e1b715..49d3f7e4863 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -60,7 +60,6 @@ export * from './lib/checkbox/checkbox.component'; export * from './lib/chips/index'; export * from './lib/combo/index'; export * from './lib/date-picker/date-picker.component'; -export * from './lib/month-picker/month-picker.component'; export * from './lib/dialog/dialog.component'; export * from './lib/drop-down/index'; export * from './lib/grids/grid/index'; diff --git a/src/app/calendar-views/calendar-views.sample.html b/src/app/calendar-views/calendar-views.sample.html index b76293f7631..709ef9d3a21 100644 --- a/src/app/calendar-views/calendar-views.sample.html +++ b/src/app/calendar-views/calendar-views.sample.html @@ -28,6 +28,7 @@

Month Picker

diff --git a/src/app/calendar-views/calendar-views.sample.ts b/src/app/calendar-views/calendar-views.sample.ts index 341bec8c153..be91e86214e 100644 --- a/src/app/calendar-views/calendar-views.sample.ts +++ b/src/app/calendar-views/calendar-views.sample.ts @@ -1,6 +1,10 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { IgxCalendarComponent, DateRangeType, IgxMonthPickerComponent } from 'igniteui-angular'; -import { IgxDaysViewComponent } from 'projects/igniteui-angular/src/lib/calendar/days-view/days-view.component'; +import { + IgxCalendarComponent, + DateRangeType, + IgxDaysViewComponent, + IgxMonthPickerComponent +} from 'igniteui-angular'; @Component({ selector: 'app-calendar-views-sample', @@ -30,6 +34,12 @@ export class CalendarViewsSampleComponent implements OnInit { year: 'numeric' } + formatViews = { + day: true, + month: true, + year: true + }; + disabledDates = [{ type: DateRangeType.Between, dateRange: [ diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 371f5a06606..abfa6badabb 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -39,7 +39,6 @@ import { IgxTooltipModule, IgxSelectModule } from 'igniteui-angular'; -import { IgxMonthPickerModule } from 'projects/igniteui-angular/src/lib/month-picker/month-picker.component'; const igniteModules = [ @@ -80,7 +79,6 @@ const igniteModules = [ IgxToastModule, IgxToggleModule, IgxTooltipModule, - IgxMonthPickerModule, IgxSelectModule ];