import type { DisabledTimes, PanelMode, PickerMode, RangeValue, EventValue } from './interface'; import type { PickerBaseProps, PickerDateProps, PickerTimeProps } from './Picker'; import type { SharedTimeProps } from './panels/TimePanel'; import PickerTrigger from './PickerTrigger'; import PickerPanel from './PickerPanel'; import usePickerInput from './hooks/usePickerInput'; import getDataOrAriaProps, { toArray, getValue, updateValues } from './utils/miscUtil'; import { getDefaultFormat, getInputSize, elementsContains } from './utils/uiUtil'; import type { ContextOperationRefProps } from './PanelContext'; import { useProvidePanel } from './PanelContext'; import { isEqual, getClosingViewDate, isSameDate, isSameWeek, isSameQuarter, formatValue, parseValue, } from './utils/dateUtil'; import useValueTexts from './hooks/useValueTexts'; import useTextValueMapping from './hooks/useTextValueMapping'; import type { GenerateConfig } from './generate'; import type { PickerPanelProps } from '.'; import { RangeContextProvider } from './RangeContext'; import useRangeDisabled from './hooks/useRangeDisabled'; import getExtraFooter from './utils/getExtraFooter'; import getRanges from './utils/getRanges'; import useRangeViewDates from './hooks/useRangeViewDates'; import type { DateRender } from './panels/DatePanel/DateBody'; import useHoverValue from './hooks/useHoverValue'; import type { VueNode } from '../_util/type'; import type { ChangeEvent, FocusEventHandler, MouseEventHandler } from '../_util/EventInterface'; import { computed, defineComponent, ref, toRef, watch, watchEffect } from 'vue'; import useMergedState from '../_util/hooks/useMergedState'; import { warning } from '../vc-util/warning'; import useState from '../_util/hooks/useState'; import classNames from '../_util/classNames'; import { useProviderTrigger } from '../vc-trigger/context'; import { legacyPropsWarning } from './utils/warnUtil'; import { useElementSize } from '../_util/hooks/_vueuse/useElementSize'; function reorderValues( values: RangeValue, generateConfig: GenerateConfig, ): RangeValue { if (values && values[0] && values[1] && generateConfig.isAfter(values[0], values[1])) { return [values[1], values[0]]; } return values; } function canValueTrigger( value: EventValue, index: number, disabled: [boolean, boolean], allowEmpty?: [boolean, boolean] | null, ): boolean { if (value) { return true; } if (allowEmpty && allowEmpty[index]) { return true; } if (disabled[(index + 1) % 2]) { return true; } return false; } export type RangeType = 'start' | 'end'; export type RangeInfo = { range: RangeType; }; export type RangeDateRender = (props: { current: DateType; today: DateType; info: RangeInfo; }) => VueNode; export type RangePickerSharedProps = { id?: string; value?: RangeValue; defaultValue?: RangeValue; defaultPickerValue?: [DateType, DateType]; placeholder?: [string, string]; disabled?: boolean | [boolean, boolean]; disabledTime?: (date: EventValue, type: RangeType) => DisabledTimes; ranges?: Record< string, Exclude, null> | (() => Exclude, null>) >; separator?: VueNode; allowEmpty?: [boolean, boolean]; mode?: [PanelMode, PanelMode]; onChange?: (values: RangeValue, formatString: [string, string]) => void; onCalendarChange?: ( values: RangeValue, formatString: [string, string], info: RangeInfo, ) => void; onPanelChange?: (values: RangeValue, modes: [PanelMode, PanelMode]) => void; onFocus?: FocusEventHandler; onBlur?: FocusEventHandler; onMousedown?: MouseEventHandler; onMouseup?: MouseEventHandler; onMouseenter?: MouseEventHandler; onMouseleave?: MouseEventHandler; onClick?: MouseEventHandler; onOk?: (dates: RangeValue) => void; direction?: 'ltr' | 'rtl'; autocomplete?: string; /** @private Internal control of active picker. Do not use since it's private usage */ activePickerIndex?: 0 | 1; dateRender?: RangeDateRender; panelRender?: (originPanel: VueNode) => VueNode; }; type OmitPickerProps = Omit< Props, | 'value' | 'defaultValue' | 'defaultPickerValue' | 'placeholder' | 'disabled' | 'disabledTime' | 'showToday' | 'showTime' | 'mode' | 'onChange' | 'onSelect' | 'onPanelChange' | 'pickerValue' | 'onPickerValueChange' | 'onOk' | 'dateRender' >; type RangeShowTimeObject = Omit, 'defaultValue'> & { defaultValue?: DateType[]; }; export type RangePickerBaseProps = {} & RangePickerSharedProps & OmitPickerProps>; export type RangePickerDateProps = { showTime?: boolean | RangeShowTimeObject; } & RangePickerSharedProps & OmitPickerProps>; export type RangePickerTimeProps = { order?: boolean; } & RangePickerSharedProps & OmitPickerProps>; export type RangePickerProps = | RangePickerBaseProps | RangePickerDateProps | RangePickerTimeProps; // TMP type to fit for ts 3.9.2 type OmitType = Omit, 'picker'> & Omit, 'picker'> & Omit, 'picker'>; type MergedRangePickerProps = { picker?: PickerMode; } & OmitType; function RangerPicker() { return defineComponent>({ name: 'RangerPicker', inheritAttrs: false, props: [ 'prefixCls', 'id', 'popupStyle', 'dropdownClassName', 'transitionName', 'dropdownAlign', 'getPopupContainer', 'generateConfig', 'locale', 'placeholder', 'autofocus', 'disabled', 'format', 'picker', 'showTime', 'showNow', 'showHour', 'showMinute', 'showSecond', 'use12Hours', 'separator', 'value', 'defaultValue', 'defaultPickerValue', 'open', 'defaultOpen', 'disabledDate', 'disabledTime', 'dateRender', 'panelRender', 'ranges', 'allowEmpty', 'allowClear', 'suffixIcon', 'clearIcon', 'pickerRef', 'inputReadOnly', 'mode', 'renderExtraFooter', 'onChange', 'onOpenChange', 'onPanelChange', 'onCalendarChange', 'onFocus', 'onBlur', 'onMousedown', 'onMouseup', 'onMouseenter', 'onMouseleave', 'onClick', 'onOk', 'onKeydown', 'components', 'order', 'direction', 'activePickerIndex', 'autocomplete', 'minuteStep', 'hourStep', 'secondStep', 'hideDisabledOptions', 'disabledMinutes', ] as any, setup(props, { attrs, expose }) { const needConfirmButton = computed( () => (props.picker === 'date' && !!props.showTime) || props.picker === 'time', ); const getPortal = useProviderTrigger(); // We record opened status here in case repeat open with picker const openRecordsRef = ref>({}); const containerRef = ref(null); const panelDivRef = ref(null); const startInputDivRef = ref(null); const endInputDivRef = ref(null); const separatorRef = ref(null); const startInputRef = ref(null); const endInputRef = ref(null); const arrowRef = ref(null); // ============================ Warning ============================ if (process.env.NODE_ENV !== 'production') { legacyPropsWarning(props); } // ============================= Misc ============================== const formatList = computed(() => toArray( getDefaultFormat(props.format, props.picker, props.showTime, props.use12Hours), ), ); // Active picker const [mergedActivePickerIndex, setMergedActivePickerIndex] = useMergedState<0 | 1>(0, { value: toRef(props, 'activePickerIndex'), }); // Operation ref const operationRef = ref(null); const mergedDisabled = computed<[boolean, boolean]>(() => { const { disabled } = props; if (Array.isArray(disabled)) { return disabled; } return [disabled || false, disabled || false]; }); // ============================= Value ============================= const [mergedValue, setInnerValue] = useMergedState>(null, { value: toRef(props, 'value'), defaultValue: props.defaultValue, postState: values => props.picker === 'time' && !props.order ? values : reorderValues(values, props.generateConfig), }); // =========================== View Date =========================== // Config view panel const [startViewDate, endViewDate, setViewDate] = useRangeViewDates({ values: mergedValue, picker: toRef(props, 'picker'), defaultDates: props.defaultPickerValue, generateConfig: toRef(props, 'generateConfig'), }); // ========================= Select Values ========================= const [selectedValue, setSelectedValue] = useMergedState(mergedValue.value, { postState: values => { let postValues = values; if (mergedDisabled.value[0] && mergedDisabled.value[1]) { return postValues; } // Fill disabled unit for (let i = 0; i < 2; i += 1) { if (mergedDisabled[i] && !getValue(postValues, i) && !getValue(props.allowEmpty, i)) { postValues = updateValues(postValues, props.generateConfig.getNow(), i); } } return postValues; }, }); // ============================= Modes ============================= const [mergedModes, setInnerModes] = useMergedState<[PanelMode, PanelMode]>( [props.picker, props.picker], { value: toRef(props, 'mode'), }, ); watch( () => props.picker, () => { setInnerModes([props.picker, props.picker]); }, ); const triggerModesChange = (modes: [PanelMode, PanelMode], values: RangeValue) => { setInnerModes(modes); props.onPanelChange?.(values, modes); }; // ========================= Disable Date ========================== const [disabledStartDate, disabledEndDate] = useRangeDisabled( { picker: toRef(props, 'picker'), selectedValue, locale: toRef(props, 'locale'), disabled: mergedDisabled, disabledDate: toRef(props, 'disabledDate'), generateConfig: toRef(props, 'generateConfig'), }, openRecordsRef, ); // ============================= Open ============================== const [mergedOpen, triggerInnerOpen] = useMergedState(false, { value: toRef(props, 'open'), defaultValue: props.defaultOpen, postState: postOpen => mergedDisabled.value[mergedActivePickerIndex.value] ? false : postOpen, onChange: newOpen => { props.onOpenChange?.(newOpen); if (!newOpen && operationRef.value && operationRef.value.onClose) { operationRef.value.onClose(); } }, }); const startOpen = computed(() => mergedOpen.value && mergedActivePickerIndex.value === 0); const endOpen = computed(() => mergedOpen.value && mergedActivePickerIndex.value === 1); const panelLeft = ref(0); const arrowLeft = ref(0); // ============================= Popup ============================= // Popup min width const popupMinWidth = ref(0); const { width: containerWidth } = useElementSize(containerRef); watch([mergedOpen, containerWidth], () => { if (!mergedOpen.value && containerRef.value) { popupMinWidth.value = containerWidth.value; } }); const { width: panelDivWidth } = useElementSize(panelDivRef); const { width: arrowWidth } = useElementSize(arrowRef); const { width: startInputDivWidth } = useElementSize(startInputDivRef); const { width: separatorWidth } = useElementSize(separatorRef); watch( [ mergedActivePickerIndex, mergedOpen, panelDivWidth, arrowWidth, startInputDivWidth, separatorWidth, () => props.direction, ], () => { arrowLeft.value = 0; if (mergedOpen.value && mergedActivePickerIndex.value) { if (startInputDivRef.value && separatorRef.value && panelDivRef.value) { arrowLeft.value = startInputDivWidth.value + separatorWidth.value; if ( panelDivWidth.value && arrowWidth.value && arrowLeft.value > panelDivWidth.value - arrowWidth.value - (props.direction === 'rtl' || arrowRef.value.offsetLeft > arrowLeft.value ? 0 : arrowRef.value.offsetLeft) ) { panelLeft.value = arrowLeft.value; } } } else if (mergedActivePickerIndex.value === 0) { panelLeft.value = 0; } }, { immediate: true }, ); // ============================ Trigger ============================ const triggerRef = ref(); function triggerOpen(newOpen: boolean, index: 0 | 1) { if (newOpen) { clearTimeout(triggerRef.value); openRecordsRef.value[index] = true; setMergedActivePickerIndex(index); triggerInnerOpen(newOpen); // Open to reset view date if (!mergedOpen.value) { setViewDate(null, index); } } else if (mergedActivePickerIndex.value === index) { triggerInnerOpen(newOpen); // Clean up async // This makes ref not quick refresh in case user open another input with blur trigger const openRecords = openRecordsRef.value; triggerRef.value = setTimeout(() => { if (openRecords === openRecordsRef.value) { openRecordsRef.value = {}; } }); } } function triggerOpenAndFocus(index: 0 | 1) { triggerOpen(true, index); // Use setTimeout to make sure panel DOM exists setTimeout(() => { const inputRef = [startInputRef, endInputRef][index]; if (inputRef.value) { inputRef.value.focus(); } }, 0); } function triggerChange(newValue: RangeValue, sourceIndex: 0 | 1) { let values = newValue; let startValue = getValue(values, 0); let endValue = getValue(values, 1); const { generateConfig, locale, picker, order, onCalendarChange, allowEmpty, onChange, showTime, } = props; // >>>>> Format start & end values if (startValue && endValue && generateConfig.isAfter(startValue, endValue)) { if ( // WeekPicker only compare week (picker === 'week' && !isSameWeek(generateConfig, locale.locale, startValue, endValue)) || // QuotaPicker only compare week (picker === 'quarter' && !isSameQuarter(generateConfig, startValue, endValue)) || // Other non-TimePicker compare date (picker !== 'week' && picker !== 'quarter' && picker !== 'time' && !(showTime ? isEqual(generateConfig, startValue, endValue) : isSameDate(generateConfig, startValue, endValue))) ) { // Clean up end date when start date is after end date if (sourceIndex === 0) { values = [startValue, null]; endValue = null; } else { startValue = null; values = [null, endValue]; } // Clean up cache since invalidate openRecordsRef.value = { [sourceIndex]: true, }; } else if (picker !== 'time' || order !== false) { // Reorder when in same date values = reorderValues(values, generateConfig); } } setSelectedValue(values); const startStr = values && values[0] ? formatValue(values[0], { generateConfig, locale, format: formatList.value[0] }) : ''; const endStr = values && values[1] ? formatValue(values[1], { generateConfig, locale, format: formatList.value[0] }) : ''; if (onCalendarChange) { const info: RangeInfo = { range: sourceIndex === 0 ? 'start' : 'end' }; onCalendarChange(values, [startStr, endStr], info); } // >>>>> Trigger `onChange` event const canStartValueTrigger = canValueTrigger( startValue, 0, mergedDisabled.value, allowEmpty, ); const canEndValueTrigger = canValueTrigger(endValue, 1, mergedDisabled.value, allowEmpty); const canTrigger = values === null || (canStartValueTrigger && canEndValueTrigger); if (canTrigger) { // Trigger onChange only when value is validate setInnerValue(values); if ( onChange && (!isEqual(generateConfig, getValue(mergedValue.value, 0), startValue) || !isEqual(generateConfig, getValue(mergedValue.value, 1), endValue)) ) { onChange(values, [startStr, endStr]); } } // >>>>> Open picker when // Always open another picker if possible let nextOpenIndex: 0 | 1 = null; if (sourceIndex === 0 && !mergedDisabled.value[1]) { nextOpenIndex = 1; } else if (sourceIndex === 1 && !mergedDisabled.value[0]) { nextOpenIndex = 0; } if ( nextOpenIndex !== null && nextOpenIndex !== mergedActivePickerIndex.value && (!openRecordsRef.value[nextOpenIndex] || !getValue(values, nextOpenIndex)) && getValue(values, sourceIndex) ) { // Delay to focus to avoid input blur trigger expired selectedValues triggerOpenAndFocus(nextOpenIndex); } else { triggerOpen(false, sourceIndex); } } const forwardKeydown = (e: KeyboardEvent) => { if (mergedOpen && operationRef.value && operationRef.value.onKeydown) { // Let popup panel handle keyboard return operationRef.value.onKeydown(e); } /* istanbul ignore next */ /* eslint-disable no-lone-blocks */ { warning( false, 'Picker not correct forward Keydown operation. Please help to fire issue about this.', ); return false; } }; // ============================= Text ============================== const sharedTextHooksProps = { formatList, generateConfig: toRef(props, 'generateConfig'), locale: toRef(props, 'locale'), }; const [startValueTexts, firstStartValueText] = useValueTexts( computed(() => getValue(selectedValue.value, 0)), sharedTextHooksProps, ); const [endValueTexts, firstEndValueText] = useValueTexts( computed(() => getValue(selectedValue.value, 1)), sharedTextHooksProps, ); const onTextChange = (newText: string, index: 0 | 1) => { const inputDate = parseValue(newText, { locale: props.locale, formatList: formatList.value, generateConfig: props.generateConfig, }); const disabledFunc = index === 0 ? disabledStartDate : disabledEndDate; if (inputDate && !disabledFunc(inputDate)) { setSelectedValue(updateValues(selectedValue.value, inputDate, index)); setViewDate(inputDate, index); } }; const [startText, triggerStartTextChange, resetStartText] = useTextValueMapping({ valueTexts: startValueTexts, onTextChange: newText => onTextChange(newText, 0), }); const [endText, triggerEndTextChange, resetEndText] = useTextValueMapping({ valueTexts: endValueTexts, onTextChange: newText => onTextChange(newText, 1), }); const [rangeHoverValue, setRangeHoverValue] = useState>(null); // ========================== Hover Range ========================== const [hoverRangedValue, setHoverRangedValue] = useState>(null); const [startHoverValue, onStartEnter, onStartLeave] = useHoverValue( startText, sharedTextHooksProps, ); const [endHoverValue, onEndEnter, onEndLeave] = useHoverValue(endText, sharedTextHooksProps); const onDateMouseenter = (date: DateType) => { setHoverRangedValue(updateValues(selectedValue.value, date, mergedActivePickerIndex.value)); if (mergedActivePickerIndex.value === 0) { onStartEnter(date); } else { onEndEnter(date); } }; const onDateMouseleave = () => { setHoverRangedValue(updateValues(selectedValue.value, null, mergedActivePickerIndex.value)); if (mergedActivePickerIndex.value === 0) { onStartLeave(); } else { onEndLeave(); } }; // ============================= Input ============================= const getSharedInputHookProps = (index: 0 | 1, resetText: () => void) => ({ forwardKeydown, onBlur: (e: FocusEvent) => { props.onBlur?.(e); }, isClickOutside: (target: EventTarget | null) => !elementsContains( [panelDivRef.value, startInputDivRef.value, endInputDivRef.value, containerRef.value], target as HTMLElement, ), onFocus: (e: FocusEvent) => { setMergedActivePickerIndex(index); props.onFocus?.(e); }, triggerOpen: (newOpen: boolean) => { triggerOpen(newOpen, index); }, onSubmit: () => { if ( // When user typing disabledDate with keyboard and enter, this value will be empty !selectedValue.value || // Normal disabled check (props.disabledDate && props.disabledDate(selectedValue.value[index])) ) { return false; } triggerChange(selectedValue.value, index); resetText(); }, onCancel: () => { triggerOpen(false, index); setSelectedValue(mergedValue.value); resetText(); }, }); const [startInputProps, { focused: startFocused, typing: startTyping }] = usePickerInput({ ...getSharedInputHookProps(0, resetStartText), blurToCancel: needConfirmButton, open: startOpen, value: startText, onKeydown: (e, preventDefault) => { props.onKeydown?.(e, preventDefault); }, }); const [endInputProps, { focused: endFocused, typing: endTyping }] = usePickerInput({ ...getSharedInputHookProps(1, resetEndText), blurToCancel: needConfirmButton, open: endOpen, value: endText, onKeydown: (e, preventDefault) => { props.onKeydown?.(e, preventDefault); }, }); // ========================== Click Picker ========================== const onPickerClick = (e: MouseEvent) => { // When click inside the picker & outside the picker's input elements // the panel should still be opened props.onClick?.(e); if ( !mergedOpen.value && !startInputRef.value.contains(e.target as Node) && !endInputRef.value.contains(e.target as Node) ) { if (!mergedDisabled.value[0]) { triggerOpenAndFocus(0); } else if (!mergedDisabled.value[1]) { triggerOpenAndFocus(1); } } }; const onPickerMousedown = (e: MouseEvent) => { // shouldn't affect input elements if picker is active props.onMousedown?.(e); if ( mergedOpen.value && (startFocused.value || endFocused.value) && !startInputRef.value.contains(e.target as Node) && !endInputRef.value.contains(e.target as Node) ) { e.preventDefault(); } }; // ============================= Sync ============================== // Close should sync back with text value const startStr = computed(() => mergedValue.value?.[0] ? formatValue(mergedValue.value[0], { locale: props.locale, format: 'YYYYMMDDHHmmss', generateConfig: props.generateConfig, }) : '', ); const endStr = computed(() => mergedValue.value?.[1] ? formatValue(mergedValue.value[1], { locale: props.locale, format: 'YYYYMMDDHHmmss', generateConfig: props.generateConfig, }) : '', ); watch([mergedOpen, startValueTexts, endValueTexts], () => { if (!mergedOpen.value) { setSelectedValue(mergedValue.value); if (!startValueTexts.value.length || startValueTexts.value[0] === '') { triggerStartTextChange(''); } else if (firstStartValueText.value !== startText.value) { resetStartText(); } if (!endValueTexts.value.length || endValueTexts.value[0] === '') { triggerEndTextChange(''); } else if (firstEndValueText.value !== endText.value) { resetEndText(); } } }); // Sync innerValue with control mode watch([startStr, endStr], () => { setSelectedValue(mergedValue.value); }); // ============================ Warning ============================ if (process.env.NODE_ENV !== 'production') { watchEffect(() => { const { value, disabled } = props; if ( value && Array.isArray(disabled) && ((getValue(disabled, 0) && !getValue(value, 0)) || (getValue(disabled, 1) && !getValue(value, 1))) ) { warning( false, '`disabled` should not set with empty `value`. You should set `allowEmpty` or `value` instead.', ); } }); } expose({ focus: () => { if (startInputRef.value) { startInputRef.value.focus(); } }, blur: () => { if (startInputRef.value) { startInputRef.value.blur(); } if (endInputRef.value) { endInputRef.value.blur(); } }, }); // ============================ Ranges ============================= const rangeList = computed(() => Object.keys(props.ranges || {}).map(label => { const range = props.ranges![label]; const newValues = typeof range === 'function' ? range() : range; return { label, onClick: () => { triggerChange(newValues, null); triggerOpen(false, mergedActivePickerIndex.value); }, onMouseenter: () => { setRangeHoverValue(newValues); }, onMouseleave: () => { setRangeHoverValue(null); }, }; }), ); // ============================= Panel ============================= const panelHoverRangedValue = computed(() => { if ( mergedOpen.value && hoverRangedValue.value && hoverRangedValue.value[0] && hoverRangedValue.value[1] && props.generateConfig.isAfter(hoverRangedValue.value[1], hoverRangedValue.value[0]) ) { return hoverRangedValue.value; } else { return null; } }); function renderPanel( panelPosition: 'left' | 'right' | false = false, panelProps: Partial> = {}, ) { const { generateConfig, showTime, dateRender, direction, disabledTime, prefixCls, locale } = props; let panelShowTime: boolean | SharedTimeProps | undefined = showTime as SharedTimeProps; if (showTime && typeof showTime === 'object' && showTime.defaultValue) { const timeDefaultValues: DateType[] = showTime.defaultValue!; panelShowTime = { ...showTime, defaultValue: getValue(timeDefaultValues, mergedActivePickerIndex.value) || undefined, }; } let panelDateRender: DateRender | null = null; if (dateRender) { panelDateRender = ({ current: date, today }) => dateRender({ current: date, today, info: { range: mergedActivePickerIndex.value ? 'end' : 'start', }, }); } return ( {...(props as any)} {...panelProps} dateRender={panelDateRender} showTime={panelShowTime} mode={mergedModes.value[mergedActivePickerIndex.value]} generateConfig={generateConfig} style={undefined} direction={direction} disabledDate={ mergedActivePickerIndex.value === 0 ? disabledStartDate : disabledEndDate } disabledTime={date => { if (disabledTime) { return disabledTime(date, mergedActivePickerIndex.value === 0 ? 'start' : 'end'); } return false; }} class={classNames({ [`${prefixCls}-panel-focused`]: mergedActivePickerIndex.value === 0 ? !startTyping.value : !endTyping.value, })} value={getValue(selectedValue.value, mergedActivePickerIndex.value)} locale={locale} tabIndex={-1} onPanelChange={(date, newMode) => { // clear hover value when panel change if (mergedActivePickerIndex.value === 0) { onStartLeave(true); } if (mergedActivePickerIndex.value === 1) { onEndLeave(true); } triggerModesChange( updateValues(mergedModes.value, newMode, mergedActivePickerIndex.value), updateValues(selectedValue.value, date, mergedActivePickerIndex.value), ); let viewDate = date; if ( panelPosition === 'right' && mergedModes.value[mergedActivePickerIndex.value] === newMode ) { viewDate = getClosingViewDate(viewDate, newMode as any, generateConfig, -1); } setViewDate(viewDate, mergedActivePickerIndex.value); }} onOk={null} onSelect={undefined} onChange={undefined} defaultValue={ mergedActivePickerIndex.value === 0 ? getValue(selectedValue.value, 1) : getValue(selectedValue.value, 0) } /> ); } const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => { const values = updateValues(selectedValue.value, date, mergedActivePickerIndex.value); if (type === 'submit' || (type !== 'key' && !needConfirmButton.value)) { // triggerChange will also update selected values triggerChange(values, mergedActivePickerIndex.value); // clear hover value style if (mergedActivePickerIndex.value === 0) { onStartLeave(); } else { onEndLeave(); } } else { setSelectedValue(values); } }; useProvidePanel({ operationRef, hideHeader: computed(() => props.picker === 'time'), onDateMouseenter, onDateMouseleave, hideRanges: computed(() => true), onSelect: onContextSelect, open: mergedOpen, }); return () => { const { prefixCls = 'rc-picker', id, popupStyle, dropdownClassName, transitionName, dropdownAlign, getPopupContainer, generateConfig, locale, placeholder, autofocus, picker = 'date', showTime, separator = '~', disabledDate, panelRender, allowClear, suffixIcon, clearIcon, inputReadOnly, renderExtraFooter, onMouseenter, onMouseleave, onMouseup, onOk, components, direction, autocomplete = 'off', } = props; const arrowPositionStyle = direction === 'rtl' ? { right: `${arrowLeft.value}px` } : { left: `${arrowLeft.value}px` }; function renderPanels() { let panels: VueNode; const extraNode = getExtraFooter( prefixCls, mergedModes.value[mergedActivePickerIndex.value], renderExtraFooter, ); const rangesNode = getRanges({ prefixCls, components, needConfirmButton: needConfirmButton.value, okDisabled: !getValue(selectedValue.value, mergedActivePickerIndex.value) || (disabledDate && disabledDate(selectedValue.value[mergedActivePickerIndex.value])), locale, rangeList: rangeList.value, onOk: () => { if (getValue(selectedValue.value, mergedActivePickerIndex.value)) { // triggerChangeOld(selectedValue.value); triggerChange(selectedValue.value, mergedActivePickerIndex.value); if (onOk) { onOk(selectedValue.value); } } }, }); if (picker !== 'time' && !showTime) { const viewDate = mergedActivePickerIndex.value === 0 ? startViewDate.value : endViewDate.value; const nextViewDate = getClosingViewDate(viewDate, picker, generateConfig); const currentMode = mergedModes.value[mergedActivePickerIndex.value]; const showDoublePanel = currentMode === picker; const leftPanel = renderPanel(showDoublePanel ? 'left' : false, { pickerValue: viewDate, onPickerValueChange: newViewDate => { setViewDate(newViewDate, mergedActivePickerIndex.value); }, }); const rightPanel = renderPanel('right', { pickerValue: nextViewDate, onPickerValueChange: newViewDate => { setViewDate( getClosingViewDate(newViewDate, picker, generateConfig, -1), mergedActivePickerIndex.value, ); }, }); if (direction === 'rtl') { panels = ( <> {rightPanel} {showDoublePanel && leftPanel} ); } else { panels = ( <> {leftPanel} {showDoublePanel && rightPanel} ); } } else { panels = renderPanel(); } let mergedNodes: VueNode = ( <>
{panels}
{(extraNode || rangesNode) && (
{extraNode} {rangesNode}
)} ); if (panelRender) { mergedNodes = panelRender(mergedNodes); } return (
{ e.preventDefault(); }} > {mergedNodes}
); } const rangePanel = (
{renderPanels()}
); // ============================= Icons ============================= let suffixNode: VueNode; if (suffixIcon) { suffixNode = {suffixIcon}; } let clearNode: VueNode; if ( allowClear && ((getValue(mergedValue.value, 0) && !mergedDisabled.value[0]) || (getValue(mergedValue.value, 1) && !mergedDisabled.value[1])) ) { clearNode = ( { e.preventDefault(); e.stopPropagation(); }} onMouseup={e => { e.preventDefault(); e.stopPropagation(); let values = mergedValue.value; if (!mergedDisabled.value[0]) { values = updateValues(values, null, 0); } if (!mergedDisabled.value[1]) { values = updateValues(values, null, 1); } triggerChange(values, null); triggerOpen(false, mergedActivePickerIndex.value); }} class={`${prefixCls}-clear`} > {clearIcon || } ); } const inputSharedProps = { size: getInputSize(picker, formatList.value[0], generateConfig), }; let activeBarLeft = 0; let activeBarWidth = 0; if (startInputDivRef.value && endInputDivRef.value && separatorRef.value) { if (mergedActivePickerIndex.value === 0) { activeBarWidth = startInputDivRef.value.offsetWidth; } else { activeBarLeft = arrowLeft.value; activeBarWidth = endInputDivRef.value.offsetWidth; } } const activeBarPositionStyle = direction === 'rtl' ? { right: `${activeBarLeft}px` } : { left: `${activeBarLeft}px` }; // ============================ Return ============================= return ( rangePanel, }} >
{ triggerStartTextChange(e.target.value); }} autofocus={autofocus} placeholder={getValue(placeholder, 0) || ''} ref={startInputRef} {...startInputProps.value} {...inputSharedProps} autocomplete={autocomplete} />
{separator}
{ triggerEndTextChange(e.target.value); }} placeholder={getValue(placeholder, 1) || ''} ref={endInputRef} {...endInputProps.value} {...inputSharedProps} autocomplete={autocomplete} />
{suffixNode} {clearNode} {getPortal()}
); }; }, }); } const InterRangerPicker = RangerPicker(); export default InterRangerPicker;