vuecssuiant-designantdreactantantd-vueenterprisefrontendui-designvue-antdvue-antd-uivue3vuecomponent
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
371 lines
13 KiB
371 lines
13 KiB
import useMergedState from '../_util/hooks/useMergedState'; |
|
import { PickerPanel } from '../vc-picker'; |
|
import type { Locale } from '../vc-picker/interface'; |
|
import type { GenerateConfig } from '../vc-picker/generate'; |
|
import type { |
|
PickerPanelBaseProps as RCPickerPanelBaseProps, |
|
PickerPanelDateProps as RCPickerPanelDateProps, |
|
PickerPanelTimeProps as RCPickerPanelTimeProps, |
|
} from '../vc-picker/PickerPanel'; |
|
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver'; |
|
import enUS from './locale/en_US'; |
|
import CalendarHeader from './Header'; |
|
import type { CustomSlotsType, VueNode } from '../_util/type'; |
|
import type { App, PropType } from 'vue'; |
|
import { computed, defineComponent, toRef } from 'vue'; |
|
import useConfigInject from '../config-provider/hooks/useConfigInject'; |
|
import classNames from '../_util/classNames'; |
|
|
|
// CSSINJS |
|
import useStyle from './style'; |
|
|
|
type InjectDefaultProps<Props> = Omit< |
|
Props, |
|
'locale' | 'generateConfig' | 'prevIcon' | 'nextIcon' | 'superPrevIcon' | 'superNextIcon' |
|
> & { |
|
locale?: typeof enUS; |
|
size?: 'large' | 'default' | 'small'; |
|
}; |
|
|
|
export interface SelectInfo { |
|
source: 'year' | 'month' | 'date' | 'customize'; |
|
} |
|
|
|
// Picker Props |
|
export type PickerPanelBaseProps<DateType> = InjectDefaultProps<RCPickerPanelBaseProps<DateType>>; |
|
export type PickerPanelDateProps<DateType> = InjectDefaultProps<RCPickerPanelDateProps<DateType>>; |
|
export type PickerPanelTimeProps<DateType> = InjectDefaultProps<RCPickerPanelTimeProps<DateType>>; |
|
|
|
export type PickerProps<DateType> = |
|
| PickerPanelBaseProps<DateType> |
|
| PickerPanelDateProps<DateType> |
|
| PickerPanelTimeProps<DateType>; |
|
|
|
export type CalendarMode = 'year' | 'month'; |
|
export type HeaderRender<DateType> = (config: { |
|
value: DateType; |
|
type: CalendarMode; |
|
onChange: (date: DateType) => void; |
|
onTypeChange: (type: CalendarMode) => void; |
|
}) => VueNode; |
|
|
|
type CustomRenderType<DateType> = (config: { current: DateType }) => VueNode; |
|
|
|
export interface CalendarProps<DateType> { |
|
prefixCls?: string; |
|
locale?: typeof enUS; |
|
validRange?: [DateType, DateType]; |
|
disabledDate?: (date: DateType) => boolean; |
|
dateFullCellRender?: CustomRenderType<DateType>; |
|
dateCellRender?: CustomRenderType<DateType>; |
|
monthFullCellRender?: CustomRenderType<DateType>; |
|
monthCellRender?: CustomRenderType<DateType>; |
|
headerRender?: HeaderRender<DateType>; |
|
value?: DateType | string; |
|
defaultValue?: DateType | string; |
|
mode?: CalendarMode; |
|
fullscreen?: boolean; |
|
onChange?: (date: DateType | string) => void; |
|
'onUpdate:value'?: (date: DateType | string) => void; |
|
onPanelChange?: (date: DateType | string, mode: CalendarMode) => void; |
|
onSelect?: (date: DateType, selectInfo: SelectInfo) => void; |
|
valueFormat?: string; |
|
} |
|
|
|
function generateCalendar< |
|
DateType, |
|
Props extends CalendarProps<DateType> = CalendarProps<DateType>, |
|
>(generateConfig: GenerateConfig<DateType>) { |
|
function isSameYear(date1: DateType, date2: DateType) { |
|
return date1 && date2 && generateConfig.getYear(date1) === generateConfig.getYear(date2); |
|
} |
|
|
|
function isSameMonth(date1: DateType, date2: DateType) { |
|
return ( |
|
isSameYear(date1, date2) && generateConfig.getMonth(date1) === generateConfig.getMonth(date2) |
|
); |
|
} |
|
|
|
function isSameDate(date1: DateType, date2: DateType) { |
|
return ( |
|
isSameMonth(date1, date2) && generateConfig.getDate(date1) === generateConfig.getDate(date2) |
|
); |
|
} |
|
|
|
const Calendar = defineComponent<Props>({ |
|
name: 'ACalendar', |
|
inheritAttrs: false, |
|
props: { |
|
prefixCls: String, |
|
locale: { type: Object as PropType<Props['locale']>, default: undefined as Props['locale'] }, |
|
validRange: { type: Array as PropType<DateType[]>, default: undefined }, |
|
disabledDate: { type: Function as PropType<Props['disabledDate']>, default: undefined }, |
|
dateFullCellRender: { |
|
type: Function as PropType<Props['dateFullCellRender']>, |
|
default: undefined, |
|
}, |
|
dateCellRender: { type: Function as PropType<Props['dateCellRender']>, default: undefined }, |
|
monthFullCellRender: { |
|
type: Function as PropType<Props['monthFullCellRender']>, |
|
default: undefined, |
|
}, |
|
monthCellRender: { type: Function as PropType<Props['monthCellRender']>, default: undefined }, |
|
headerRender: { type: Function as PropType<Props['headerRender']>, default: undefined }, |
|
value: { |
|
type: [Object, String] as PropType<Props['value']>, |
|
default: undefined as Props['value'], |
|
}, |
|
defaultValue: { |
|
type: [Object, String] as PropType<Props['defaultValue']>, |
|
default: undefined as Props['defaultValue'], |
|
}, |
|
mode: { type: String as PropType<Props['mode']>, default: undefined }, |
|
fullscreen: { type: Boolean as PropType<Props['fullscreen']>, default: undefined }, |
|
onChange: { type: Function as PropType<Props['onChange']>, default: undefined }, |
|
'onUpdate:value': { type: Function as PropType<Props['onUpdate:value']>, default: undefined }, |
|
onPanelChange: { type: Function as PropType<Props['onPanelChange']>, default: undefined }, |
|
onSelect: { type: Function as PropType<Props['onSelect']>, default: undefined }, |
|
valueFormat: { type: String, default: undefined }, |
|
} as any, |
|
slots: Object as CustomSlotsType<{ |
|
dateFullCellRender?: { current: DateType }; |
|
dateCellRender?: { current: DateType }; |
|
monthFullCellRender?: { current: DateType }; |
|
monthCellRender?: { current: DateType }; |
|
headerRender?: { |
|
value: DateType; |
|
type: CalendarMode; |
|
onChange: (date: DateType) => void; |
|
onTypeChange: (type: CalendarMode) => void; |
|
}; |
|
default: any; |
|
}>, |
|
setup(p, { emit, slots, attrs }) { |
|
const props = p as unknown as Props; |
|
const { prefixCls, direction } = useConfigInject('picker', props); |
|
|
|
// style |
|
const [wrapSSR, hashId] = useStyle(prefixCls); |
|
|
|
const calendarPrefixCls = computed(() => `${prefixCls.value}-calendar`); |
|
const maybeToString = (date: DateType) => { |
|
return props.valueFormat ? generateConfig.toString(date, props.valueFormat) : date; |
|
}; |
|
const value = computed(() => { |
|
if (props.value) { |
|
return props.valueFormat |
|
? (generateConfig.toDate(props.value, props.valueFormat) as DateType) |
|
: (props.value as DateType); |
|
} |
|
return (props.value === '' ? undefined : props.value) as DateType; |
|
}); |
|
const defaultValue = computed(() => { |
|
if (props.defaultValue) { |
|
return props.valueFormat |
|
? (generateConfig.toDate(props.defaultValue, props.valueFormat) as DateType) |
|
: (props.defaultValue as DateType); |
|
} |
|
return (props.defaultValue === '' ? undefined : props.defaultValue) as DateType; |
|
}); |
|
|
|
// Value |
|
const [mergedValue, setMergedValue] = useMergedState( |
|
() => value.value || generateConfig.getNow(), |
|
{ |
|
defaultValue: defaultValue.value, |
|
value, |
|
}, |
|
); |
|
|
|
// Mode |
|
const [mergedMode, setMergedMode] = useMergedState('month', { |
|
value: toRef(props, 'mode'), |
|
}); |
|
|
|
const panelMode = computed(() => (mergedMode.value === 'year' ? 'month' : 'date')); |
|
|
|
const mergedDisabledDate = computed(() => { |
|
return (date: DateType) => { |
|
const notInRange = props.validRange |
|
? generateConfig.isAfter(props.validRange[0], date) || |
|
generateConfig.isAfter(date, props.validRange[1]) |
|
: false; |
|
return notInRange || !!props.disabledDate?.(date); |
|
}; |
|
}); |
|
|
|
// ====================== Events ====================== |
|
const triggerPanelChange = (date: DateType, newMode: CalendarMode) => { |
|
emit('panelChange', maybeToString(date), newMode); |
|
}; |
|
|
|
const triggerChange = (date: DateType) => { |
|
setMergedValue(date); |
|
|
|
if (!isSameDate(date, mergedValue.value)) { |
|
// Trigger when month panel switch month |
|
if ( |
|
(panelMode.value === 'date' && !isSameMonth(date, mergedValue.value)) || |
|
(panelMode.value === 'month' && !isSameYear(date, mergedValue.value)) |
|
) { |
|
triggerPanelChange(date, mergedMode.value); |
|
} |
|
const val = maybeToString(date); |
|
emit('update:value', val); |
|
emit('change', val); |
|
} |
|
}; |
|
|
|
const triggerModeChange = (newMode: CalendarMode) => { |
|
setMergedMode(newMode); |
|
triggerPanelChange(mergedValue.value, newMode); |
|
}; |
|
|
|
const onInternalSelect = (date: DateType, source: SelectInfo['source']) => { |
|
triggerChange(date); |
|
emit('select', maybeToString(date), { source }); |
|
}; |
|
// ====================== Locale ====================== |
|
const defaultLocale = computed(() => { |
|
const { locale } = props; |
|
const result = { |
|
...enUS, |
|
...locale, |
|
}; |
|
result.lang = { |
|
...result.lang, |
|
...(locale || {}).lang, |
|
}; |
|
return result; |
|
}); |
|
|
|
const [mergedLocale] = useLocaleReceiver('Calendar', defaultLocale) as [typeof defaultLocale]; |
|
|
|
return () => { |
|
const today = generateConfig.getNow(); |
|
const { |
|
dateFullCellRender = slots?.dateFullCellRender, |
|
dateCellRender = slots?.dateCellRender, |
|
monthFullCellRender = slots?.monthFullCellRender, |
|
monthCellRender = slots?.monthCellRender, |
|
headerRender = slots?.headerRender, |
|
fullscreen = true, |
|
validRange, |
|
} = props; |
|
// ====================== Render ====================== |
|
const dateRender = ({ current: date }) => { |
|
if (dateFullCellRender) { |
|
return dateFullCellRender({ current: date }); |
|
} |
|
return ( |
|
<div |
|
class={classNames( |
|
`${prefixCls.value}-cell-inner`, |
|
`${calendarPrefixCls.value}-date`, |
|
{ |
|
[`${calendarPrefixCls.value}-date-today`]: isSameDate(today, date), |
|
}, |
|
)} |
|
> |
|
<div class={`${calendarPrefixCls.value}-date-value`}> |
|
{String(generateConfig.getDate(date)).padStart(2, '0')} |
|
</div> |
|
<div class={`${calendarPrefixCls.value}-date-content`}> |
|
{dateCellRender && dateCellRender({ current: date })} |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|
|
const monthRender = ({ current: date }, locale: Locale) => { |
|
if (monthFullCellRender) { |
|
return monthFullCellRender({ current: date }); |
|
} |
|
|
|
const months = locale.shortMonths || generateConfig.locale.getShortMonths!(locale.locale); |
|
|
|
return ( |
|
<div |
|
class={classNames( |
|
`${prefixCls.value}-cell-inner`, |
|
`${calendarPrefixCls.value}-date`, |
|
{ |
|
[`${calendarPrefixCls.value}-date-today`]: isSameMonth(today, date), |
|
}, |
|
)} |
|
> |
|
<div class={`${calendarPrefixCls.value}-date-value`}> |
|
{months[generateConfig.getMonth(date)]} |
|
</div> |
|
<div class={`${calendarPrefixCls.value}-date-content`}> |
|
{monthCellRender && monthCellRender({ current: date })} |
|
</div> |
|
</div> |
|
); |
|
}; |
|
return wrapSSR( |
|
<div |
|
{...attrs} |
|
class={classNames( |
|
calendarPrefixCls.value, |
|
{ |
|
[`${calendarPrefixCls.value}-full`]: fullscreen, |
|
[`${calendarPrefixCls.value}-mini`]: !fullscreen, |
|
[`${calendarPrefixCls.value}-rtl`]: direction.value === 'rtl', |
|
}, |
|
attrs.class, |
|
hashId.value, |
|
)} |
|
> |
|
{headerRender ? ( |
|
headerRender({ |
|
value: mergedValue.value, |
|
type: mergedMode.value, |
|
onChange: nextDate => { |
|
onInternalSelect(nextDate, 'customize'); |
|
}, |
|
onTypeChange: triggerModeChange, |
|
}) |
|
) : ( |
|
<CalendarHeader |
|
prefixCls={calendarPrefixCls.value} |
|
value={mergedValue.value} |
|
generateConfig={generateConfig} |
|
mode={mergedMode.value} |
|
fullscreen={fullscreen} |
|
locale={mergedLocale.value.lang} |
|
validRange={validRange} |
|
onChange={onInternalSelect} |
|
onModeChange={triggerModeChange} |
|
/> |
|
)} |
|
<PickerPanel |
|
value={mergedValue.value} |
|
prefixCls={prefixCls.value} |
|
locale={mergedLocale.value.lang} |
|
generateConfig={generateConfig} |
|
dateRender={dateRender} |
|
monthCellRender={obj => monthRender(obj, mergedLocale.value.lang)} |
|
onSelect={nextDate => { |
|
onInternalSelect(nextDate, panelMode.value); |
|
}} |
|
mode={panelMode.value} |
|
picker={panelMode.value} |
|
disabledDate={mergedDisabledDate.value} |
|
hideHeader |
|
/> |
|
</div>, |
|
); |
|
}; |
|
}, |
|
}); |
|
|
|
Calendar.install = function (app: App) { |
|
app.component(Calendar.name, Calendar); |
|
return app; |
|
}; |
|
|
|
return Calendar; |
|
} |
|
|
|
export default generateCalendar;
|
|
|