Skip to content

Commit b1fcedf

Browse files
Merge pull request #5151 from qburst/issue-5147/fix/date-navigation-issue
Fix #5147: 🐛 Fix navigation issue with disabled dates in date picker
2 parents 78ba9eb + c2210dd commit b1fcedf

File tree

2 files changed

+144
-3
lines changed

2 files changed

+144
-3
lines changed

src/calendar.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { clsx } from "clsx";
2+
import { differenceInDays } from "date-fns";
23
import React, { Component, createRef } from "react";
34

45
import CalendarContainer from "./calendar_container";
@@ -40,6 +41,9 @@ import {
4041
DEFAULT_YEAR_ITEM_NUMBER,
4142
getMonthInLocale,
4243
type Locale,
44+
getStartOfMonth,
45+
getEndOfMonth,
46+
isDayDisabled,
4347
} from "./date_utils";
4448
import InputTime from "./input_time";
4549
import Month from "./month";
@@ -379,14 +383,42 @@ export default class Calendar extends Component<CalendarProps, CalendarState> {
379383
this.props.setPreSelection && this.props.setPreSelection(date);
380384
};
381385

386+
getEnabledPreSelectionDateForMonth = (date: Date) => {
387+
if (!isDayDisabled(date, this.props)) {
388+
return date;
389+
}
390+
391+
const startOfMonth = getStartOfMonth(date);
392+
const endOfMonth = getEndOfMonth(date);
393+
394+
const totalDays = differenceInDays(endOfMonth, startOfMonth);
395+
396+
let preSelectedDate = null;
397+
398+
for (let dayIdx = 0; dayIdx <= totalDays; dayIdx++) {
399+
const processingDate = addDays(startOfMonth, dayIdx);
400+
401+
if (!isDayDisabled(processingDate, this.props)) {
402+
preSelectedDate = processingDate;
403+
break;
404+
}
405+
}
406+
407+
return preSelectedDate;
408+
};
409+
382410
handleMonthChange = (date: Date): void => {
383-
this.handleCustomMonthChange(date);
411+
const enabledPreSelectionDate =
412+
this.getEnabledPreSelectionDateForMonth(date) ?? date;
413+
414+
this.handleCustomMonthChange(enabledPreSelectionDate);
384415
if (this.props.adjustDateOnChange) {
385-
this.props.onSelect(date);
416+
this.props.onSelect(enabledPreSelectionDate);
386417
this.props.setOpen?.(true);
387418
}
388419

389-
this.props.setPreSelection && this.props.setPreSelection(date);
420+
this.props.setPreSelection &&
421+
this.props.setPreSelection(enabledPreSelectionDate);
390422
};
391423

392424
handleCustomMonthChange = (date: Date): void => {

src/test/calendar_test.test.tsx

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import { render, fireEvent, act, waitFor } from "@testing-library/react";
6+
import { setDate, startOfMonth, eachDayOfInterval, endOfMonth } from "date-fns";
67
import { endOfYear } from "date-fns/endOfYear";
78
import { isSunday } from "date-fns/isSunday";
89
import { eo } from "date-fns/locale/eo";
@@ -28,6 +29,8 @@ import {
2829
isSameDay,
2930
subMonths,
3031
subYears,
32+
addDays,
33+
getDate,
3134
} from "../date_utils";
3235
import DatePicker from "../index";
3336

@@ -2257,6 +2260,112 @@ describe("Calendar", () => {
22572260
currentMonth === 0 ? 11 : currentMonth - 1,
22582261
);
22592262
});
2263+
2264+
describe("pre-selection & disabled dates", () => {
2265+
it("should update the pre-selected dates to the first enabled day in a month when the next month is selected", () => {
2266+
const selected = new Date("2024-06-01");
2267+
const excludeDate = addMonths(selected, 1);
2268+
2269+
const { container } = render(
2270+
<DatePicker selected={selected} excludeDates={[excludeDate]} />,
2271+
);
2272+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
2273+
fireEvent.focus(input);
2274+
2275+
const nextButton = safeQuerySelector<HTMLButtonElement>(
2276+
container,
2277+
".react-datepicker__navigation--next",
2278+
);
2279+
fireEvent.click(nextButton);
2280+
2281+
const preSelectedNewDate = safeQuerySelector(
2282+
container,
2283+
".react-datepicker__day--keyboard-selected",
2284+
).textContent;
2285+
const expectedPreSelectedNewDate = addDays(excludeDate, 1);
2286+
2287+
expect(Number(preSelectedNewDate)).toBe(
2288+
getDate(expectedPreSelectedNewDate),
2289+
);
2290+
});
2291+
2292+
it("should update the pre-selected dates to the first enabled day in a month when the previous month is selected", () => {
2293+
const selected = new Date("2024-06-08");
2294+
const excludeDate = addMonths(selected, 1);
2295+
2296+
const { container } = render(
2297+
<DatePicker selected={selected} excludeDates={[excludeDate]} />,
2298+
);
2299+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
2300+
fireEvent.focus(input);
2301+
2302+
const nextButton = safeQuerySelector<HTMLButtonElement>(
2303+
container,
2304+
".react-datepicker__navigation--next",
2305+
);
2306+
fireEvent.click(nextButton);
2307+
2308+
const preSelectedNewDate = safeQuerySelector(
2309+
container,
2310+
".react-datepicker__day--keyboard-selected",
2311+
).textContent;
2312+
const expectedPreSelectedNewDate = setDate(excludeDate, 1);
2313+
2314+
expect(Number(preSelectedNewDate)).toBe(
2315+
getDate(expectedPreSelectedNewDate),
2316+
);
2317+
});
2318+
2319+
it("shouldn't set pre-select any date if all dates of a next month is disabled", () => {
2320+
const selected = new Date("2024-06-08");
2321+
const nextMonth = addMonths(selected, 1);
2322+
const excludeDates = eachDayOfInterval({
2323+
start: startOfMonth(nextMonth),
2324+
end: endOfMonth(nextMonth),
2325+
});
2326+
2327+
const { container } = render(
2328+
<DatePicker selected={selected} excludeDates={excludeDates} />,
2329+
);
2330+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
2331+
fireEvent.focus(input);
2332+
2333+
const nextButton = safeQuerySelector<HTMLButtonElement>(
2334+
container,
2335+
".react-datepicker__navigation--next",
2336+
);
2337+
fireEvent.click(nextButton);
2338+
2339+
expect(
2340+
container.querySelector(".react-datepicker__day--keyboard-selected"),
2341+
).toBeNull();
2342+
});
2343+
2344+
it("shouldn't set pre-select any date if all dates of a last month is disabled", () => {
2345+
const selected = new Date("2024-06-08");
2346+
const lastMonth = subMonths(selected, 1);
2347+
const excludeDates = eachDayOfInterval({
2348+
start: startOfMonth(lastMonth),
2349+
end: endOfMonth(lastMonth),
2350+
});
2351+
2352+
const { container } = render(
2353+
<DatePicker selected={selected} excludeDates={excludeDates} />,
2354+
);
2355+
const input = safeQuerySelector<HTMLInputElement>(container, "input");
2356+
fireEvent.focus(input);
2357+
2358+
const nextButton = safeQuerySelector<HTMLButtonElement>(
2359+
container,
2360+
".react-datepicker__navigation--previous",
2361+
);
2362+
fireEvent.click(nextButton);
2363+
2364+
expect(
2365+
container.querySelector(".react-datepicker__day--keyboard-selected"),
2366+
).toBeNull();
2367+
});
2368+
});
22602369
});
22612370

22622371
describe("showTimeSelect", () => {

0 commit comments

Comments
 (0)