diff --git a/src/calendar.tsx b/src/calendar.tsx index 74eaef196..f1b9223fe 100644 --- a/src/calendar.tsx +++ b/src/calendar.tsx @@ -1,4 +1,5 @@ import { clsx } from "clsx"; +import { differenceInDays } from "date-fns"; import React, { Component, createRef } from "react"; import CalendarContainer from "./calendar_container"; @@ -40,6 +41,9 @@ import { DEFAULT_YEAR_ITEM_NUMBER, getMonthInLocale, type Locale, + getStartOfMonth, + getEndOfMonth, + isDayDisabled, } from "./date_utils"; import InputTime from "./input_time"; import Month from "./month"; @@ -379,14 +383,42 @@ export default class Calendar extends Component { this.props.setPreSelection && this.props.setPreSelection(date); }; + getEnabledPreSelectionDateForMonth = (date: Date) => { + if (!isDayDisabled(date, this.props)) { + return date; + } + + const startOfMonth = getStartOfMonth(date); + const endOfMonth = getEndOfMonth(date); + + const totalDays = differenceInDays(endOfMonth, startOfMonth); + + let preSelectedDate = null; + + for (let dayIdx = 0; dayIdx <= totalDays; dayIdx++) { + const processingDate = addDays(startOfMonth, dayIdx); + + if (!isDayDisabled(processingDate, this.props)) { + preSelectedDate = processingDate; + break; + } + } + + return preSelectedDate; + }; + handleMonthChange = (date: Date): void => { - this.handleCustomMonthChange(date); + const enabledPreSelectionDate = + this.getEnabledPreSelectionDateForMonth(date) ?? date; + + this.handleCustomMonthChange(enabledPreSelectionDate); if (this.props.adjustDateOnChange) { - this.props.onSelect(date); + this.props.onSelect(enabledPreSelectionDate); this.props.setOpen?.(true); } - this.props.setPreSelection && this.props.setPreSelection(date); + this.props.setPreSelection && + this.props.setPreSelection(enabledPreSelectionDate); }; handleCustomMonthChange = (date: Date): void => { @@ -478,8 +510,7 @@ export default class Calendar extends Component { date: subYears( date, this.props.showYearPicker - ? (this.props.yearItemNumber ?? - Calendar.defaultProps.yearItemNumber) + ? this.props.yearItemNumber ?? Calendar.defaultProps.yearItemNumber : 1, ), }), @@ -593,8 +624,7 @@ export default class Calendar extends Component { date: addYears( date, this.props.showYearPicker - ? (this.props.yearItemNumber ?? - Calendar.defaultProps.yearItemNumber) + ? this.props.yearItemNumber ?? Calendar.defaultProps.yearItemNumber : 1, ), }), diff --git a/src/test/calendar_test.test.tsx b/src/test/calendar_test.test.tsx index f0912d721..9bf6d34e9 100644 --- a/src/test/calendar_test.test.tsx +++ b/src/test/calendar_test.test.tsx @@ -3,6 +3,7 @@ */ import { render, fireEvent, act, waitFor } from "@testing-library/react"; +import { setDate, startOfMonth, eachDayOfInterval, endOfMonth } from "date-fns"; import { endOfYear } from "date-fns/endOfYear"; import { isSunday } from "date-fns/isSunday"; import { eo } from "date-fns/locale/eo"; @@ -28,6 +29,8 @@ import { isSameDay, subMonths, subYears, + addDays, + getDate, } from "../date_utils"; import DatePicker from "../index"; @@ -2257,6 +2260,112 @@ describe("Calendar", () => { currentMonth === 0 ? 11 : currentMonth - 1, ); }); + + describe("pre-selection & disabled dates", () => { + it("should update the pre-selected dates to the first enabled day in a month when the next month is selected", () => { + const selected = new Date("2024-06-01"); + const excludeDate = addMonths(selected, 1); + + const { container } = render( + , + ); + const input = safeQuerySelector(container, "input"); + fireEvent.focus(input); + + const nextButton = safeQuerySelector( + container, + ".react-datepicker__navigation--next", + ); + fireEvent.click(nextButton); + + const preSelectedNewDate = safeQuerySelector( + container, + ".react-datepicker__day--keyboard-selected", + ).textContent; + const expectedPreSelectedNewDate = addDays(excludeDate, 1); + + expect(Number(preSelectedNewDate)).toBe( + getDate(expectedPreSelectedNewDate), + ); + }); + + it("should update the pre-selected dates to the first enabled day in a month when the previous month is selected", () => { + const selected = new Date("2024-06-08"); + const excludeDate = addMonths(selected, 1); + + const { container } = render( + , + ); + const input = safeQuerySelector(container, "input"); + fireEvent.focus(input); + + const nextButton = safeQuerySelector( + container, + ".react-datepicker__navigation--next", + ); + fireEvent.click(nextButton); + + const preSelectedNewDate = safeQuerySelector( + container, + ".react-datepicker__day--keyboard-selected", + ).textContent; + const expectedPreSelectedNewDate = setDate(excludeDate, 1); + + expect(Number(preSelectedNewDate)).toBe( + getDate(expectedPreSelectedNewDate), + ); + }); + + it("shouldn't set pre-select any date if all dates of a next month is disabled", () => { + const selected = new Date("2024-06-08"); + const nextMonth = addMonths(selected, 1); + const excludeDates = eachDayOfInterval({ + start: startOfMonth(nextMonth), + end: endOfMonth(nextMonth), + }); + + const { container } = render( + , + ); + const input = safeQuerySelector(container, "input"); + fireEvent.focus(input); + + const nextButton = safeQuerySelector( + container, + ".react-datepicker__navigation--next", + ); + fireEvent.click(nextButton); + + expect( + container.querySelector(".react-datepicker__day--keyboard-selected"), + ).toBeNull(); + }); + + it("shouldn't set pre-select any date if all dates of a last month is disabled", () => { + const selected = new Date("2024-06-08"); + const lastMonth = subMonths(selected, 1); + const excludeDates = eachDayOfInterval({ + start: startOfMonth(lastMonth), + end: endOfMonth(lastMonth), + }); + + const { container } = render( + , + ); + const input = safeQuerySelector(container, "input"); + fireEvent.focus(input); + + const nextButton = safeQuerySelector( + container, + ".react-datepicker__navigation--previous", + ); + fireEvent.click(nextButton); + + expect( + container.querySelector(".react-datepicker__day--keyboard-selected"), + ).toBeNull(); + }); + }); }); describe("showTimeSelect", () => {