diff --git a/packages/components/date-picker/__tests__/date-picker.test.ts b/packages/components/date-picker/__tests__/date-picker.test.ts index d96795aba3..5f75a605a8 100644 --- a/packages/components/date-picker/__tests__/date-picker.test.ts +++ b/packages/components/date-picker/__tests__/date-picker.test.ts @@ -365,6 +365,66 @@ describe('DatePicker', () => { expect(document.querySelector('.disabled')).not.toBeNull() }) + it('select year picker when using disabledDate prop', async () => { + const wrapper = _mount( + ``, + () => ({ + value: '2024-01-01', + disabledDate(time) { + const dayTime = new Date(2023, 1, 4).getTime() + return time.getTime() < dayTime + }, + }) + ) + const input = wrapper.find('input') + input.trigger('blur') + input.trigger('focus') + await nextTick() + const yearLabel: HTMLElement = document.querySelectorAll( + '.el-date-picker__header-label' + )[0] + yearLabel.click() + await nextTick() + const yearCells = document.querySelectorAll('.el-date-table-cell__text') + const year2023 = [...yearCells].find((item) => item.innerHTML === '2023') + year2023.click() + await nextTick() + expect(input.element.value).toBe('2023-02-04') + }) + + it('select month picker when using disabledDate prop', async () => { + const wrapper = _mount( + ``, + () => ({ + value: '2023-05-01', + disabledDate(time) { + const dayTime = new Date(2023, 1, 4).getTime() + return time.getTime() < dayTime + }, + }) + ) + const input = wrapper.find('input') + input.trigger('blur') + input.trigger('focus') + await nextTick() + const monthLabel: HTMLElement = document.querySelectorAll( + '.el-date-picker__header-label' + )[1] + monthLabel.click() + await nextTick() + const monthCells = document.querySelectorAll('.el-date-table-cell__text') + const februaryCell = monthCells[1] + februaryCell.click() + await nextTick() + expect(input.element.value).toBe('2023-02-04') + }) + it('should work when using disabledDate prop and daterange type', async () => { const wrapper = _mount( ` { - const firstDay = dayjs().locale(lang).startOf('month').month(month).year(year) - const numOfDays = firstDay.daysInMonth() - return rangeArr(numOfDays).map((n) => firstDay.add(n, 'day').toDate()) -} - const props = defineProps(basicMonthTableProps) const emit = defineEmits(['changerange', 'pick', 'select']) @@ -230,11 +224,15 @@ const handleMonthTableClick = (event: MouseEvent | KeyboardEvent) => { emit('pick', castArray(props.parsedValue), false) return } - const newMonth = props.date.startOf('month').month(month) - + const newMonth = getValidDateOfMonth( + props.date.year(), + month, + lang.value, + props.disabledDate + ) const newValue = hasClass(target, 'current') ? castArray(props.parsedValue).filter( - (d) => Number(d) !== Number(newMonth) + (d) => d?.month() !== newMonth.month() ) : castArray(props.parsedValue).concat([dayjs(newMonth)]) emit('pick', newValue) diff --git a/packages/components/date-picker/src/date-picker-com/basic-year-table.vue b/packages/components/date-picker/src/date-picker-com/basic-year-table.vue index d5a65decd5..5d5b74bbe1 100644 --- a/packages/components/date-picker/src/date-picker-com/basic-year-table.vue +++ b/packages/components/date-picker/src/date-picker-com/basic-year-table.vue @@ -34,6 +34,7 @@ import { useLocale, useNamespace } from '@element-plus/hooks' import { rangeArr } from '@element-plus/components/time-picker' import { castArray, hasClass } from '@element-plus/utils' import { basicYearTableProps } from '../props/basic-year-table' +import { getValidDateOfYear } from '../utils' import ElDatePickerCell from './basic-cell-render' type YearCell = { @@ -201,9 +202,14 @@ const handleYearTableClick = (event: MouseEvent | KeyboardEvent) => { emit('pick', castArray(props.parsedValue), false) return } + const vaildYear = getValidDateOfYear( + newDate.startOf('year'), + lang.value, + props.disabledDate + ) const newValue = hasClass(target, 'current') ? castArray(props.parsedValue).filter((d) => d?.year() !== selectedYear) - : castArray(props.parsedValue).concat([newDate]) + : castArray(props.parsedValue).concat([vaildYear]) emit('pick', newValue) } else { emit('pick', selectedYear) diff --git a/packages/components/date-picker/src/date-picker-com/panel-date-pick.vue b/packages/components/date-picker/src/date-picker-com/panel-date-pick.vue index f7110fd4d4..1c92090d77 100644 --- a/packages/components/date-picker/src/date-picker-com/panel-date-pick.vue +++ b/packages/components/date-picker/src/date-picker-com/panel-date-pick.vue @@ -224,6 +224,7 @@ import { } from '@element-plus/icons-vue' import { TOOLTIP_INJECTION_KEY } from '@element-plus/components/tooltip' import { panelDatePickProps } from '../props/panel-date-pick' +import { getValidDateOfMonth, getValidDateOfYear } from '../utils' import DateTable from './basic-date-table.vue' import MonthTable from './basic-month-table.vue' import YearTable from './basic-year-table.vue' @@ -429,12 +430,22 @@ const handleMonthPick = async ( keepOpen?: boolean ) => { if (selectionMode.value === 'month') { - innerDate.value = innerDate.value.startOf('month').month(month as number) + innerDate.value = getValidDateOfMonth( + innerDate.value.year(), + month as number, + lang.value, + disabledDate + ) emit(innerDate.value, false) } else if (selectionMode.value === 'months') { emit(month as MonthsPickerEmits, keepOpen ?? true) } else { - innerDate.value = innerDate.value.startOf('month').month(month as number) + innerDate.value = getValidDateOfMonth( + innerDate.value.year(), + month as number, + lang.value, + disabledDate + ) currentView.value = 'date' if (['month', 'year', 'date', 'week'].includes(selectionMode.value)) { emit(innerDate.value, true) @@ -450,12 +461,14 @@ const handleYearPick = async ( keepOpen?: boolean ) => { if (selectionMode.value === 'year') { - innerDate.value = innerDate.value.startOf('year').year(year as number) + const data = innerDate.value.startOf('year').year(year as number) + innerDate.value = getValidDateOfYear(data, lang.value, disabledDate) emit(innerDate.value, false) } else if (selectionMode.value === 'years') { emit(year as YearsPickerEmits, keepOpen ?? true) } else { - innerDate.value = innerDate.value.year(year as number) + const data = innerDate.value.year(year as number) + innerDate.value = getValidDateOfYear(data, lang.value, disabledDate) currentView.value = 'month' if (['month', 'year', 'date', 'week'].includes(selectionMode.value)) { emit(innerDate.value, true) diff --git a/packages/components/date-picker/src/props/shared.ts b/packages/components/date-picker/src/props/shared.ts index bc15a71236..86aac5d51c 100644 --- a/packages/components/date-picker/src/props/shared.ts +++ b/packages/components/date-picker/src/props/shared.ts @@ -21,9 +21,11 @@ export type RangeState = { selecting: boolean } +export type DisabledDateType = (date: Date) => boolean + export const datePickerSharedProps = buildProps({ disabledDate: { - type: definePropType<(date: Date) => boolean>(Function), + type: definePropType(Function), }, date: { type: definePropType(Object), diff --git a/packages/components/date-picker/src/utils.ts b/packages/components/date-picker/src/utils.ts index edd05a7eb5..337792c346 100644 --- a/packages/components/date-picker/src/utils.ts +++ b/packages/components/date-picker/src/utils.ts @@ -1,8 +1,10 @@ import dayjs from 'dayjs' import { isArray } from '@element-plus/utils' +import { rangeArr } from '@element-plus/components/time-picker' import type { Dayjs } from 'dayjs' import type { DateCell } from './date-picker.type' +import type { DisabledDateType } from './props/shared' type DayRange = [Dayjs | undefined, Dayjs | undefined] @@ -132,3 +134,46 @@ export const buildPickerTable = ( setRowMetadata?.(row) } } + +export const datesInMonth = (year: number, month: number, lang: string) => { + const firstDay = dayjs().locale(lang).startOf('month').month(month).year(year) + const numOfDays = firstDay.daysInMonth() + return rangeArr(numOfDays).map((n) => firstDay.add(n, 'day').toDate()) +} + +export const getValidDateOfMonth = ( + year: number, + month: number, + lang: string, + disabledDate?: DisabledDateType +) => { + const _value = dayjs().year(year).month(month).startOf('month') + const _date = datesInMonth(year, month, lang).find((date) => { + return !disabledDate?.(date) + }) + if (_date) { + return dayjs(_date).locale(lang) + } + return _value.locale(lang) +} + +export const getValidDateOfYear = ( + value: Dayjs, + lang: string, + disabledDate?: DisabledDateType +) => { + const year = value.year() + if (!disabledDate?.(value.toDate())) { + return value.locale(lang) + } + const month = value.month() + if (!datesInMonth(year, month, lang).every(disabledDate)) { + return getValidDateOfMonth(year, month, lang, disabledDate) + } + for (let i = 0; i < 12; i++) { + if (!datesInMonth(year, i, lang).every(disabledDate)) { + return getValidDateOfMonth(year, i, lang, disabledDate) + } + } + return value +}