diff --git a/CHANGELOG.md b/CHANGELOG.md index 47569e601..5295ad655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Fixed + - #### Calendar & Date picker + - Incorrect date rollover for in certain scenarios [#1710](https://github.com/IgniteUI/igniteui-webcomponents/issues/1710) + ## [6.0.1] - 2025-05-28 ### Added - #### Radio group diff --git a/src/components/calendar/model.spec.ts b/src/components/calendar/model.spec.ts index d6e37e7c8..2142eebfd 100644 --- a/src/components/calendar/model.spec.ts +++ b/src/components/calendar/model.spec.ts @@ -127,20 +127,15 @@ describe('Calendar day model', () => { const nonLeapFebruary = leapFebruary.set({ year: 2023 }); let { year, month, date } = nonLeapFebruary; - // Shift to first day of next month -> 2024/03/01 - expect([year, month, date]).to.eql([2023, 2, 1]); - - const lastDayOfJuly = new CalendarDay({ - year: 2024, - month: 6, - date: 31, - }); + // Shift to last day of the current month -> 2023-02-28 + expect([year, month, date]).to.eql([2023, 1, 28]); + const lastDayOfJuly = new CalendarDay({ year: 2024, month: 6, date: 31 }); const lastDayOfApril = lastDayOfJuly.set({ month: 3 }); ({ year, month, date } = lastDayOfApril); - // April does not have 31 days so shift to first day of May - expect([year, month, date]).to.eql([2024, 4, 1]); + // April does not have 31 days so clamp to the last day of April + expect([year, month, date]).to.eql([2024, 3, 30]); }); }); diff --git a/src/components/calendar/model.ts b/src/components/calendar/model.ts index 42df6b135..4f5ff955c 100644 --- a/src/components/calendar/model.ts +++ b/src/components/calendar/model.ts @@ -28,7 +28,7 @@ function checkRollover(original: CalendarDay, modified: CalendarDay) { /* blazorSuppress */ export class CalendarDay { - private _date!: Date; + private readonly _date: Date; /** Constructs and returns the current day. */ public static get today() { @@ -77,11 +77,21 @@ export class CalendarDay { * Returns a new instance with values replaced. */ public set(args: Partial) { - return new CalendarDay({ - year: args.year ?? this.year, - month: args.month ?? this.month, - date: args.date ?? this.date, - }); + const year = args.year ?? this.year; + const month = args.month ?? this.month; + const date = args.date ?? this.date; + + const temp = new Date(year, month, date); + + if (date > 0 && temp.getMonth() !== month) { + return new CalendarDay({ + year, + month, + date: new Date(year, month + 1, 0).getDate(), + }); + } + + return new CalendarDay({ year, month, date }); } public add(unit: DayInterval, value: number) { diff --git a/src/components/date-picker/date-picker.spec.ts b/src/components/date-picker/date-picker.spec.ts index ad17f3acc..225ded8ea 100644 --- a/src/components/date-picker/date-picker.spec.ts +++ b/src/components/date-picker/date-picker.spec.ts @@ -1,7 +1,11 @@ import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; import { spy } from 'sinon'; import IgcCalendarComponent from '../calendar/calendar.js'; -import { getCalendarDOM, getDayViewDOM } from '../calendar/helpers.spec.js'; +import { + getCalendarDOM, + getDOMDate, + getDayViewDOM, +} from '../calendar/helpers.spec.js'; import { CalendarDay, toCalendarDay } from '../calendar/model.js'; import { DateRangeType } from '../calendar/types.js'; import { @@ -897,6 +901,43 @@ describe('Date picker', () => { expect(picker.open).to.be.true; }); + it('issue 1710', async () => { + const activeDate = new CalendarDay({ year: 2025, month: 4, date: 29 }); + const targetDate = activeDate.add('day', 2); + + // Select the last date of May + picker.activeDate = activeDate.native; + await picker.show(); + + const calendarDOM = getCalendarDOM(calendar); + const lastOfMay = getDOMDate(targetDate, calendarDOM.views.days); + + simulateClick(lastOfMay); + await elementUpdated(picker); + + expect(checkDatesEqual(picker.value!, targetDate)); + + // Open the picker and switch to months view + await picker.show(); + + simulateClick(calendarDOM.navigation.months); + await elementUpdated(picker); + + const monthElements = Array.from( + calendarDOM.views.months.renderRoot.querySelectorAll( + '[role="gridcell"]' + ) + ); + + const monthNames = new Set(monthElements.map((each) => each.innerText)); + const selectedMonths = monthElements.filter((each) => + each.matches('[aria-selected="true"]') + ); + + expect(monthNames).lengthOf(12); + expect(selectedMonths).lengthOf(1); + }); + describe('Readonly state', () => { describe('Dropdown mode', () => { beforeEach(async () => {