diff --git a/packages/react-date-picker/src/DatePicker.spec.tsx b/packages/react-date-picker/src/DatePicker.spec.tsx index 1b841980..18e4a87f 100644 --- a/packages/react-date-picker/src/DatePicker.spec.tsx +++ b/packages/react-date-picker/src/DatePicker.spec.tsx @@ -326,6 +326,34 @@ describe('DatePicker', () => { expect(calendar2).toBeInTheDocument(); }); + it('resets calendar view to selected value when reopened', async () => { + const value = new Date(2026, 3, 15); + const { container } = await render( + {}} />, + ); + + const button = page.getByTestId('calendar-button'); + await userEvent.click(button); + + const navigationLabel = container.querySelector('button.react-calendar__navigation__label'); + expect(navigationLabel).toHaveTextContent('April 2026'); + + const arrows = container.querySelectorAll('button.react-calendar__navigation__arrow'); + const nextArrow = arrows[2] as HTMLElement; // Index 2 is the next month arrow (›) + + await userEvent.click(nextArrow); + + expect(navigationLabel).toHaveTextContent('May 2026'); + + await userEvent.click(button); + await userEvent.click(button); + + const reopenedNavigationLabel = container.querySelector( + 'button.react-calendar__navigation__label', + ); + expect(reopenedNavigationLabel).toHaveTextContent('April 2026'); + }); + function triggerFocusInEvent(locator: Locator) { const element = locator.element(); @@ -590,6 +618,19 @@ describe('DatePicker', () => { expect(onChange).toHaveBeenCalledWith(new Date(2023, 0, 1)); }); + it('updates the displayed value when selecting a date from the calendar without a controlled value', async () => { + const { container } = await render(); + + const firstTile = container.querySelector('.react-calendar__tile') as HTMLButtonElement; + const nativeInput = container.querySelector('input[type="date"]') as HTMLInputElement; + + await act(async () => { + await userEvent.click(firstTile); + }); + + expect(nativeInput.value).not.toBe(''); + }); + it('does not call onChange for leap day until yyyy year is complete', async () => { const onChange = vi.fn(); diff --git a/packages/react-date-picker/src/DatePicker.tsx b/packages/react-date-picker/src/DatePicker.tsx index c078bda1..6012167c 100644 --- a/packages/react-date-picker/src/DatePicker.tsx +++ b/packages/react-date-picker/src/DatePicker.tsx @@ -21,6 +21,14 @@ import type { const baseClassName = 'react-date-picker'; const outsideActionEvents = ['mousedown', 'focusin', 'touchstart'] as const; +function getActiveStartDateFromValue(value: LooseValue | undefined) { + if (Array.isArray(value)) { + value = value[0]; + } + + return value instanceof Date ? value : undefined; +} + const iconProps = { xmlns: 'http://www.w3.org/2000/svg', width: 19, @@ -369,13 +377,27 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement { } = props; const [isOpen, setIsOpen] = useState(isOpenProps); + const [activeStartDate, setActiveStartDate] = useState( + getActiveStartDateFromValue(value), + ); + const [selectedValue, setSelectedValue] = useState(value); const wrapper = useRef(null); const calendarWrapper = useRef(null); + useEffect(() => { + if (isOpen) { + setActiveStartDate(getActiveStartDateFromValue(selectedValue ?? value)); + } + }, [isOpen, selectedValue, value]); + useEffect(() => { setIsOpen(isOpenProps); }, [isOpenProps]); + useEffect(() => { + setSelectedValue(value); + }, [value]); + function openCalendar({ reason }: { reason: OpenReason }) { if (shouldOpenCalendar) { if (!shouldOpenCalendar({ reason })) { @@ -383,6 +405,7 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement { } } + setActiveStartDate(getActiveStartDateFromValue(value)); setIsOpen(true); if (onCalendarOpen) { @@ -416,6 +439,8 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement { } function onChange(value: Value, shouldCloseCalendar: boolean = shouldCloseCalendarOnSelect) { + setSelectedValue(value); + if (shouldCloseCalendar) { closeCalendar({ reason: 'select' }); } @@ -510,7 +535,8 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement { }, [handleOutsideActionListeners]); function renderInputs() { - const [valueFrom] = Array.isArray(value) ? value : [value]; + const currentValue = selectedValue ?? value; + const [valueFrom] = Array.isArray(currentValue) ? currentValue : [currentValue]; const ariaLabelProps = { dayAriaLabel, @@ -583,19 +609,22 @@ export default function DatePicker(props: DatePickerProps): React.ReactElement { return null; } - const { calendarProps, portalContainer, value } = props; + const { calendarProps, portalContainer } = props; + const currentValue = selectedValue ?? value; const className = `${baseClassName}__calendar`; const classNames = clsx(className, `${className}--${isOpen ? 'open' : 'closed'}`); const calendar = ( setActiveStartDate(activeStartDate)} onChange={(value) => onChange(value)} - value={value} + value={currentValue} {...calendarProps} /> );