From 2120b95fd8e7470c5d6aec26dbe13d8cca2dfcaf Mon Sep 17 00:00:00 2001 From: nninyeong Date: Thu, 6 Aug 2026 00:35:35 +0900 Subject: [PATCH 1/2] fix: align month picker keyboard navigation with bounds --- src/date_utils.ts | 5 +++- src/month.tsx | 5 ++-- src/test/date_utils_test.test.ts | 14 ++++++++++ src/test/month_test.test.tsx | 46 ++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/date_utils.ts b/src/date_utils.ts index 7fba0d960..ec8be2478 100644 --- a/src/date_utils.ts +++ b/src/date_utils.ts @@ -1140,7 +1140,10 @@ export function isMonthYearDisabled( > = {}, ): boolean { return ( - isOutOfBounds(date, { minDate, maxDate }) || + isOutOfBounds(date, { + minDate: minDate ? startOfMonth(minDate) : undefined, + maxDate: maxDate ? endOfMonth(maxDate) : undefined, + }) || (excludeDates && excludeDates.some((excludedDate) => isSameMonth( diff --git a/src/month.tsx b/src/month.tsx index 6eee82deb..521069b58 100644 --- a/src/month.tsx +++ b/src/month.tsx @@ -11,6 +11,7 @@ import { getMonth, getMonthInLocale, getMonthShortInLocale, + getEndOfMonth, getQuarter, getQuarterShortInLocale, getStartOfMonth, @@ -652,7 +653,7 @@ export default class Month extends Component { break; } // if minDate exists and the new month is before the minimum month, it will try to find the next available month after - if (minDate && newCalculatedDate < minDate) { + if (minDate && newCalculatedDate < getStartOfMonth(minDate)) { eventKeyCopy = KeyType.ArrowRight; const obj = calculateNewDateAndMonth( eventKeyCopy, @@ -664,7 +665,7 @@ export default class Month extends Component { } // if maxDate exists and the new month is after the maximum month, it will try to find the next available month before - if (maxDate && newCalculatedDate > maxDate) { + if (maxDate && newCalculatedDate > getEndOfMonth(maxDate)) { eventKeyCopy = KeyType.ArrowLeft; const obj = calculateNewDateAndMonth( eventKeyCopy, diff --git a/src/test/date_utils_test.test.ts b/src/test/date_utils_test.test.ts index ee5b98047..7d6bd2385 100644 --- a/src/test/date_utils_test.test.ts +++ b/src/test/date_utils_test.test.ts @@ -1591,6 +1591,20 @@ describe("date_utils", () => { expect(isMonthYearDisabled(date, props)).toBe(true); }); + it("should return false if date is before a mid-month min date in the same month", () => { + const date = new Date(2023, 2, 1); + expect( + isMonthYearDisabled(date, { minDate: new Date(2023, 2, 15) }), + ).toBe(false); + }); + + it("should return false if date is after a mid-month max date in the same month", () => { + const date = new Date(2023, 2, 31); + expect( + isMonthYearDisabled(date, { maxDate: new Date(2023, 2, 15) }), + ).toBe(false); + }); + it("should return false if month is not disabled", () => { const date = new Date(new Date(2023, 3, 1)); expect(isMonthYearDisabled(date, props)).toBe(false); diff --git a/src/test/month_test.test.tsx b/src/test/month_test.test.tsx index d8366aa2e..c4022877c 100644 --- a/src/test/month_test.test.tsx +++ b/src/test/month_test.test.tsx @@ -1881,6 +1881,52 @@ describe("Month", () => { ); }); + it("should pre-select March when arrowLeft navigates to a month with a mid-month min date", () => { + let preSelected: Date | null | undefined = null; + const setPreSelection = (param?: Date | null) => { + preSelected = param; + }; + const monthComponent = renderMonth({ + selected: newDate("2015-04-01"), + day: newDate("2015-04-01"), + setPreSelection, + preSelection: newDate("2015-04-01"), + minDate: newDate("2015-03-15"), + }); + + fireEvent.keyDown( + monthComponent.querySelector(".react-datepicker__month-3") as Element, + getKey(KeyType.ArrowLeft), + ); + + expect((preSelected! as Date).toString()).toBe( + newDate("2015-03-01").toString(), + ); + }); + + it("should pre-select June when arrowRight navigates to a month with a mid-month max date", () => { + let preSelected: Date | null | undefined = null; + const setPreSelection = (param?: Date | null) => { + preSelected = param; + }; + const monthComponent = renderMonth({ + selected: newDate("2015-05-31"), + day: newDate("2015-05-31"), + setPreSelection, + preSelection: newDate("2015-05-31"), + maxDate: newDate("2015-06-15"), + }); + + fireEvent.keyDown( + monthComponent.querySelector(".react-datepicker__month-4") as Element, + getKey(KeyType.ArrowRight), + ); + + expect((preSelected! as Date).toString()).toBe( + newDate("2015-06-30").toString(), + ); + }); + it("should trigger setPreSelection and set May as pre-selected on arrowUp", () => { let preSelected: Date | null | undefined = null; const setPreSelection = (param?: Date | null) => { From 38ccbd3b7e40ebb9a0bb08872e298a8b8441f31f Mon Sep 17 00:00:00 2001 From: nninyeong Date: Thu, 6 Aug 2026 01:07:04 +0900 Subject: [PATCH 2/2] fix: keep month picker preselection aligned --- src/index.tsx | 13 +++++-- src/test/month_test.test.tsx | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 05fde7b81..99f07cb58 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -43,6 +43,7 @@ import { DEFAULT_YEAR_ITEM_NUMBER, isSameDay, isMonthDisabled, + isMonthYearDisabled, isYearDisabled, safeMultipleDatesFormat, getHolidaysMap, @@ -1054,15 +1055,19 @@ export class DatePicker extends Component { } }; - // When checking preSelection via min/maxDate, times need to be manipulated via getStartOfDay/getEndOfDay + // Validate pre-selection at the active picker's granularity: month or day. setPreSelection = (date?: Date | null): void => { if (this.props.readOnly) return; const hasMinDate = isDate(this.props.minDate); const hasMaxDate = isDate(this.props.maxDate); let isValidDateSelection = true; if (date) { - const dateStartOfDay = getStartOfDay(date); - if (hasMinDate && hasMaxDate) { + if (this.props.showMonthYearPicker) { + isValidDateSelection = !isMonthYearDisabled(date, { + minDate: this.props.minDate, + maxDate: this.props.maxDate, + }); + } else if (hasMinDate && hasMaxDate) { // isDayInRange uses getStartOfDay internally, so not necessary to manipulate times here isValidDateSelection = isDayInRange( date, @@ -1070,11 +1075,13 @@ export class DatePicker extends Component { this.props.maxDate, ); } else if (hasMinDate) { + const dateStartOfDay = getStartOfDay(date); const minDateStartOfDay = getStartOfDay(this.props.minDate); isValidDateSelection = isAfter(date, minDateStartOfDay) || isEqual(dateStartOfDay, minDateStartOfDay); } else if (hasMaxDate) { + const dateStartOfDay = getStartOfDay(date); const maxDateEndOfDay = getEndOfDay(this.props.maxDate); isValidDateSelection = isBefore(date, maxDateEndOfDay) || diff --git a/src/test/month_test.test.tsx b/src/test/month_test.test.tsx index c4022877c..a18300820 100644 --- a/src/test/month_test.test.tsx +++ b/src/test/month_test.test.tsx @@ -1927,6 +1927,80 @@ describe("Month", () => { ); }); + it("should keep keyboard pre-selection aligned after navigating through a min-date boundary month", () => { + let instance: DatePicker | null = null; + const { container } = render( + { + instance = node; + }} + inline + showMonthYearPicker + selected={newDate("2015-02-15")} + minDate={newDate("2015-01-31")} + />, + ); + const january = container.querySelector( + ".react-datepicker__month-0", + ) as HTMLElement; + const february = container.querySelector( + ".react-datepicker__month-1", + ) as HTMLElement; + const march = container.querySelector( + ".react-datepicker__month-2", + ) as HTMLElement; + + february.focus(); + fireEvent.keyDown(february, getKey(KeyType.ArrowLeft)); + fireEvent.keyDown(january, getKey(KeyType.ArrowRight)); + fireEvent.keyDown(february, getKey(KeyType.ArrowRight)); + + expect(document.activeElement).toBe(march); + expect(instance!.state.preSelection).toEqual(newDate("2015-03-15")); + expect( + container.querySelector( + ".react-datepicker__month-text--keyboard-selected", + ), + ).toBe(march); + }); + + it("should keep keyboard pre-selection aligned after navigating through a max-date boundary month", () => { + let instance: DatePicker | null = null; + const { container } = render( + { + instance = node; + }} + inline + showMonthYearPicker + selected={newDate("2015-11-15")} + maxDate={newDate("2015-12-01")} + />, + ); + const october = container.querySelector( + ".react-datepicker__month-9", + ) as HTMLElement; + const november = container.querySelector( + ".react-datepicker__month-10", + ) as HTMLElement; + const december = container.querySelector( + ".react-datepicker__month-11", + ) as HTMLElement; + + november.focus(); + fireEvent.keyDown(november, getKey(KeyType.ArrowRight)); + fireEvent.keyDown(december, getKey(KeyType.ArrowLeft)); + fireEvent.keyDown(november, getKey(KeyType.ArrowLeft)); + + expect(document.activeElement).toBe(october); + expect(instance!.state.preSelection).toEqual(newDate("2015-10-15")); + expect( + container.querySelector( + ".react-datepicker__month-text--keyboard-selected", + ), + ).toBe(october); + }); + it("should trigger setPreSelection and set May as pre-selected on arrowUp", () => { let preSelected: Date | null | undefined = null; const setPreSelection = (param?: Date | null) => {