From 22a1cb61658bee9ac9fe0588d894d85569494f57 Mon Sep 17 00:00:00 2001 From: gpolychronisAmadeus <54309994+gpolychronisAmadeus@users.noreply.github.com> Date: Fri, 4 Oct 2019 15:41:58 +0200 Subject: [PATCH] fix(datepicker): keep day/month when adding/removing month/year in ngb-calendar (#3355) Part of #3398 --- .../focus/datepicker-focus.e2e-spec.ts | 16 +++++----- src/datepicker/datepicker-service.spec.ts | 8 ++--- src/datepicker/datepicker-tools.ts | 7 ++-- src/datepicker/datepicker.spec.ts | 12 +++---- src/datepicker/ngb-calendar.spec.ts | 32 ++++++++++++++----- src/datepicker/ngb-calendar.ts | 19 +++++++++-- 6 files changed, 63 insertions(+), 31 deletions(-) diff --git a/e2e-app/src/app/datepicker/focus/datepicker-focus.e2e-spec.ts b/e2e-app/src/app/datepicker/focus/datepicker-focus.e2e-spec.ts index 358a4717d4..4234766ade 100644 --- a/e2e-app/src/app/datepicker/focus/datepicker-focus.e2e-spec.ts +++ b/e2e-app/src/app/datepicker/focus/datepicker-focus.e2e-spec.ts @@ -270,36 +270,36 @@ describe('Datepicker', () => { await expectFocused(page.getDayElement(new Date(2018, 7, 31)), `Last day of month should be focused`); }); - it(`should focus first day of previous month with 'PageUp'`, async() => { + it(`should focus same day of previous month with 'PageUp'`, async() => { await page.preSelectDate(); // 10 AUG 2018 await page.openDatepicker(); await sendKey(Key.PAGE_UP); - await expectFocused(page.getDayElement(new Date(2018, 6, 1)), `First day of previous month should be focused`); + await expectFocused(page.getDayElement(new Date(2018, 6, 10)), `Same day of previous month should be focused`); }); - it(`should focus first day of next month with 'PageDown'`, async() => { + it(`should focus same day of next month with 'PageDown'`, async() => { await page.preSelectDate(); // 10 AUG 2018 await page.openDatepicker(); await sendKey(Key.PAGE_DOWN); - await expectFocused(page.getDayElement(new Date(2018, 8, 1)), `First day of next month should be focused`); + await expectFocused(page.getDayElement(new Date(2018, 8, 10)), `Same day of next month should be focused`); }); - it(`should focus first day of previous year with 'Shift+PageUp'`, async() => { + it(`should focus same day of previous year with 'Shift+PageUp'`, async() => { await page.preSelectDate(); // 10 AUG 2018 await page.openDatepicker(); await sendKey(Key.SHIFT, Key.PAGE_UP); - await expectFocused(page.getDayElement(new Date(2017, 0, 1)), `First day of previous year should be focused`); + await expectFocused(page.getDayElement(new Date(2017, 7, 10)), `Same day of previous year should be focused`); }); - it(`should focus first day of next year with 'Shift+PageDown'`, async() => { + it(`should focus same day of next year with 'Shift+PageDown'`, async() => { await page.preSelectDate(); // 10 AUG 2018 await page.openDatepicker(); await sendKey(Key.SHIFT, Key.PAGE_DOWN); - await expectFocused(page.getDayElement(new Date(2019, 0, 1)), `First day of next year should be focused`); + await expectFocused(page.getDayElement(new Date(2019, 7, 10)), `Same day of next year should be focused`); }); it(`should focus min available day with 'Shift+Home'`, async() => { diff --git a/src/datepicker/datepicker-service.spec.ts b/src/datepicker/datepicker-service.spec.ts index 0538876d14..722e0d663f 100644 --- a/src/datepicker/datepicker-service.spec.ts +++ b/src/datepicker/datepicker-service.spec.ts @@ -1022,20 +1022,20 @@ describe('ngb-datepicker-service', () => { // months service.focus(date); service.focusMove('m', 1); - expect(model.focusDate).toEqual(new NgbDate(2017, 6, 1)); + expect(model.focusDate).toEqual(new NgbDate(2017, 6, 5)); service.focus(date); service.focusMove('m', -1); - expect(model.focusDate).toEqual(new NgbDate(2017, 4, 1)); + expect(model.focusDate).toEqual(new NgbDate(2017, 4, 5)); // years service.focus(date); service.focusMove('y', 1); - expect(model.focusDate).toEqual(new NgbDate(2018, 1, 1)); + expect(model.focusDate).toEqual(new NgbDate(2018, 5, 5)); service.focus(date); service.focusMove('y', -1); - expect(model.focusDate).toEqual(new NgbDate(2016, 1, 1)); + expect(model.focusDate).toEqual(new NgbDate(2016, 5, 5)); }); it(`should move focus when 'minDate' changes`, () => { diff --git a/src/datepicker/datepicker-tools.ts b/src/datepicker/datepicker-tools.ts index 29eb5d0596..631211722f 100644 --- a/src/datepicker/datepicker-tools.ts +++ b/src/datepicker/datepicker-tools.ts @@ -78,11 +78,12 @@ export function generateSelectBoxYears(date: NgbDate, minDate: NgbDate, maxDate: } export function nextMonthDisabled(calendar: NgbCalendar, date: NgbDate, maxDate: NgbDate) { - return maxDate && calendar.getNext(date, 'm').after(maxDate); + const nextDate = Object.assign(calendar.getNext(date, 'm'), {day: 1}); + return maxDate && nextDate.after(maxDate); } export function prevMonthDisabled(calendar: NgbCalendar, date: NgbDate, minDate: NgbDate) { - const prevDate = calendar.getPrev(date, 'm'); + const prevDate = Object.assign(calendar.getPrev(date, 'm'), {day: 1}); return minDate && (prevDate.year === minDate.year && prevDate.month < minDate.month || prevDate.year < minDate.year && minDate.month === 1); } @@ -96,7 +97,7 @@ export function buildMonths( // generate new first dates, nullify or reuse months const firstDates = Array.from({length: displayMonths}, (_, i) => { - const firstDate = calendar.getNext(date, 'm', i); + const firstDate = Object.assign(calendar.getNext(date, 'm', i), {day: 1}); months[i] = null; if (!force) { diff --git a/src/datepicker/datepicker.spec.ts b/src/datepicker/datepicker.spec.ts index c6e36babae..cde4bba145 100644 --- a/src/datepicker/datepicker.spec.ts +++ b/src/datepicker/datepicker.spec.ts @@ -980,24 +980,24 @@ describe('ngb-datepicker', () => { triggerKeyDown(getMonthContainer(datepicker), 33 /* page up */); fixture.detectChanges(); - expectFocusedDate(datepicker, new NgbDate(2016, 7, 1)); + expectFocusedDate(datepicker, new NgbDate(2016, 7, 2)); expectSelectedDate(datepicker, null); triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */); fixture.detectChanges(); - expectFocusedDate(datepicker, new NgbDate(2016, 8, 1)); + expectFocusedDate(datepicker, new NgbDate(2016, 8, 2)); expectSelectedDate(datepicker, null); triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */); fixture.detectChanges(); - expectFocusedDate(datepicker, new NgbDate(2016, 9, 1)); + expectFocusedDate(datepicker, new NgbDate(2016, 9, 2)); expectSelectedDate(datepicker, null); triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */); fixture.detectChanges(); datepicker = fixture.debugElement.query(By.directive(NgbDatepicker)); - expectFocusedDate(datepicker, new NgbDate(2016, 10, 1)); + expectFocusedDate(datepicker, new NgbDate(2016, 10, 2)); expectSelectedDate(datepicker, null); }); @@ -1016,13 +1016,13 @@ describe('ngb-datepicker', () => { triggerKeyDown(getMonthContainer(datepicker), 33 /* page up */, true /* shift */); fixture.detectChanges(); - expectFocusedDate(datepicker, new NgbDate(2015, 1, 1), true); + expectFocusedDate(datepicker, new NgbDate(2015, 8, 1), true); expectSelectedDate(datepicker, null); triggerKeyDown(getMonthContainer(datepicker), 34 /* page down */, true /* shift */); fixture.detectChanges(); - expectFocusedDate(datepicker, new NgbDate(2016, 1, 1)); + expectFocusedDate(datepicker, new NgbDate(2016, 8, 1)); expectSelectedDate(datepicker, null); }); diff --git a/src/datepicker/ngb-calendar.spec.ts b/src/datepicker/ngb-calendar.spec.ts index 781decf748..adae02de9e 100644 --- a/src/datepicker/ngb-calendar.spec.ts +++ b/src/datepicker/ngb-calendar.spec.ts @@ -43,25 +43,41 @@ describe('ngb-calendar-gregorian', () => { }); it('should add months to date', () => { - expect(calendar.getNext(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 8, 1)); + expect(calendar.getNext(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 8, 22)); expect(calendar.getNext(new NgbDate(2016, 7, 1), 'm')).toEqual(new NgbDate(2016, 8, 1)); - expect(calendar.getNext(new NgbDate(2016, 12, 22), 'm')).toEqual(new NgbDate(2017, 1, 1)); + expect(calendar.getNext(new NgbDate(2016, 12, 22), 'm')).toEqual(new NgbDate(2017, 1, 22)); + expect(calendar.getNext(new NgbDate(2016, 1, 29), 'm')).toEqual(new NgbDate(2016, 2, 29)); + expect(calendar.getNext(new NgbDate(2016, 1, 30), 'm')).toEqual(new NgbDate(2016, 2, 29)); + expect(calendar.getNext(new NgbDate(2016, 10, 30), 'm', 6)).toEqual(new NgbDate(2017, 4, 30)); + expect(calendar.getNext(new NgbDate(2016, 10, 31), 'm', 6)).toEqual(new NgbDate(2017, 4, 30)); }); it('should subtract months from date', () => { - expect(calendar.getPrev(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 6, 1)); + expect(calendar.getPrev(new NgbDate(2016, 7, 22), 'm')).toEqual(new NgbDate(2016, 6, 22)); expect(calendar.getPrev(new NgbDate(2016, 8, 1), 'm')).toEqual(new NgbDate(2016, 7, 1)); - expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'm')).toEqual(new NgbDate(2016, 12, 1)); + expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'm')).toEqual(new NgbDate(2016, 12, 22)); + expect(calendar.getPrev(new NgbDate(2016, 3, 29), 'm')).toEqual(new NgbDate(2016, 2, 29)); + expect(calendar.getPrev(new NgbDate(2016, 3, 30), 'm')).toEqual(new NgbDate(2016, 2, 29)); + expect(calendar.getPrev(new NgbDate(2016, 10, 30), 'm', 4)).toEqual(new NgbDate(2016, 6, 30)); + expect(calendar.getPrev(new NgbDate(2016, 10, 31), 'm', 4)).toEqual(new NgbDate(2016, 6, 30)); }); it('should add years to date', () => { - expect(calendar.getNext(new NgbDate(2016, 1, 22), 'y')).toEqual(new NgbDate(2017, 1, 1)); - expect(calendar.getNext(new NgbDate(2017, 12, 22), 'y')).toEqual(new NgbDate(2018, 1, 1)); + expect(calendar.getNext(new NgbDate(2016, 1, 22), 'y')).toEqual(new NgbDate(2017, 1, 22)); + expect(calendar.getNext(new NgbDate(2017, 12, 22), 'y')).toEqual(new NgbDate(2018, 12, 22)); + expect(calendar.getNext(new NgbDate(2016, 2, 29), 'y')).toEqual(new NgbDate(2017, 2, 28)); + expect(calendar.getNext(new NgbDate(2016, 2, 28), 'y')).toEqual(new NgbDate(2017, 2, 28)); + expect(calendar.getNext(new NgbDate(2016, 2, 29), 'y', 4)).toEqual(new NgbDate(2020, 2, 29)); + expect(calendar.getNext(new NgbDate(2016, 2, 29), 'y', 3)).toEqual(new NgbDate(2019, 2, 28)); }); it('should subtract years from date', () => { - expect(calendar.getPrev(new NgbDate(2016, 12, 22), 'y')).toEqual(new NgbDate(2015, 1, 1)); - expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'y')).toEqual(new NgbDate(2016, 1, 1)); + expect(calendar.getPrev(new NgbDate(2016, 12, 22), 'y')).toEqual(new NgbDate(2015, 12, 22)); + expect(calendar.getPrev(new NgbDate(2017, 1, 22), 'y')).toEqual(new NgbDate(2016, 1, 22)); + expect(calendar.getPrev(new NgbDate(2016, 2, 28), 'y')).toEqual(new NgbDate(2015, 2, 28)); + expect(calendar.getPrev(new NgbDate(2016, 2, 29), 'y')).toEqual(new NgbDate(2015, 2, 28)); + expect(calendar.getPrev(new NgbDate(2016, 2, 29), 'y', 4)).toEqual(new NgbDate(2012, 2, 29)); + expect(calendar.getPrev(new NgbDate(2016, 2, 29), 'y', 3)).toEqual(new NgbDate(2013, 2, 28)); }); it('should properly recognize invalid javascript date', () => { diff --git a/src/datepicker/ngb-calendar.ts b/src/datepicker/ngb-calendar.ts index f42a1debd1..0c71a3484d 100644 --- a/src/datepicker/ngb-calendar.ts +++ b/src/datepicker/ngb-calendar.ts @@ -98,20 +98,35 @@ export class NgbCalendarGregorian extends NgbCalendar { getNext(date: NgbDate, period: NgbPeriod = 'd', number = 1) { let jsDate = toJSDate(date); + let checkMonth = true; + let expectedMonth = jsDate.getMonth(); switch (period) { case 'y': - return new NgbDate(date.year + number, 1, 1); + jsDate.setFullYear(jsDate.getFullYear() + number); + break; case 'm': - jsDate = new Date(date.year, date.month + number - 1, 1, 12); + expectedMonth += number; + jsDate.setMonth(expectedMonth); + expectedMonth = expectedMonth % 12; + if (expectedMonth < 0) { + expectedMonth = expectedMonth + 12; + } break; case 'd': jsDate.setDate(jsDate.getDate() + number); + checkMonth = false; break; default: return date; } + if (checkMonth && jsDate.getMonth() !== expectedMonth) { + // this means the destination month has less days than the initial month + // let's go back to the end of the previous month: + jsDate.setDate(0); + } + return fromJSDate(jsDate); }