diff --git a/.gitignore b/.gitignore index cfde826..09e63ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea .vscode .cache .DS_Store diff --git a/__tests__/Calendar.test.ts b/__tests__/Calendar.test.ts index 4ff7c50..eef4f6d 100644 --- a/__tests__/Calendar.test.ts +++ b/__tests__/Calendar.test.ts @@ -66,8 +66,12 @@ describe('CalendarPanel', () => { wrapper = mount(Calendar); const td = wrapper.find('.mx-table-date td:nth-child(6)'); expect(td.classes()).not.toContain('active'); - await wrapper.setProps({ value: new Date(2019, 9, 4) }); + await wrapper.setProps({ + value: new Date(2019, 9, 4), + holidayDate: () => true, + }); expect(td.classes()).toContain('active'); + expect(td.classes()).toContain('holiday'); }); it('prop: disabledDate', () => { @@ -96,6 +100,58 @@ describe('CalendarPanel', () => { expect(mockFn).not.toHaveBeenCalled(); }); + [true, false].forEach((holidayClickable) => { + it('props: holidayClickable', () => { + const holidayDate = (date: Date) => { + return date < new Date(2019, 9, 1) || date > new Date(2019, 9, 20); + }; + const mockFn = jest.fn(); + wrapper = mount(Calendar, { + props: { + value: new Date(2019, 9, 1), + ['onUpdate:value']: mockFn, + holidayClickable: holidayClickable, + holidayDate: holidayDate, + }, + }); + wrapper.find('.mx-table-date td').trigger('click'); + + if (holidayClickable) { + expect(mockFn).toHaveBeenCalled(); + } else { + expect(mockFn).not.toHaveBeenCalled(); + } + }); + }); + + it('prop: holidayDate', () => { + const holidayDate = (date: Date) => { + return date < new Date(2019, 9, 1) || date > new Date(2019, 9, 20); + }; + const mockFn = jest.fn(); + wrapper = mount(Calendar, { + props: { + value: new Date(2019, 9, 4), + ['onUpdate:value']: mockFn, + holidayClickable: false, + holidayDate: holidayDate, + }, + }); + const tds = wrapper.findAll('.mx-table-date td'); + for (let i = 0; i < 42; i++) { + const td = tds[i]; + const classes = td.classes(); + if (i < 2 || i > 21) { + expect(classes).toContain('holiday'); + } else { + expect(classes).not.toContain('holiday'); + } + } + + tds[1].trigger('click'); + expect(mockFn).not.toHaveBeenCalled(); + }); + const renderType = (type: 'date' | 'month' | 'year') => { it(`prop: type=${type}`, () => { wrapper = mount(Calendar, { diff --git a/lib/DatePicker.tsx b/lib/DatePicker.tsx index 63f8ae8..3c2af4e 100644 --- a/lib/DatePicker.tsx +++ b/lib/DatePicker.tsx @@ -36,6 +36,7 @@ const booleanKeys = keys, bo 'showTimePanel', 'showWeekNumber', 'use12h', + 'holidayClickable', ]); const formatMap = { diff --git a/lib/Picker.tsx b/lib/Picker.tsx index 61d497e..09d4117 100644 --- a/lib/Picker.tsx +++ b/lib/Picker.tsx @@ -25,6 +25,8 @@ export interface PickerBaseProps { confirmText?: string; shortcuts?: Array<{ text: string; onClick: () => Date | Date[] }>; disabledDate?: (v: Date) => boolean; + holidayDate?: (v: Date) => boolean; + holidayClickable?: boolean; disabledTime?: (v: Date) => boolean; onClose?: () => void; onOpen?: () => void; @@ -49,6 +51,8 @@ function Picker(originalProps: PickerProps, { slots }: SetupContext) { format: 'YYYY-MM-DD', type: 'date' as PickerType, disabledDate: () => false, + holidayDate: () => false, + holidayClickable: false, disabledTime: () => false, confirmText: 'OK', }); @@ -244,6 +248,8 @@ function Picker(originalProps: PickerProps, { slots }: SetupContext) { formatDate={formatDate} parseDate={parseDate} disabledDate={disabledDateTime} + holidayDate={props.holidayDate} + holidayClickable={props.holidayClickable} onChange={emitValue} onClick={openPopup} onFocus={openPopup} @@ -293,6 +299,8 @@ const pickerbaseProps = keys()([ 'onChange', 'onUpdate:open', 'onUpdate:value', + 'holidayClickable', + 'holidayDate', ]); const pickerProps = [...pickerbaseProps, ...pickerInputBaseProps]; diff --git a/lib/PickerInput.tsx b/lib/PickerInput.tsx index 5b84de3..bafcc31 100644 --- a/lib/PickerInput.tsx +++ b/lib/PickerInput.tsx @@ -26,6 +26,8 @@ export interface PickerInputProps extends PickerInputBaseProps { formatDate: (v: Date) => string; parseDate: (v: string) => Date; disabledDate: (v: Date) => boolean; + holidayDate: (v: Date) => boolean; + holidayClickable?: boolean; onChange: (v: Date | Date[] | null | null[]) => void; onFocus: () => void; onBlur: () => void; @@ -39,6 +41,7 @@ function PickerInput(originalProps: PickerInputProps, { slots }: SetupContext) { clearable: true, range: false, multiple: false, + holidayClickable: true, }); const prefixClass = usePrefixClass(); @@ -185,6 +188,8 @@ const pickerInputProps = keys()([ 'onFocus', 'onBlur', 'onClick', + 'holidayDate', + 'holidayClickable', ...pickerInputBaseProps, ]); diff --git a/lib/calendar/Calendar.tsx b/lib/calendar/Calendar.tsx index e8a3968..f22c20f 100644 --- a/lib/calendar/Calendar.tsx +++ b/lib/calendar/Calendar.tsx @@ -1,4 +1,5 @@ import { computed, ref, watchEffect } from 'vue'; +import { PanelType, PickerType } from '../type'; import { getValidDate, isValidDate, @@ -8,11 +9,10 @@ import { startOfMonth, startOfYear, } from '../util/date'; +import { defineVueComponent, keys, withDefault } from '../vueUtil'; import { TableDate } from './TableDate'; import { TableMonth } from './TableMonth'; import { TableYear } from './TableYear'; -import { PanelType, PickerType } from '../type'; -import { defineVueComponent, keys, withDefault } from '../vueUtil'; export interface CalendarProps { type?: PickerType; @@ -20,6 +20,8 @@ export interface CalendarProps { defaultValue?: Date; defaultPanel?: PickerType; disabledDate?: (value: Date, innerValue?: Date[]) => boolean; + holidayClickable?: boolean; + holidayDate?: (value: Date, innerValue?: Date[]) => boolean; getClasses?: (value: Date, innerValue: Date[], classes: string) => string[] | string; calendar?: Date; multiple?: boolean; @@ -40,6 +42,8 @@ function Calendar(originalProps: CalendarProps) { defaultValue: startOfDay(new Date()), type: 'date' as PickerType, disabledDate: () => false, + holidayClickable: false, + holidayDate: () => false, getClasses: () => [], titleFormat: 'YYYY-MM-DD', }); @@ -85,8 +89,19 @@ function Calendar(originalProps: CalendarProps) { return props.disabledDate(new Date(date), innerValue.value); }; + const isHoliday = (date: Date) => { + return props.holidayDate(new Date(date), innerValue.value); + }; + + const isClickable = (date: Date) => { + if (isDisabled(date)) { + return false; + } + return !(!props.holidayClickable && isHoliday(date)); + }; + const emitDate = (date: Date, type: string) => { - if (!isDisabled(date)) { + if (isClickable(date)) { props.onPick?.(date); if (props.multiple === true) { const nextDates = innerValue.value.filter((v) => v.getTime() !== date.getTime()); @@ -131,6 +146,9 @@ function Calendar(originalProps: CalendarProps) { }; const getCellClasses = (cellDate: Date, classes: string[] = []) => { + if (isHoliday(cellDate)) { + classes.push('holiday'); + } if (isDisabled(cellDate)) { classes.push('disabled'); } else if (innerValue.value.some((v) => v.getTime() === cellDate.getTime())) { @@ -222,6 +240,8 @@ export const calendarProps = keys()([ 'defaultValue', 'defaultPanel', 'disabledDate', + 'holidayDate', + 'holidayClickable', 'getClasses', 'calendar', 'multiple', diff --git a/src/App.vue b/src/App.vue index 039838d..32a64a0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,88 +1,101 @@