diff --git a/src/components/calendar-picker-view/calendar-picker-view.tsx b/src/components/calendar-picker-view/calendar-picker-view.tsx index 0c9835a465..63c32969d1 100644 --- a/src/components/calendar-picker-view/calendar-picker-view.tsx +++ b/src/components/calendar-picker-view/calendar-picker-view.tsx @@ -135,9 +135,22 @@ export const CalendarPickerView = forwardRef< const scrollTo = useSyncScroll(current, context.visible, bodyRef) // ============================== Boundary ============================== + const VISIBLE_MONTHS = 6 + + const alignRange = (target: dayjs.Dayjs) => { + if (!props.min) { + setDefaultMin(target) + } + if (!props.max) { + setDefaultMax(target.add(VISIBLE_MONTHS, 'month')) + } + } + // 记录默认的 min 和 max,并在外部的值超出边界时自动扩充 const [defaultMin, setDefaultMin] = useState(current) - const [defaultMax, setDefaultMax] = useState(() => current.add(6, 'month')) + const [defaultMax, setDefaultMax] = useState(() => + current.add(VISIBLE_MONTHS, 'month') + ) useEffect(() => { if (dateRange) { @@ -150,7 +163,7 @@ export const CalendarPickerView = forwardRef< setDefaultMax(dayjs(endDate).endOf('month')) } } - }, [dateRange]) + }, [dateRange, defaultMin, defaultMax, props.min, props.max]) const maxDay = useMemo( () => (props.max ? dayjs(props.max) : defaultMax), @@ -162,6 +175,21 @@ export const CalendarPickerView = forwardRef< ) // ================================ Refs ================================ + const jumpToPage = (page: Page) => { + let next = convertPageToDayjs(page) + if (props.min && next.isBefore(minDay)) { + next = minDay + } + if (props.max && next.isAfter(maxDay)) { + next = maxDay.date(1) + } + if (next.isBefore(defaultMin) || next.isAfter(defaultMax)) { + alignRange(next) + } + setCurrent(next) + scrollTo(next) + } + useImperativeHandle(ref, () => ({ jumpTo: pageOrPageGenerator => { let page: Page @@ -173,14 +201,11 @@ export const CalendarPickerView = forwardRef< } else { page = pageOrPageGenerator } - const next = convertPageToDayjs(page) - setCurrent(next) - scrollTo(next) + jumpToPage(page) }, jumpToToday: () => { - const next = dayjs().date(1) - setCurrent(next) - scrollTo(next) + const today = dayjs() + jumpToPage({ year: today.year(), month: today.month() + 1 }) }, getDateRange: () => dateRange, })) diff --git a/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx b/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx index 8d442e641f..e9620b5206 100644 --- a/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx +++ b/src/components/calendar-picker-view/tests/calendar-picker-view.test.tsx @@ -195,6 +195,122 @@ describe('Calendar', () => { expect(container.querySelectorAll(`.${classPrefix}-cell`)).toHaveLength(30) }) + test('jumpTo expands rendering range', () => { + const App = () => { + const ref = useRef(null) + return ( + <> + + + + + ) + } + const { container, getByText } = render() + + // defaultMin starts at today (2023-05), jumpTo 2021-01 resets window around target + fireEvent.click(getByText('jumpToPast')) + expect( + container.querySelector('[data-year-month="2021-1"]') + ).toBeInTheDocument() + + // jumpToFuture 2026-12 resets window, 2021-1 should no longer be rendered + fireEvent.click(getByText('jumpToFuture')) + expect( + container.querySelector('[data-year-month="2026-12"]') + ).toBeInTheDocument() + expect( + container.querySelector('[data-year-month="2021-1"]') + ).not.toBeInTheDocument() + }) + + test('jumpTo keeps selected date in rendering range', () => { + const App = () => { + const ref = useRef(null) + return ( + <> + + + + ) + } + const { container, getByText } = render() + + // Selected date is 2023-05, jumpTo 2021-01 should keep 2023-05 rendered + fireEvent.click(getByText('jumpToPast')) + expect( + container.querySelector('[data-year-month="2021-1"]') + ).toBeInTheDocument() + expect( + container.querySelector('[data-year-month="2023-5"]') + ).toBeInTheDocument() + }) + + test('jumpTo clamps to min/max when bounds are set', () => { + const App = () => { + const ref = useRef(null) + return ( + <> + + + + + ) + } + const { container, getByText } = render() + + // jumpTo before min should clamp to min month (2023-01) + fireEvent.click(getByText('jumpBeforeMin')) + expect( + container.querySelector('[data-year-month="2023-1"]') + ).toBeInTheDocument() + + // jumpTo after max should clamp to max month (2023-12) + fireEvent.click(getByText('jumpAfterMax')) + expect( + container.querySelector('[data-year-month="2023-12"]') + ).toBeInTheDocument() + }) + test('auto expand month list', () => { const { container, rerender } = render(