diff --git a/components/calendar/Header.tsx b/components/calendar/Header.tsx index 15efba8029..eeef2e8d5a 100644 --- a/components/calendar/Header.tsx +++ b/components/calendar/Header.tsx @@ -5,7 +5,7 @@ import { useContext, useMemo } from 'react'; import { FormItemInputContext } from '../form/context'; import { Button, Group } from '../radio'; import Select from '../select'; -import type { CalendarMode } from './generateCalendar'; +import type { CalendarMode, SelectInfo } from './generateCalendar'; const YearSelectOffset = 10; const YearSelectTotal = 20; @@ -147,7 +147,7 @@ export interface CalendarHeaderProps { locale: Locale; mode: CalendarMode; fullscreen: boolean; - onChange: (date: DateType) => void; + onChange: (date: DateType, source: SelectInfo['source']) => void; onModeChange: (mode: CalendarMode) => void; } function CalendarHeader(props: CalendarHeaderProps) { @@ -165,7 +165,6 @@ function CalendarHeader(props: CalendarHeaderProps) { const sharedProps = { ...props, - onChange, fullscreen, divRef, }; @@ -173,8 +172,20 @@ function CalendarHeader(props: CalendarHeaderProps) { return (
- - {mode === 'month' && } + { + onChange(v, 'year'); + }} + /> + {mode === 'month' && ( + { + onChange(v, 'month'); + }} + /> + )}
diff --git a/components/calendar/__tests__/index.test.tsx b/components/calendar/__tests__/index.test.tsx index 6e5c8227bd..49a6cd022b 100644 --- a/components/calendar/__tests__/index.test.tsx +++ b/components/calendar/__tests__/index.test.tsx @@ -76,7 +76,7 @@ describe('Calendar', () => { const { container } = render(); fireEvent.click(container.querySelector('.ant-picker-cell')!); - expect(onSelect).toHaveBeenCalledWith(expect.anything()); + expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'date' }); const value = onSelect.mock.calls[0][0]; expect(Dayjs.isDayjs(value)).toBe(true); @@ -270,7 +270,7 @@ describe('Calendar', () => { const end = Dayjs('2019-11-01'); const onValueChange = jest.fn(); createWrapper(start, end, value, onValueChange); - expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(3)); + expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(3), 'year'); }); it('if start.month > value.month, set value.month to start.month', () => { @@ -279,7 +279,7 @@ describe('Calendar', () => { const end = Dayjs('2019-03-01'); const onValueChange = jest.fn(); createWrapper(start, end, value, onValueChange); - expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(10)); + expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(10), 'year'); }); it('if change year and month > end month, set value.month to end.month', () => { @@ -302,7 +302,7 @@ describe('Calendar', () => { fireEvent.click( Array.from(wrapper.container.querySelectorAll('.ant-select-item-option')).at(-1)!, ); - expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(2)); + expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month(2), 'year'); }); it('onMonthChange should work correctly', () => { @@ -324,7 +324,7 @@ describe('Calendar', () => { ); openSelect(wrapper.container, '.ant-picker-calendar-month-select'); clickSelectItem(wrapper.container); - expect(onValueChange).toHaveBeenCalledWith(value.month(10)); + expect(onValueChange).toHaveBeenCalledWith(value.month(10), 'month'); }); it('onTypeChange should work correctly', () => { diff --git a/components/calendar/__tests__/select.test.tsx b/components/calendar/__tests__/select.test.tsx new file mode 100644 index 0000000000..8bc736e048 --- /dev/null +++ b/components/calendar/__tests__/select.test.tsx @@ -0,0 +1,95 @@ +import Dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import MockDate from 'mockdate'; +import { resetWarned } from 'rc-util/lib/warning'; +import React from 'react'; +import Calendar from '..'; +import { fireEvent, render, waitFakeTimer } from '../../../tests/utils'; + +describe('Calendar.onSelect', () => { + beforeAll(() => { + MockDate.set(Dayjs('2000-01-01').valueOf()); + }); + + beforeEach(() => { + resetWarned(); + jest.useFakeTimers(); + jest.clearAllTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('source of year select', async () => { + const onSelect = jest.fn(); + const { container } = render(); + + fireEvent.mouseDown(container.querySelector('.ant-select-selector')!); + await waitFakeTimer(); + + fireEvent.click(container.querySelector('.ant-select-item-option')!); + await waitFakeTimer(); + + expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'year' }); + }); + + it('source of month select', async () => { + const onSelect = jest.fn(); + const { container } = render(); + + fireEvent.mouseDown(container.querySelectorAll('.ant-select-selector')[1]!); + await waitFakeTimer(); + + fireEvent.click(container.querySelector('.ant-select-item-option')!); + await waitFakeTimer(); + + expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'month' }); + }); + + it('source of customize', async () => { + const onSelect = jest.fn(); + const { container } = render( + ( + + )} + />, + ); + + fireEvent.click(container.querySelector('.bamboo')!); + await waitFakeTimer(); + + expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'customize' }); + }); + + it('source of date', () => { + const onSelect = jest.fn(); + const { container } = render(); + + fireEvent.click(container.querySelector('.ant-picker-cell')!); + expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'date' }); + }); + + it('source of date with month panel', async () => { + const onSelect = jest.fn(); + const onPanelChange = jest.fn(); + const { container } = render(); + + // Click year radio + fireEvent.click(container.querySelectorAll('.ant-radio-button-input')[1]!); + expect(onPanelChange).toHaveBeenCalledWith(expect.anything(), 'year'); + + fireEvent.click(container.querySelector('.ant-picker-cell')!); + expect(onSelect).toHaveBeenCalledWith(expect.anything(), { source: 'date' }); + }); +}); diff --git a/components/calendar/generateCalendar.tsx b/components/calendar/generateCalendar.tsx index 278a91253f..556b6446ef 100644 --- a/components/calendar/generateCalendar.tsx +++ b/components/calendar/generateCalendar.tsx @@ -1,12 +1,12 @@ import classNames from 'classnames'; import { PickerPanel as RCPickerPanel } from 'rc-picker'; -import type { GenerateConfig } from 'rc-picker/lib/generate'; -import type { CellRenderInfo } from 'rc-picker/lib/interface'; import type { PickerPanelBaseProps as RCPickerPanelBaseProps, PickerPanelDateProps as RCPickerPanelDateProps, PickerPanelTimeProps as RCPickerPanelTimeProps, } from 'rc-picker/lib/PickerPanel'; +import type { GenerateConfig } from 'rc-picker/lib/generate'; +import type { CellRenderInfo } from 'rc-picker/lib/interface'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; import * as React from 'react'; import { ConfigContext } from '../config-provider'; @@ -14,8 +14,8 @@ import { useLocale } from '../locale'; import CalendarHeader from './Header'; import enUS from './locale/en_US'; -import useStyle from './style'; import warning from '../_util/warning'; +import useStyle from './style'; type InjectDefaultProps = Omit< Props, @@ -43,6 +43,10 @@ export type HeaderRender = (config: { onTypeChange: (type: CalendarMode) => void; }) => React.ReactNode; +export interface SelectInfo { + source: 'year' | 'month' | 'date' | 'customize'; +} + export interface CalendarProps { prefixCls?: string; className?: string; @@ -68,7 +72,7 @@ export interface CalendarProps { fullscreen?: boolean; onChange?: (date: DateType) => void; onPanelChange?: (date: DateType, mode: CalendarMode) => void; - onSelect?: (date: DateType) => void; + onSelect?: (date: DateType, selectInfo: SelectInfo) => void; } function generateCalendar(generateConfig: GenerateConfig) { @@ -198,10 +202,10 @@ function generateCalendar(generateConfig: GenerateConfig) { triggerPanelChange(mergedValue, newMode); }; - const onInternalSelect = (date: DateType) => { + const onInternalSelect = (date: DateType, source: SelectInfo['source']) => { triggerChange(date); - onSelect?.(date); + onSelect?.(date, { source }); }; // ====================== Locale ====================== @@ -310,7 +314,9 @@ function generateCalendar(generateConfig: GenerateConfig) { headerRender({ value: mergedValue, type: mergedMode, - onChange: onInternalSelect, + onChange: (nextDate) => { + onInternalSelect(nextDate, 'customize'); + }, onTypeChange: triggerModeChange, }) ) : ( @@ -332,7 +338,9 @@ function generateCalendar(generateConfig: GenerateConfig) { locale={contextLocale?.lang} generateConfig={generateConfig} cellRender={mergedCellRender} - onSelect={onInternalSelect} + onSelect={(nextDate) => { + onInternalSelect(nextDate, 'date'); + }} mode={panelMode} picker={panelMode} disabledDate={mergedDisabledDate} diff --git a/components/calendar/index.en-US.md b/components/calendar/index.en-US.md index 5a534c9c00..084ae4dc78 100644 --- a/components/calendar/index.en-US.md +++ b/components/calendar/index.en-US.md @@ -55,7 +55,7 @@ When data is in the form of dates, such as schedules, timetables, prices calenda | value | The current selected date | [dayjs](https://day.js.org/) | - | | | onChange | Callback for when date changes | function(date: Dayjs) | - | | | onPanelChange | Callback for when panel changes | function(date: Dayjs, mode: string) | - | | -| onSelect | Callback for when a date is selected | function(date: Dayjs) | - | | +| onSelect | Callback for when a date is selected, include source info | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | `info`: 5.6.0 | ## Design Token @@ -74,3 +74,17 @@ See [How to set locale for date-related components](/components/date-picker/#loc ### Date-related components locale is not working? See FAQ [Date-related-components-locale-is-not-working?](/docs/react/faq#date-related-components-locale-is-not-working) + +### How to get date from panel click? + +`onSelect` provide `info.source` to help on this: + +```tsx + { + if (source === 'date') { + console.log('Panel Select:', source); + } + }} +/> +``` diff --git a/components/calendar/index.zh-CN.md b/components/calendar/index.zh-CN.md index 56039fdb92..228817e476 100644 --- a/components/calendar/index.zh-CN.md +++ b/components/calendar/index.zh-CN.md @@ -60,7 +60,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA | value | 展示日期 | [dayjs](https://day.js.org/) | - | | | onChange | 日期变化回调 | function(date: Dayjs) | - | | | onPanelChange | 日期面板变化回调 | function(date: Dayjs, mode: string) | - | | -| onSelect | 点击选择日期回调 | function(date: Dayjs) | - | | +| onSelect | 选择日期回调,包含来源信息 | function(date: Dayjs, info: { source: 'year' \| 'month' \| 'date' \| 'customize' }) | - | `info`: 5.6.0 | ## Design Token @@ -79,3 +79,17 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*-p-wQLik200AAA ### 为什么时间类组件的国际化 locale 设置不生效? 参考 FAQ [为什么时间类组件的国际化 locale 设置不生效?](/docs/react/faq#为什么时间类组件的国际化-locale-设置不生效)。 + +### 如何仅获取来自面板点击的日期? + +`onSelect` 事件提供额外的来源信息,你可以通过 `info.source` 来判断来源: + +```tsx + { + if (source === 'date') { + console.log('Panel Select:', source); + } + }} +/> +```