diff --git a/src/PickerPanel.tsx b/src/PickerPanel.tsx index ea36b4c72..9527e986e 100644 --- a/src/PickerPanel.tsx +++ b/src/PickerPanel.tsx @@ -27,14 +27,14 @@ import type { DateRender } from './panels/DatePanel/DateBody'; import DatetimePanel from './panels/DatetimePanel'; import DecadePanel from './panels/DecadePanel'; import MonthPanel from './panels/MonthPanel'; -import type { MonthCellRender } from './panels/MonthPanel/MonthBody'; +import { MONTH_COL_COUNT, type MonthCellRender } from './panels/MonthPanel/MonthBody'; import QuarterPanel from './panels/QuarterPanel'; import type { SharedTimeProps } from './panels/TimePanel'; import TimePanel from './panels/TimePanel'; import WeekPanel from './panels/WeekPanel'; import YearPanel from './panels/YearPanel'; import RangeContext from './RangeContext'; -import { isEqual } from './utils/dateUtil'; +import { isEqual, WEEK_DAY_COUNT } from './utils/dateUtil'; import getExtraFooter from './utils/getExtraFooter'; import getRanges from './utils/getRanges'; import { getLowerBoundTime, setDateTime, setTime } from './utils/timeUtil'; @@ -125,6 +125,9 @@ type MergedPickerPanelProps = { picker?: PickerMode; } & OmitType; +// Calendar picker type +const CALENDAR_PANEL_MODE: PanelMode[] = ['date', 'month']; + function PickerPanel(props: PickerPanelProps) { const { prefixCls = 'rc-picker', @@ -297,9 +300,49 @@ function PickerPanel(props: PickerPanelProps) { } }; + const isSelectable = (key) => { + if (CALENDAR_PANEL_MODE.includes(mergedMode)) { + let date; + let operationFnc; + const isDateMode = mergedMode === 'date'; + if (key === KeyCode.PAGE_UP || key === KeyCode.PAGE_DOWN) { + operationFnc = isDateMode ? generateConfig.addMonth : generateConfig.addYear; + } else { + operationFnc = isDateMode ? generateConfig.addDate : generateConfig.addMonth; + } + + switch (key) { + case KeyCode.LEFT: + case KeyCode.PAGE_UP: + date = operationFnc(viewDate, -1); + break; + case KeyCode.RIGHT: + case KeyCode.PAGE_DOWN: + date = operationFnc(viewDate, 1); + break; + case KeyCode.UP: + case KeyCode.DOWN: + date = operationFnc( + viewDate, + Number( + `${key === KeyCode.UP ? '-' : ''}${isDateMode ? WEEK_DAY_COUNT : MONTH_COL_COUNT}`, + ), + ); + break; + } + + if (date) { + return !disabledDate?.(date); + } + } + return true; + }; + // ========================= Interactive ========================== const onInternalKeyDown = (e: React.KeyboardEvent) => { if (panelRef.current && panelRef.current.onKeyDown) { + let selectable = true; + const { which } = e; if ( [ KeyCode.LEFT, @@ -309,11 +352,18 @@ function PickerPanel(props: PickerPanelProps) { KeyCode.PAGE_UP, KeyCode.PAGE_DOWN, KeyCode.ENTER, - ].includes(e.which) + ].includes(which) ) { e.preventDefault(); + if (which !== KeyCode.ENTER && tabIndex === 0) { + selectable = isSelectable(which); + } + } + + // Cannot use keyboard to select disabled date + if (selectable) { + return panelRef.current.onKeyDown(e); } - return panelRef.current.onKeyDown(e); } /* istanbul ignore next */ @@ -379,7 +429,6 @@ function PickerPanel(props: PickerPanelProps) { delete pickerProps.onChange; delete pickerProps.onSelect; - switch (mergedMode) { case 'decade': panelNode = ( diff --git a/tests/keyboard.spec.tsx b/tests/keyboard.spec.tsx index 808145fc3..cb79e65e6 100644 --- a/tests/keyboard.spec.tsx +++ b/tests/keyboard.spec.tsx @@ -534,16 +534,56 @@ describe('Picker.Keyboard', () => { ); fireEvent.focus(document.querySelector('.rc-picker-panel')); - - // 9-10 is disabled + // 9-02、9-04、9-10 is disabled + panelKeyDown(KeyCode.LEFT); + panelKeyDown(KeyCode.RIGHT); panelKeyDown(KeyCode.DOWN); - expect(isSame(onSelect.mock.calls[0][0], '1990-09-10')).toBeTruthy(); - expect(onChange).not.toHaveBeenCalled(); + expect(onSelect).not.toHaveBeenCalled(); + + // 7-27、8-27 is enabled + panelKeyDown(KeyCode.UP); + expect(isSame(onSelect.mock.calls[0][0], '1990-08-27')).toBeTruthy(); + onSelect.mockReset(); + panelKeyDown(KeyCode.PAGE_UP); + expect(isSame(onSelect.mock.calls[0][0], '1990-07-27')).toBeTruthy(); + onSelect.mockReset(); + panelKeyDown(KeyCode.PAGE_DOWN); + expect(isSame(onSelect.mock.calls[0][0], '1990-08-27')).toBeTruthy(); + }); + + it('month panel', () => { + const onChange = jest.fn(); + const onSelect = jest.fn(); + const now = new Date(); + render( + date.month() < now.getMonth()} + />, + ); + + fireEvent.focus(document.querySelector('.rc-picker-panel')); - // 9-17 is enabled + // PAGE_UP and PAGE_DOWN do not trigger the select + panelKeyDown(KeyCode.PAGE_UP); + panelKeyDown(KeyCode.PAGE_DOWN); + expect(onSelect).not.toHaveBeenCalled(); + + // The disabled date is before August + panelKeyDown(KeyCode.LEFT); + panelKeyDown(KeyCode.UP); + expect(onSelect).not.toHaveBeenCalled(); + + // August and subsequent dates are enable + panelKeyDown(KeyCode.RIGHT); + expect(isSame(onSelect.mock.calls[0][0], '1990-10-03')).toBeTruthy(); + onSelect.mockReset(); + panelKeyDown(KeyCode.LEFT); + onSelect.mockReset(); panelKeyDown(KeyCode.DOWN); - expect(isSame(onSelect.mock.calls[1][0], '1990-09-17')).toBeTruthy(); - expect(isSame(onChange.mock.calls[0][0], '1990-09-17')).toBeTruthy(); + expect(isSame(onSelect.mock.calls[0][0], '1990-12-03')).toBeTruthy(); }); }); });