diff --git a/components/components.ts b/components/components.ts index a1afe4239..3cc40ef3a 100644 --- a/components/components.ts +++ b/components/components.ts @@ -179,7 +179,7 @@ export { default as Tabs, TabPane, TabContent } from './tabs'; export type { TagProps } from './tag'; export { default as Tag, CheckableTag } from './tag'; -export type { TimePickerProps } from './time-picker'; +export type { TimePickerProps, TimeRangePickerProps } from './time-picker'; export { default as TimePicker } from './time-picker'; export type { TimelineProps, TimelineItemProps } from './timeline'; diff --git a/components/date-picker/date-fns.tsx b/components/date-picker/date-fns.tsx index 337df66d4..57fd2050b 100755 --- a/components/date-picker/date-fns.tsx +++ b/components/date-picker/date-fns.tsx @@ -1,5 +1,5 @@ import type { App } from 'vue'; -import dataFnsGenerateConfig from '../vc-picker/generate/dataFns'; +import dataFnsGenerateConfig from '../vc-picker/generate/dateFns'; import type { PickerProps, PickerDateProps, @@ -32,4 +32,10 @@ DatePicker.install = function (app: App) { export { RangePicker, WeekPicker, MonthPicker, QuarterPicker }; -export default DatePicker as typeof DatePicker & Plugin; +export default DatePicker as typeof DatePicker & + Plugin & { + readonly RangePicker: typeof RangePicker; + readonly MonthPicker: typeof MonthPicker; + readonly WeekPicker: typeof WeekPicker; + readonly QuarterPicker: typeof QuarterPicker; + }; diff --git a/components/date-picker/dayjs.tsx b/components/date-picker/dayjs.tsx index b77f1b0da..ec5aea6d0 100755 --- a/components/date-picker/dayjs.tsx +++ b/components/date-picker/dayjs.tsx @@ -33,4 +33,10 @@ DatePicker.install = function (app: App) { export { RangePicker, WeekPicker, MonthPicker, QuarterPicker }; -export default DatePicker as typeof DatePicker & Plugin; +export default DatePicker as typeof DatePicker & + Plugin & { + readonly RangePicker: typeof RangePicker; + readonly MonthPicker: typeof MonthPicker; + readonly WeekPicker: typeof WeekPicker; + readonly QuarterPicker: typeof QuarterPicker; + }; diff --git a/components/date-picker/generatePicker/generateRangePicker.tsx b/components/date-picker/generatePicker/generateRangePicker.tsx index 2cde9df85..2f8219cd7 100644 --- a/components/date-picker/generatePicker/generateRangePicker.tsx +++ b/components/date-picker/generatePicker/generateRangePicker.tsx @@ -151,7 +151,7 @@ export default function generateRangePicker(generateConfig: GenerateCo additionalOverrideProps = { ...additionalOverrideProps, ...(showTime ? getTimeProps({ format, picker, ...showTime }) : {}), - ...(picker === 'time' ? getTimeProps({ format, ...p, picker }) : {}), + ...(picker === 'time' ? getTimeProps({ format, ...restProps, picker }) : {}), }; const pre = prefixCls.value; return ( diff --git a/components/date-picker/generatePicker/generateSinglePicker.tsx b/components/date-picker/generatePicker/generateSinglePicker.tsx index 4513c65b7..2075d0c66 100644 --- a/components/date-picker/generatePicker/generateSinglePicker.tsx +++ b/components/date-picker/generatePicker/generateSinglePicker.tsx @@ -9,7 +9,7 @@ import { getPlaceholder } from '../util'; import { useLocaleReceiver } from '../../locale-provider/LocaleReceiver'; import type { PickerProps, PickerDateProps, PickerTimeProps } from '.'; import { getTimeProps, Components } from '.'; -import { computed, defineComponent, ref } from 'vue'; +import { computed, DefineComponent, defineComponent, ref } from 'vue'; import useConfigInject from '../../_util/hooks/useConfigInject'; import classNames from '../../_util/classNames'; import { commonProps, datePickerProps, ExtraDatePickerProps } from './props'; @@ -21,7 +21,7 @@ export default function generatePicker(generateConfig: GenerateConfig< function getPicker( picker?: PickerMode, displayName?: string, - ) { + ): DefineComponent { return defineComponent({ name: displayName, inheritAttrs: false, @@ -152,7 +152,7 @@ export default function generatePicker(generateConfig: GenerateConfig< }) : {}), ...(mergedPicker === 'time' - ? getTimeProps({ format, ...p, picker: mergedPicker }) + ? getTimeProps({ format, ...restProps, picker: mergedPicker }) : {}), }; const pre = prefixCls.value; @@ -204,7 +204,7 @@ export default function generatePicker(generateConfig: GenerateConfig< ); }; }, - }); + }) as unknown as DefineComponent; } const DatePicker = getPicker(undefined, 'ADatePicker'); diff --git a/components/date-picker/moment.tsx b/components/date-picker/moment.tsx index 13f26465e..7ffdc2482 100755 --- a/components/date-picker/moment.tsx +++ b/components/date-picker/moment.tsx @@ -35,4 +35,10 @@ DatePicker.install = function (app: App) { export { RangePicker, WeekPicker, MonthPicker, QuarterPicker }; -export default DatePicker as typeof DatePicker & Plugin; +export default DatePicker as typeof DatePicker & + Plugin & { + readonly RangePicker: typeof RangePicker; + readonly MonthPicker: typeof MonthPicker; + readonly WeekPicker: typeof WeekPicker; + readonly QuarterPicker: typeof QuarterPicker; + }; diff --git a/components/time-picker/date-fns.tsx b/components/time-picker/date-fns.tsx new file mode 100644 index 000000000..d3e7b3a11 --- /dev/null +++ b/components/time-picker/date-fns.tsx @@ -0,0 +1,30 @@ +import createTimePicker from './time-picker'; +import dateFnsGenerateConfig from '../vc-picker/generate/dateFns'; +import { App } from 'vue'; +import { PickerTimeProps, RangePickerTimeProps } from '../date-picker/generatePicker'; + +const { TimePicker, TimeRangePicker } = createTimePicker(dateFnsGenerateConfig); + +export interface TimeRangePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; +} +export interface TimePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; +} + +/* istanbul ignore next */ +TimePicker.install = function (app: App) { + app.component(TimePicker.name, TimePicker); + app.component(TimeRangePicker.name, TimeRangePicker); + return app; +}; +TimePicker.TimeRangePicker = TimeRangePicker; + +export { TimePicker, TimeRangePicker }; + +export default TimePicker as typeof TimePicker & + Plugin & { + readonly TimeRangePicker: typeof TimeRangePicker; + }; diff --git a/components/time-picker/dayjs.tsx b/components/time-picker/dayjs.tsx new file mode 100644 index 000000000..e9e57e219 --- /dev/null +++ b/components/time-picker/dayjs.tsx @@ -0,0 +1,31 @@ +import { Dayjs } from 'dayjs'; +import createTimePicker from './time-picker'; +import dayjsGenerateConfig from '../vc-picker/generate/dayjs'; +import { App } from 'vue'; +import { PickerTimeProps, RangePickerTimeProps } from '../date-picker/generatePicker'; + +const { TimePicker, TimeRangePicker } = createTimePicker(dayjsGenerateConfig); + +export interface TimeRangePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; +} +export interface TimePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; +} + +/* istanbul ignore next */ +TimePicker.install = function (app: App) { + app.component(TimePicker.name, TimePicker); + app.component(TimeRangePicker.name, TimeRangePicker); + return app; +}; +TimePicker.TimeRangePicker = TimeRangePicker; + +export { TimePicker, TimeRangePicker }; + +export default TimePicker as typeof TimePicker & + Plugin & { + readonly TimeRangePicker: typeof TimeRangePicker; + }; diff --git a/components/time-picker/index.tsx b/components/time-picker/index.tsx index 1cf6d6438..a6f2f3609 100644 --- a/components/time-picker/index.tsx +++ b/components/time-picker/index.tsx @@ -1,276 +1,8 @@ -import omit from 'omit.js'; -import type { ExtractPropTypes } from 'vue'; -import { defineComponent, inject, provide } from 'vue'; -import VcTimePicker from '../vc-time-picker'; -import LocaleReceiver from '../locale-provider/LocaleReceiver'; -import BaseMixin from '../_util/BaseMixin'; -import PropTypes from '../_util/vue-types'; -import warning from '../_util/warning'; -import ClockCircleOutlined from '@ant-design/icons-vue/ClockCircleOutlined'; -import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; -import enUS from './locale/en_US'; -import { hasProp, getOptionProps, getComponent, isValidElement } from '../_util/props-util'; -import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { cloneElement } from '../_util/vnode'; -import { defaultConfigProvider } from '../config-provider'; -import { - checkValidate, - stringToMoment, - momentToString, - TimeOrTimesType, -} from '../_util/moment-util'; -import { tuple, withInstall } from '../_util/type'; -import classNames from '../_util/classNames'; - -export function generateShowHourMinuteSecond(format: string) { - // Ref: http://momentjs.com/docs/#/parsing/string-format/ - return { - showHour: format.indexOf('H') > -1 || format.indexOf('h') > -1 || format.indexOf('k') > -1, - showMinute: format.indexOf('m') > -1, - showSecond: format.indexOf('s') > -1, - }; -} - +import TimePicker from './dayjs'; +export * from './dayjs'; export interface TimePickerLocale { placeholder?: string; rangePlaceholder?: [string, string]; } -export const timePickerProps = () => ({ - size: PropTypes.oneOf(tuple('large', 'default', 'small')), - value: TimeOrTimesType, - defaultValue: TimeOrTimesType, - open: PropTypes.looseBool, - format: PropTypes.string, - disabled: PropTypes.looseBool, - placeholder: PropTypes.string, - prefixCls: PropTypes.string, - hideDisabledOptions: PropTypes.looseBool, - disabledHours: PropTypes.func, - disabledMinutes: PropTypes.func, - disabledSeconds: PropTypes.func, - getPopupContainer: PropTypes.func, - use12Hours: PropTypes.looseBool, - focusOnOpen: PropTypes.looseBool, - hourStep: PropTypes.number, - minuteStep: PropTypes.number, - secondStep: PropTypes.number, - allowEmpty: PropTypes.looseBool, - allowClear: PropTypes.looseBool, - inputReadOnly: PropTypes.looseBool, - clearText: PropTypes.string, - defaultOpenValue: PropTypes.object, - popupClassName: PropTypes.string, - popupStyle: PropTypes.style, - suffixIcon: PropTypes.any, - align: PropTypes.object, - placement: PropTypes.any, - transitionName: PropTypes.string, - autofocus: PropTypes.looseBool, - addon: PropTypes.any, - clearIcon: PropTypes.any, - locale: PropTypes.object, - valueFormat: PropTypes.string, - onChange: PropTypes.func, - onAmPmChange: PropTypes.func, - onOpen: PropTypes.func, - onClose: PropTypes.func, - onFocus: PropTypes.func, - onBlur: PropTypes.func, - onKeydown: PropTypes.func, - onOpenChange: PropTypes.func, -}); - -export type TimePickerProps = Partial>>; - -const TimePicker = defineComponent({ - name: 'ATimePicker', - mixins: [BaseMixin], - inheritAttrs: false, - props: initDefaultProps(timePickerProps(), { - align: { - offset: [0, -2], - }, - disabled: false, - disabledHours: undefined, - disabledMinutes: undefined, - disabledSeconds: undefined, - hideDisabledOptions: false, - placement: 'bottomLeft', - transitionName: 'slide-up', - focusOnOpen: true, - allowClear: true, - }), - emits: ['update:value', 'update:open', 'change', 'openChange', 'focus', 'blur', 'keydown'], - setup() { - return { - popupRef: null, - timePickerRef: null, - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - - data() { - const { value, defaultValue, valueFormat } = this; - - checkValidate('TimePicker', defaultValue, 'defaultValue', valueFormat); - checkValidate('TimePicker', value, 'value', valueFormat); - warning( - !hasProp(this, 'allowEmpty'), - 'TimePicker', - '`allowEmpty` is deprecated. Please use `allowClear` instead.', - ); - return { - sValue: stringToMoment(value || defaultValue, valueFormat), - }; - }, - watch: { - value(val) { - checkValidate('TimePicker', val, 'value', this.valueFormat); - this.setState({ sValue: stringToMoment(val, this.valueFormat) }); - }, - }, - created() { - provide('savePopupRef', this.savePopupRef); - }, - methods: { - getDefaultFormat() { - const { format, use12Hours } = this; - if (format) { - return format; - } else if (use12Hours) { - return 'h:mm:ss a'; - } - return 'HH:mm:ss'; - }, - - getAllowClear() { - const { allowClear, allowEmpty } = this.$props; - if (hasProp(this, 'allowClear')) { - return allowClear; - } - return allowEmpty; - }, - getDefaultLocale() { - const defaultLocale = { - ...enUS, - ...this.$props.locale, - }; - return defaultLocale; - }, - savePopupRef(ref) { - this.popupRef = ref; - }, - saveTimePicker(timePickerRef) { - this.timePickerRef = timePickerRef; - }, - handleChange(value) { - if (!hasProp(this, 'value')) { - this.setState({ sValue: value }); - } - const { format = 'HH:mm:ss' } = this; - const val = this.valueFormat ? momentToString(value, this.valueFormat) : value; - this.$emit('update:value', val); - this.$emit('change', val, (value && value.format(format)) || ''); - }, - - handleOpenClose({ open }) { - this.$emit('update:open', open); - this.$emit('openChange', open); - }, - - focus() { - (this.timePickerRef as any).focus(); - }, - - blur() { - (this.timePickerRef as any).blur(); - }, - - renderInputIcon(prefixCls: string) { - let suffixIcon = getComponent(this, 'suffixIcon'); - suffixIcon = Array.isArray(suffixIcon) ? suffixIcon[0] : suffixIcon; - const clockIcon = (suffixIcon && - isValidElement(suffixIcon) && - cloneElement(suffixIcon, { - class: `${prefixCls}-clock-icon`, - })) || ; - - return {clockIcon}; - }, - - renderClearIcon(prefixCls: string) { - const clearIcon = getComponent(this, 'clearIcon'); - const clearIconPrefixCls = `${prefixCls}-clear`; - - if (clearIcon && isValidElement(clearIcon)) { - return cloneElement(clearIcon, { - class: clearIconPrefixCls, - }); - } - - return ; - }, - - renderTimePicker(locale) { - let props = getOptionProps(this); - props = omit(props, ['defaultValue', 'suffixIcon', 'allowEmpty', 'allowClear']); - const { class: className } = this.$attrs; - const { prefixCls: customizePrefixCls, getPopupContainer, placeholder, size } = props; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('time-picker', customizePrefixCls); - const inputPrefixCls = getPrefixCls('input'); - const pickerInputClass = classNames(`${prefixCls}-input`, inputPrefixCls); - - const format = this.getDefaultFormat(); - const pickerClassName = { - [className as string]: className, - [`${prefixCls}-${size}`]: !!size, - }; - const tempAddon = getComponent(this, 'addon', {}, false); - const pickerAddon = panel => { - return tempAddon ? ( -
- {typeof tempAddon === 'function' ? tempAddon(panel) : tempAddon} -
- ) : null; - }; - const inputIcon = this.renderInputIcon(prefixCls); - const clearIcon = this.renderClearIcon(prefixCls); - const { getPopupContainer: getContextPopupContainer } = this.configProvider; - const timeProps = { - ...generateShowHourMinuteSecond(format), - ...props, - ...this.$attrs, - allowEmpty: this.getAllowClear(), - prefixCls, - pickerInputClass, - getPopupContainer: getPopupContainer || getContextPopupContainer, - format, - value: this.sValue, - placeholder: placeholder === undefined ? locale.placeholder : placeholder, - addon: pickerAddon, - inputIcon, - clearIcon, - class: pickerClassName, - ref: this.saveTimePicker, - onChange: this.handleChange, - onOpen: this.handleOpenClose, - onClose: this.handleOpenClose, - }; - return ; - }, - }, - - render() { - return ( - - ); - }, -}); - -export default withInstall(TimePicker); +export default TimePicker; diff --git a/components/time-picker/moment.tsx b/components/time-picker/moment.tsx new file mode 100644 index 000000000..2d065d7cd --- /dev/null +++ b/components/time-picker/moment.tsx @@ -0,0 +1,31 @@ +import { Moment } from 'moment'; +import createTimePicker from './time-picker'; +import momentGenerateConfig from '../vc-picker/generate/moment'; +import { App } from 'vue'; +import { PickerTimeProps, RangePickerTimeProps } from '../date-picker/generatePicker'; + +const { TimePicker, TimeRangePicker } = createTimePicker(momentGenerateConfig); + +export interface TimeRangePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; +} +export interface TimePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; +} + +/* istanbul ignore next */ +TimePicker.install = function (app: App) { + app.component(TimePicker.name, TimePicker); + app.component(TimeRangePicker.name, TimeRangePicker); + return app; +}; +TimePicker.TimeRangePicker = TimeRangePicker; + +export { TimePicker, TimeRangePicker }; + +export default TimePicker as typeof TimePicker & + Plugin & { + readonly TimeRangePicker: typeof TimeRangePicker; + }; diff --git a/components/time-picker/time-picker.tsx b/components/time-picker/time-picker.tsx new file mode 100644 index 000000000..061073a5f --- /dev/null +++ b/components/time-picker/time-picker.tsx @@ -0,0 +1,199 @@ +import { defineComponent, ref } from 'vue'; +import generatePicker, { + PickerTimeProps, + RangePickerTimeProps, +} from '../date-picker/generatePicker'; +import { + commonProps, + datePickerProps, + rangePickerProps, +} from '../date-picker/generatePicker/props'; +import { GenerateConfig } from '../vc-picker/generate'; +import { PanelMode, RangeValue } from '../vc-picker/interface'; +import { RangePickerSharedProps } from '../vc-picker/RangePicker'; +import devWarning from '../vc-util/devWarning'; + +export interface TimePickerLocale { + placeholder?: string; + rangePlaceholder?: [string, string]; +} + +const timpePickerProps = { + format: String, + showNow: Boolean, + showHour: Boolean, + showMinute: Boolean, + showSecond: Boolean, + use12Hours: Boolean, + hourStep: Number, + minuteStep: Number, + secondStep: Number, + hideDisabledOptions: Boolean, + popupClassName: String, +}; + +function createTimePicker(generateConfig: GenerateConfig) { + const DatePicker = generatePicker(generateConfig); + const { TimePicker: InternalTimePicker, RangePicker: InternalRangePicker } = DatePicker as any; + interface TimeRangePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; + } + interface TimePickerProps extends Omit, 'picker'> { + popupClassName?: string; + valueFormat?: string; + } + const TimePicker = defineComponent({ + name: 'ATimePicker', + inheritAttrs: false, + props: { + ...commonProps(), + ...datePickerProps(), + ...timpePickerProps, + } as any, + slot: ['addon', 'renderExtraFooter', 'suffixIcon', 'clearIcon'], + emits: ['change', 'openChange', 'focus', 'blur', 'ok', 'update:value'], + setup(props, { slots, expose, emit, attrs }) { + devWarning( + !slots.addon, + 'TimePicker', + '`addon` is deprecated. Please use `renderExtraFooter` instead.', + ); + const pickerRef = ref(); + expose({ + focus: () => { + pickerRef.value?.focus(); + }, + blur: () => { + pickerRef.value?.blur(); + }, + }); + const onChange = (value: DateType | string, dateString: string) => { + emit('update:value', value); + emit('change', value, dateString); + }; + const onOpenChange = (open: boolean) => { + emit('openChange', open); + }; + const onFoucs = () => { + emit('focus'); + }; + const onBlur = () => { + emit('blur'); + }; + const onOk = (value: DateType) => { + emit('ok', value); + }; + return () => { + return ( + + ); + }; + }, + }); + + const TimeRangePicker = defineComponent({ + name: 'ATimeRangePicker', + inheritAttrs: false, + props: { + ...commonProps(), + ...rangePickerProps(), + ...timpePickerProps, + order: { type: Boolean, default: true }, + } as any, + slot: ['renderExtraFooter', 'suffixIcon', 'clearIcon'], + emits: [ + 'change', + 'panelChange', + 'ok', + 'openChange', + 'update:value', + 'calendarChange', + 'focus', + 'blur', + ], + setup(props, { slots, expose, emit, attrs }) { + const pickerRef = ref(); + expose({ + focus: () => { + pickerRef.value?.focus(); + }, + blur: () => { + pickerRef.value?.blur(); + }, + }); + const onChange = ( + values: [DateType, DateType] | [string, string], + dateStrings: [string, string], + ) => { + emit('update:value', values); + emit('change', values, dateStrings); + }; + const onOpenChange = (open: boolean) => { + emit('openChange', open); + }; + const onFoucs = () => { + emit('focus'); + }; + const onBlur = () => { + emit('blur'); + }; + const onPanelChange = ( + values: string | RangeValue, + modes: [PanelMode, PanelMode], + ) => { + emit('panelChange', values, modes); + }; + const onOk = (value: DateType) => { + emit('ok', value); + }; + const onCalendarChange: RangePickerSharedProps['onCalendarChange'] = ( + values: [DateType, DateType] | [string, string], + dateStrings: [string, string], + info, + ) => { + emit('calendarChange', values, dateStrings, info); + }; + return () => { + return ( + + ); + }; + }, + }); + + return { + TimePicker, + TimeRangePicker, + }; +} + +export default createTimePicker; diff --git a/components/vc-picker/Picker.tsx b/components/vc-picker/Picker.tsx index aaa1963ca..6a1f80335 100644 --- a/components/vc-picker/Picker.tsx +++ b/components/vc-picker/Picker.tsx @@ -191,7 +191,7 @@ function Picker() { // ], setup(props, { attrs, expose }) { const inputRef = ref(null); - const picker = computed(() => picker.value ?? 'date'); + const picker = computed(() => props.picker ?? 'date'); const needConfirmButton = computed( () => (picker.value === 'date' && !!props.showTime) || picker.value === 'time', ); diff --git a/components/vc-picker/generate/dataFns.ts b/components/vc-picker/generate/dateFns.ts similarity index 100% rename from components/vc-picker/generate/dataFns.ts rename to components/vc-picker/generate/dateFns.ts diff --git a/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx b/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx index c03b64b7b..ccd197967 100644 --- a/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx +++ b/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx @@ -3,7 +3,7 @@ import { useInjectPanel } from '../../PanelContext'; import classNames from '../../../_util/classNames'; import { ref } from '@vue/reactivity'; import { onBeforeUnmount, watch } from '@vue/runtime-core'; -import { defineComponent } from 'vue'; +import { defineComponent, nextTick } from 'vue'; export type Unit = { label: any; @@ -43,17 +43,23 @@ export default defineComponent({ scrollRef.value?.(); }); - watch(open, () => { - scrollRef.value?.(); - if (open.value) { - const li = liRefs.value.get(props.value!); - if (li) { - scrollRef.value = waitElementReady(li, () => { - scrollTo(ulRef.value!, li.offsetTop, 0); - }); - } - } - }); + watch( + open, + () => { + scrollRef.value?.(); + nextTick(() => { + if (open.value) { + const li = liRefs.value.get(props.value!); + if (li) { + scrollRef.value = waitElementReady(li, () => { + scrollTo(ulRef.value!, li.offsetTop, 0); + }); + } + } + }); + }, + { immediate: true, flush: 'post' }, + ); return () => { const { prefixCls, units, onSelect, value, active, hideDisabledOptions } = props; const cellPrefixCls = `${prefixCls}-cell`;