Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/date_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
13 changes: 10 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
DEFAULT_YEAR_ITEM_NUMBER,
isSameDay,
isMonthDisabled,
isMonthYearDisabled,
isYearDisabled,
safeMultipleDatesFormat,
getHolidaysMap,
Expand Down Expand Up @@ -1054,27 +1055,33 @@ export class DatePicker extends Component<DatePickerProps, DatePickerState> {
}
};

// 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,
this.props.minDate,
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) ||
Expand Down
5 changes: 3 additions & 2 deletions src/month.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getMonth,
getMonthInLocale,
getMonthShortInLocale,
getEndOfMonth,
getQuarter,
getQuarterShortInLocale,
getStartOfMonth,
Expand Down Expand Up @@ -652,7 +653,7 @@ export default class Month extends Component<MonthProps> {
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,
Expand All @@ -664,7 +665,7 @@ export default class Month extends Component<MonthProps> {
}

// 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,
Expand Down
14 changes: 14 additions & 0 deletions src/test/date_utils_test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
120 changes: 120 additions & 0 deletions src/test/month_test.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1881,6 +1881,126 @@ 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 keep keyboard pre-selection aligned after navigating through a min-date boundary month", () => {
let instance: DatePicker | null = null;
const { container } = render(
<DatePicker
ref={(node) => {
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(
<DatePicker
ref={(node) => {
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) => {
Expand Down