diff --git a/src/calendar.tsx b/src/calendar.tsx index 5c5d39135..3122ceb98 100644 --- a/src/calendar.tsx +++ b/src/calendar.tsx @@ -180,6 +180,7 @@ type CalendarProps = React.PropsWithChildren< showPreviousMonths?: boolean; monthsShown?: number; monthSelectedIn?: number; + onMonthSelectedInChange?: (monthSelectedIn: number) => void; onSelect: ( day: Date, event?: @@ -455,7 +456,12 @@ export default class Calendar extends Component { ({ date }) => ({ date: setMonth(date, Number(month)), }), - () => this.handleMonthChange(this.state.date), + () => { + this.handleMonthChange(this.state.date); + // Reset monthSelectedIn to 0 so the target month appears in the leftmost position + // This ensures consistent behavior when using changeMonth in custom headers + this.props.onMonthSelectedInChange?.(0); + }, ); }; diff --git a/src/index.tsx b/src/index.tsx index 6006fa2ad..84b72a3b3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1463,6 +1463,10 @@ export class DatePicker extends Component { } }; + handleMonthSelectedInChange = (monthSelectedIn: number): void => { + this.setState({ monthSelectedIn }); + }; + renderCalendar = () => { if (!this.props.inline && !this.isCalendarOpen()) { return null; @@ -1494,6 +1498,7 @@ export class DatePicker extends Component { dropdownMode={ this.props.dropdownMode ?? DatePicker.defaultProps.dropdownMode } + onMonthSelectedInChange={this.handleMonthSelectedInChange} > {this.props.children} diff --git a/src/test/calendar_test.test.tsx b/src/test/calendar_test.test.tsx index 0af467016..ba1c0782a 100644 --- a/src/test/calendar_test.test.tsx +++ b/src/test/calendar_test.test.tsx @@ -984,6 +984,47 @@ describe("Calendar", () => { expect(header).toHaveLength(1); expect(time).toHaveLength(1); }); + + it("should display the target month in the leftmost position when changeMonth is called with monthsShown >= 2", () => { + // This test verifies the fix for issue #3829 + // When using changeMonth in a custom header with monthsShown >= 2, + // the target month should always appear in the leftmost position + // regardless of which calendar panel the user last selected a date in + const onMonthSelectedInChangeSpy = jest.fn(); + + const renderCustomHeaderWithMonthSelect = ({ + changeMonth, + }: { + changeMonth: (month: number) => void; + }) => ( +
+ +
+ ); + + const { calendar } = getCalendar({ + renderCustomHeader: renderCustomHeaderWithMonthSelect, + monthsShown: 2, + onMonthSelectedInChange: onMonthSelectedInChangeSpy, + }); + + // Select June (month index 5) from the month dropdown + const monthSelect = safeQuerySelector(calendar, ".month-select"); + fireEvent.change(monthSelect, { target: { value: 5 } }); + + // Verify that onMonthSelectedInChange was called with 0 + // This ensures the target month appears in the leftmost position + expect(onMonthSelectedInChangeSpy).toHaveBeenCalledWith(0); + }); }); describe("when showDisabledMonthNavigation is enabled", () => { diff --git a/src/test/datepicker_test.test.tsx b/src/test/datepicker_test.test.tsx index fab4db472..37755e509 100644 --- a/src/test/datepicker_test.test.tsx +++ b/src/test/datepicker_test.test.tsx @@ -2897,6 +2897,59 @@ describe("DatePicker", () => { expect(instance!.state.monthSelectedIn).toEqual(undefined); }); + it("should reset monthSelectedIn to 0 when changeMonth is called from custom header", () => { + let instance: DatePicker | null = null; + let changeMonthFn: ((month: number) => void) | null = null; + + const { container } = render( + { + instance = node; + }} + inline + monthsShown={2} + selected={newDate("2024-06-15")} + renderCustomHeader={({ changeMonth }) => { + changeMonthFn = changeMonth; + return ( +
+ +
+ ); + }} + />, + ); + + expect(instance).toBeTruthy(); + + // First, select a day in the second month panel to set monthSelectedIn to 1 + const dayButtonsInSecondMonth = container + .querySelectorAll(".react-datepicker__month-container")[1] + ?.querySelectorAll( + ".react-datepicker__day:not(.react-datepicker__day--outside-month)", + ); + expect(dayButtonsInSecondMonth).toBeTruthy(); + expect(dayButtonsInSecondMonth!.length).toBeGreaterThan(0); + + // Click a day in the second month to set monthSelectedIn to 1 + fireEvent.click(dayButtonsInSecondMonth![10]!); + expect(instance!.state.monthSelectedIn).toEqual(1); + + // Now call changeMonth from the custom header + expect(changeMonthFn).toBeTruthy(); + act(() => { + changeMonthFn!(0); // Change to January + }); + + // monthSelectedIn should be reset to 0 + expect(instance!.state.monthSelectedIn).toEqual(0); + }); + it("should show the popper arrow when showPopperArrow is true", () => { const { container } = render(); const input = safeQuerySelector(container, "input");