From 16fc2a10a9fa110c5e47478d7a0081dd05994299 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Mon, 19 Jul 2021 23:25:36 +0800 Subject: [PATCH] Merge branch 'refactor-date' of github.com:vueComponent/ant-design-vue into refactor-date # Conflicts: # components/vc-picker/generate/dayjs.ts # components/vc-picker/generate/moment.ts # v2-doc --- components/_util/EventInterface.ts | 3 + components/_util/hooks/useMemo.ts | 4 +- components/_util/hooks/useMergedState.ts | 50 + components/_util/hooks/useState.ts | 17 + components/vc-picker/PanelContext.tsx | 15 +- components/vc-picker/Picker.tsx | 672 +++++-------- components/vc-picker/PickerPanel.tsx | 913 +++++++++--------- components/vc-picker/PickerTrigger.tsx | 9 +- components/vc-picker/RangeContext.tsx | 12 +- .../vc-picker/hooks/useCellClassName.ts | 24 +- components/vc-picker/hooks/useHoverValue.ts | 4 +- components/vc-picker/hooks/useMergeProps.ts | 8 + components/vc-picker/hooks/usePickerInput.ts | 140 +-- .../vc-picker/hooks/useTextValueMapping.ts | 31 +- components/vc-picker/hooks/useValueTexts.ts | 27 +- components/vc-picker/interface.ts | 5 +- .../vc-picker/panels/DatePanel/DateBody.tsx | 40 +- .../vc-picker/panels/DatePanel/DateHeader.tsx | 11 +- .../vc-picker/panels/DatePanel/index.tsx | 14 +- .../vc-picker/panels/DatetimePanel/index.tsx | 53 +- .../panels/DecadePanel/DecadeBody.tsx | 8 +- .../panels/DecadePanel/DecadeHeader.tsx | 20 +- .../vc-picker/panels/DecadePanel/index.tsx | 38 +- components/vc-picker/panels/Header.tsx | 20 +- .../vc-picker/panels/MonthPanel/MonthBody.tsx | 14 +- .../panels/MonthPanel/MonthHeader.tsx | 23 +- .../vc-picker/panels/MonthPanel/index.tsx | 17 +- components/vc-picker/panels/PanelBody.tsx | 43 +- .../panels/QuarterPanel/QuarterBody.tsx | 14 +- .../panels/QuarterPanel/QuarterHeader.tsx | 23 +- .../vc-picker/panels/QuarterPanel/index.tsx | 12 +- .../vc-picker/panels/TimePanel/TimeBody.tsx | 107 +- .../vc-picker/panels/TimePanel/TimeHeader.tsx | 12 +- .../panels/TimePanel/TimeUnitColumn.tsx | 4 +- .../vc-picker/panels/TimePanel/index.tsx | 21 +- .../vc-picker/panels/WeekPanel/index.tsx | 18 +- .../vc-picker/panels/YearPanel/YearBody.tsx | 15 +- .../vc-picker/panels/YearPanel/YearHeader.tsx | 11 +- .../vc-picker/panels/YearPanel/index.tsx | 26 +- components/vc-picker/utils/getExtraFooter.tsx | 5 +- components/vc-picker/utils/getRanges.tsx | 1 - components/vc-picker/utils/miscUtil.ts | 4 +- components/vc-picker/utils/timeUtil.ts | 16 +- components/vc-picker/utils/uiUtil.ts | 4 +- 44 files changed, 1187 insertions(+), 1341 deletions(-) create mode 100644 components/_util/EventInterface.ts create mode 100644 components/_util/hooks/useMergedState.ts create mode 100644 components/_util/hooks/useState.ts create mode 100644 components/vc-picker/hooks/useMergeProps.ts diff --git a/components/_util/EventInterface.ts b/components/_util/EventInterface.ts new file mode 100644 index 000000000..f1bb28081 --- /dev/null +++ b/components/_util/EventInterface.ts @@ -0,0 +1,3 @@ +export type FocusEventHandler = (e: FocusEvent) => void; +export type MouseEventHandler = (e: MouseEvent) => void; +export type KeyboardEventHandler = (e: KeyboardEvent) => void; diff --git a/components/_util/hooks/useMemo.ts b/components/_util/hooks/useMemo.ts index f137badca..769136999 100644 --- a/components/_util/hooks/useMemo.ts +++ b/components/_util/hooks/useMemo.ts @@ -1,9 +1,9 @@ -import type { Ref } from 'vue'; +import type { Ref, WatchSource } from 'vue'; import { ref, watch } from 'vue'; export default function useMemo( getValue: () => T, - condition: any[], + condition: (WatchSource | object)[], shouldUpdate?: (prev: any[], next: any[]) => boolean, ) { const cacheRef: Ref = ref(getValue() as any); diff --git a/components/_util/hooks/useMergedState.ts b/components/_util/hooks/useMergedState.ts new file mode 100644 index 000000000..ac9ab33a9 --- /dev/null +++ b/components/_util/hooks/useMergedState.ts @@ -0,0 +1,50 @@ +import type { Ref, UnwrapRef } from 'vue'; +import { watchEffect } from 'vue'; +import { unref } from 'vue'; +import { watch } from 'vue'; +import { ref } from 'vue'; + +export default function useMergedState>( + defaultStateValue: T | (() => T), + option?: { + defaultValue?: T | (() => T); + value?: Ref | Ref>; + onChange?: (val: T, prevValue: T) => void; + postState?: (val: T) => T; + }, +): [R, (val: T) => void] { + const { defaultValue, value } = option || {}; + let initValue: T = + typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue; + if (value.value !== undefined) { + initValue = unref(value as any) as T; + } + if (defaultValue !== undefined) { + initValue = typeof defaultValue === 'function' ? (defaultValue as any)() : defaultValue; + } + + const innerValue = ref(initValue) as Ref; + const mergedValue = ref(initValue) as Ref; + watchEffect(() => { + let val = value.value !== undefined ? value.value : innerValue.value; + if (option.postState) { + val = option.postState(val as T); + } + mergedValue.value = val as T; + }); + + function triggerChange(newValue: T) { + const preVal = mergedValue.value; + innerValue.value = newValue; + if (mergedValue.value !== newValue && option.onChange) { + option.onChange(newValue, preVal); + } + } + + // Effect of reset value to `undefined` + watch(value, () => { + innerValue.value = value.value as T; + }); + + return [mergedValue as unknown as R, triggerChange]; +} diff --git a/components/_util/hooks/useState.ts b/components/_util/hooks/useState.ts new file mode 100644 index 000000000..a74476a9e --- /dev/null +++ b/components/_util/hooks/useState.ts @@ -0,0 +1,17 @@ +import type { Ref } from 'vue'; +import { ref } from 'vue'; + +export default function useState>( + defaultStateValue: T | (() => T), +): [R, (val: T) => void] { + const initValue: T = + typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue; + + const innerValue = ref(initValue) as Ref; + + function triggerChange(newValue: T) { + innerValue.value = newValue; + } + + return [innerValue as unknown as R, triggerChange]; +} diff --git a/components/vc-picker/PanelContext.tsx b/components/vc-picker/PanelContext.tsx index 85d8341e8..82193aa2e 100644 --- a/components/vc-picker/PanelContext.tsx +++ b/components/vc-picker/PanelContext.tsx @@ -9,22 +9,21 @@ export type ContextOperationRefProps = { export type PanelContextProps = { operationRef?: Ref; /** Only work with time panel */ - hideHeader?: boolean; + hideHeader?: Ref; panelRef?: Ref; - hidePrevBtn?: boolean; - hideNextBtn?: boolean; + hidePrevBtn?: Ref; + hideNextBtn?: Ref; onDateMouseEnter?: (date: any) => void; onDateMouseLeave?: (date: any) => void; onSelect?: OnSelect; - hideRanges?: boolean; - open?: boolean; - mode?: PanelMode; + hideRanges?: Ref; + open?: Ref; + mode?: Ref; /** Only used for TimePicker and this is a deprecated prop */ - defaultOpenValue?: any; + defaultOpenValue?: Ref; }; - const PanelContextKey: InjectionKey = Symbol('PanelContextProps'); export const useProvidePanel = (props: PanelContextProps) => { diff --git a/components/vc-picker/Picker.tsx b/components/vc-picker/Picker.tsx index f2e7253e0..bb78bb712 100644 --- a/components/vc-picker/Picker.tsx +++ b/components/vc-picker/Picker.tsx @@ -11,11 +11,6 @@ * Tips: Should add faq about `datetime` mode with `defaultValue` */ -import * as React from 'react'; -import classNames from 'classnames'; -import type { AlignType } from 'rc-trigger/lib/interface'; -import warning from 'rc-util/lib/warning'; -import useMergedState from 'rc-util/lib/hooks/useMergedState'; import type { PickerPanelBaseProps, PickerPanelDateProps, @@ -33,6 +28,22 @@ import usePickerInput from './hooks/usePickerInput'; import useTextValueMapping from './hooks/useTextValueMapping'; import useValueTexts from './hooks/useValueTexts'; import useHoverValue from './hooks/useHoverValue'; +import { + computed, + CSSProperties, + defineComponent, + HtmlHTMLAttributes, + ref, + Ref, + toRef, + toRefs, +} from 'vue'; +import { FocusEventHandler, MouseEventHandler } from '../_util/EventInterface'; +import { VueNode } from '../_util/type'; +import { AlignType } from '../vc-align/interface'; +import useMergedState from '../_util/hooks/useMergedState'; +import { locale } from 'dayjs'; +import { warning } from '../vc-util/warning'; export type PickerRefConfig = { focus: () => void; @@ -42,13 +53,13 @@ export type PickerRefConfig = { export type PickerSharedProps = { dropdownClassName?: string; dropdownAlign?: AlignType; - popupStyle?: React.CSSProperties; + popupStyle?: CSSProperties; transitionName?: string; placeholder?: string; allowClear?: boolean; - autoFocus?: boolean; + autofocus?: boolean; disabled?: boolean; - tabIndex?: number; + tabindex?: number; open?: boolean; defaultOpen?: boolean; /** Make input readOnly to avoid popup keyboard in mobile */ @@ -59,39 +70,39 @@ export type PickerSharedProps = { format?: string | CustomFormat | (string | CustomFormat)[]; // Render - suffixIcon?: React.ReactNode; - clearIcon?: React.ReactNode; - prevIcon?: React.ReactNode; - nextIcon?: React.ReactNode; - superPrevIcon?: React.ReactNode; - superNextIcon?: React.ReactNode; + suffixIcon?: VueNode; + clearIcon?: VueNode; + prevIcon?: VueNode; + nextIcon?: VueNode; + superPrevIcon?: VueNode; + superNextIcon?: VueNode; getPopupContainer?: (node: HTMLElement) => HTMLElement; - panelRender?: (originPanel: React.ReactNode) => React.ReactNode; + panelRender?: (originPanel: VueNode) => VueNode; // Events onChange?: (value: DateType | null, dateString: string) => void; onOpenChange?: (open: boolean) => void; - onFocus?: React.FocusEventHandler; - onBlur?: React.FocusEventHandler; - onMouseDown?: React.MouseEventHandler; - onMouseUp?: React.MouseEventHandler; - onMouseEnter?: React.MouseEventHandler; - onMouseLeave?: React.MouseEventHandler; - onClick?: React.MouseEventHandler; - onContextMenu?: React.MouseEventHandler; - onKeyDown?: (event: React.KeyboardEvent, preventDefault: () => void) => void; + onFocus?: FocusEventHandler; + onBlur?: FocusEventHandler; + onMouseDown?: MouseEventHandler; + onMouseUp?: MouseEventHandler; + onMouseEnter?: MouseEventHandler; + onMouseLeave?: MouseEventHandler; + onClick?: MouseEventHandler; + onContextMenu?: MouseEventHandler; + onKeyDown?: (event: KeyboardEvent, preventDefault: () => void) => void; // Internal /** @private Internal usage, do not use in production mode!!! */ - pickerRef?: React.MutableRefObject; + pickerRef?: Ref; // WAI-ARIA role?: string; name?: string; - autoComplete?: string; + autocomplete?: string; direction?: 'ltr' | 'rtl'; -} & React.AriaAttributes; +} & HtmlHTMLAttributes; type OmitPanelProps = Omit< Props, @@ -127,435 +138,224 @@ type MergedPickerProps = { picker?: PickerMode; } & OmitType; -function InnerPicker(props: PickerProps) { - const { - prefixCls = 'rc-picker', - id, - tabIndex, - style, - className, - dropdownClassName, - dropdownAlign, - popupStyle, - transitionName, - generateConfig, - locale, - inputReadOnly, - allowClear, - autoFocus, - showTime, - picker = 'date', - format, - use12Hours, - value, - defaultValue, - open, - defaultOpen, - defaultOpenValue, - suffixIcon, - clearIcon, - disabled, - disabledDate, - placeholder, - getPopupContainer, - pickerRef, - panelRender, - onChange, - onOpenChange, - onFocus, - onBlur, - onMouseDown, - onMouseUp, - onMouseEnter, - onMouseLeave, - onContextMenu, - onClick, - onKeyDown, - onSelect, - direction, - autoComplete = 'off', - } = props as MergedPickerProps; +function Picker() { + return defineComponent>({ + name: 'Picker', + props: [ + 'prefixCls', + 'id', + 'tabindex', + 'dropdownClassName', + 'dropdownAlign', + 'popupStyle', + 'transitionName', + 'generateConfig', + 'locale', + 'inputReadOnly', + 'allowClear', + 'autofocus', + 'showTime', + 'picker', + 'format', + 'use12Hours', + 'value', + 'defaultValue', + 'open', + 'defaultOpen', + 'defaultOpenValue', + 'suffixIcon', + 'clearIcon', + 'disabled', + 'disabledDate', + 'placeholder', + 'getPopupContainer', + 'pickerRef', + 'panelRender', + 'onChange', + 'onOpenChange', + 'onFocus', + 'onBlur', + 'onMouseDown', + 'onMouseUp', + 'onMouseEnter', + 'onMouseLeave', + 'onContextMenu', + 'onClick', + 'onKeyDown', + 'onSelect', + 'direction', + 'autocomplete', + ] as any, + inheritAttrs: false, + slots: [ + 'suffixIcon', + 'clearIcon', + 'prevIcon', + 'nextIcon', + 'superPrevIcon', + 'superNextIcon', + 'panelRender', + ], + setup(props, { slots, attrs, expose }) { + const inputRef = ref(null); + const needConfirmButton = computed( + () => (props.picker === 'date' && !!props.showTime) || props.picker === 'time', + ); - const inputRef = React.useRef(null); + // ============================= State ============================= + const formatList = computed(() => + toArray(getDefaultFormat(props.format, props.picker, props.showTime, props.use12Hours)), + ); - const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time'; + // Panel ref + const panelDivRef = ref(null); + const inputDivRef = ref(null); - // ============================= State ============================= - const formatList = toArray(getDefaultFormat(format, picker, showTime, use12Hours)); - - // Panel ref - const panelDivRef = React.useRef(null); - const inputDivRef = React.useRef(null); - - // Real value - const [mergedValue, setInnerValue] = useMergedState(null, { - value, - defaultValue, - }); - - // Selected value - const [selectedValue, setSelectedValue] = React.useState(mergedValue); - - // Operation ref - const operationRef: React.MutableRefObject = - React.useRef(null); - - // Open - const [mergedOpen, triggerInnerOpen] = useMergedState(false, { - value: open, - defaultValue: defaultOpen, - postState: (postOpen) => (disabled ? false : postOpen), - onChange: (newOpen) => { - if (onOpenChange) { - onOpenChange(newOpen); - } - - if (!newOpen && operationRef.current && operationRef.current.onClose) { - operationRef.current.onClose(); - } - }, - }); - - // ============================= Text ============================== - const [valueTexts, firstValueText] = useValueTexts(selectedValue, { - formatList, - generateConfig, - locale, - }); - - const [text, triggerTextChange, resetText] = useTextValueMapping({ - valueTexts, - onTextChange: (newText) => { - const inputDate = parseValue(newText, { - locale, - formatList, - generateConfig, + // Real value + const [mergedValue, setInnerValue] = useMergedState(null, { + value: toRef(props, 'value'), + defaultValue: props.defaultValue, }); - if (inputDate && (!disabledDate || !disabledDate(inputDate))) { - setSelectedValue(inputDate); - } - }, - }); - // ============================ Trigger ============================ - const triggerChange = (newValue: DateType | null) => { - setSelectedValue(newValue); - setInnerValue(newValue); + const selectedValue = ref(mergedValue.value) as Ref; + const setSelectedValue = (val: DateType) => { + selectedValue.value = val; + }; - if (onChange && !isEqual(generateConfig, mergedValue, newValue)) { - onChange( - newValue, - newValue ? formatValue(newValue, { generateConfig, locale, format: formatList[0] }) : '', - ); - } - }; + // Operation ref + const operationRef = ref(null); - const triggerOpen = (newOpen: boolean) => { - if (disabled && newOpen) { - return; - } + // Open + const [mergedOpen, triggerInnerOpen] = useMergedState(false, { + value: toRef(props, 'open'), + defaultValue: props.defaultOpen, + postState: postOpen => (props.disabled ? false : postOpen), + onChange: newOpen => { + if (props.onOpenChange) { + props.onOpenChange(newOpen); + } - triggerInnerOpen(newOpen); - }; + if (!newOpen && operationRef.value && operationRef.value.onClose) { + operationRef.value.onClose(); + } + }, + }); - const forwardKeyDown = (e: React.KeyboardEvent) => { - if (mergedOpen && operationRef.current && operationRef.current.onKeyDown) { - // Let popup panel handle keyboard - return operationRef.current.onKeyDown(e); - } + // ============================= Text ============================== + const texts = useValueTexts(selectedValue, { + formatList, + generateConfig: toRef(props, 'generateConfig'), + locale: toRef(props, 'locale'), + }); + const valueTexts = computed(() => texts.value[0]); + const firstValueText = computed(() => texts.value[1]); - /* 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; - } - }; + const [text, triggerTextChange, resetText] = useTextValueMapping({ + valueTexts, + onTextChange: newText => { + const inputDate = parseValue(newText, { + locale: props.locale, + formatList: formatList.value, + generateConfig: props.generateConfig, + }); + if (inputDate && (!props.disabledDate || !props.disabledDate(inputDate))) { + setSelectedValue(inputDate); + } + }, + }); - const onInternalMouseUp: React.MouseEventHandler = (...args) => { - if (onMouseUp) { - onMouseUp(...args); - } + // ============================ Trigger ============================ + const triggerChange = (newValue: DateType | null) => { + const { onChange, generateConfig, locale } = props; + setSelectedValue(newValue); + setInnerValue(newValue); - if (inputRef.current) { - inputRef.current.focus(); - triggerOpen(true); - } - }; - - // ============================= Input ============================= - const [inputProps, { focused, typing }] = usePickerInput({ - blurToCancel: needConfirmButton, - open: mergedOpen, - value: text, - triggerOpen, - forwardKeyDown, - isClickOutside: (target) => - !elementsContains([panelDivRef.current, inputDivRef.current], target as HTMLElement), - onSubmit: () => { - if (disabledDate && disabledDate(selectedValue)) { - return false; - } - - triggerChange(selectedValue); - triggerOpen(false); - resetText(); - return true; - }, - onCancel: () => { - triggerOpen(false); - setSelectedValue(mergedValue); - resetText(); - }, - onKeyDown: (e, preventDefault) => { - onKeyDown?.(e, preventDefault); - }, - onFocus, - onBlur, - }); - - // ============================= Sync ============================== - // Close should sync back with text value - React.useEffect(() => { - if (!mergedOpen) { - setSelectedValue(mergedValue); - - if (!valueTexts.length || valueTexts[0] === '') { - triggerTextChange(''); - } else if (firstValueText !== text) { - resetText(); - } - } - }, [mergedOpen, valueTexts]); - - // Change picker should sync back with text value - React.useEffect(() => { - if (!mergedOpen) { - resetText(); - } - }, [picker]); - - // Sync innerValue with control mode - React.useEffect(() => { - // Sync select value - setSelectedValue(mergedValue); - }, [mergedValue]); - - // ============================ Private ============================ - if (pickerRef) { - pickerRef.current = { - focus: () => { - if (inputRef.current) { - inputRef.current.focus(); + if (onChange && !isEqual(generateConfig, mergedValue.value, newValue)) { + onChange( + newValue, + newValue + ? formatValue(newValue, { generateConfig, locale, format: formatList[0] }) + : '', + ); } - }, - blur: () => { - if (inputRef.current) { - inputRef.current.blur(); + }; + + const triggerOpen = (newOpen: boolean) => { + if (props.disabled && newOpen) { + return; } - }, - }; - } - const [hoverValue, onEnter, onLeave] = useHoverValue(text, { - formatList, - generateConfig, - locale, - }); + triggerInnerOpen(newOpen); + }; - // ============================= Panel ============================= - const panelProps = { - // Remove `picker` & `format` here since TimePicker is little different with other panel - ...(props as Omit, 'picker' | 'format'>), - className: undefined, - style: undefined, - pickerValue: undefined, - onPickerValueChange: undefined, - onChange: null, - }; + const forwardKeyDown = (e: KeyboardEvent) => { + if (mergedOpen && operationRef.value && operationRef.value.onKeyDown) { + // Let popup panel handle keyboard + return operationRef.value.onKeyDown(e); + } - let panelNode: React.ReactNode = ( - - {...panelProps} - generateConfig={generateConfig} - class={classNames({ - [`${prefixCls}-panel-focused`]: !typing, - })} - value={selectedValue} - locale={locale} - tabIndex={-1} - onSelect={(date) => { - onSelect?.(date); - setSelectedValue(date); - }} - direction={direction} - onPanelChange={(viewDate, mode) => { - const { onPanelChange } = props; - onLeave(true); - onPanelChange?.(viewDate, mode); - }} - /> - ); + /* 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; + } + }; - if (panelRender) { - panelNode = panelRender(panelNode); - } + const onInternalMouseUp: MouseEventHandler = (...args) => { + if (props.onMouseUp) { + props.onMouseUp(...args); + } - const panel = ( -
{ - e.preventDefault(); - }} - > - {panelNode} -
- ); + if (inputRef.value) { + inputRef.value.focus(); + triggerOpen(true); + } + }; - let suffixNode: React.ReactNode; - if (suffixIcon) { - suffixNode = {suffixIcon}; - } - - let clearNode: React.ReactNode; - if (allowClear && mergedValue && !disabled) { - clearNode = ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onMouseup={(e) => { - e.preventDefault(); - e.stopPropagation(); - triggerChange(null); - triggerOpen(false); - }} - class={`${prefixCls}-clear`} - role="button" - > - {clearIcon || } - - ); - } - - // ============================ Warning ============================ - if (process.env.NODE_ENV !== 'production') { - warning( - !defaultOpenValue, - '`defaultOpenValue` may confuse user for the current value status. Please use `defaultValue` instead.', - ); - } - - // ============================ Return ============================= - const onContextSelect = (date: DateType, type: 'key' | 'mouse' | 'submit') => { - if (type === 'submit' || (type !== 'key' && !needConfirmButton)) { - // triggerChange will also update selected values - triggerChange(date); - triggerOpen(false); - } - }; - const popupPlacement = direction === 'rtl' ? 'bottomRight' : 'bottomLeft'; - - return ( - - -
-
- { - triggerTextChange(e.target.value); - }} - autofocus={autoFocus} - placeholder={placeholder} - ref={inputRef} - title={text} - {...inputProps} - size={getInputSize(picker, formatList[0], generateConfig)} - {...getDataOrAriaProps(props)} - autocomplete={autoComplete} - /> - {suffixNode} - {clearNode} -
-
-
-
- ); + value: text, + triggerOpen, + forwardKeyDown, + isClickOutside: target => + !elementsContains([panelDivRef.current, inputDivRef.current], target as HTMLElement), + onSubmit: () => { + if (props.disabledDate && props.disabledDate(selectedValue.value)) { + return false; + } + + triggerChange(selectedValue.value); + triggerOpen(false); + resetText(); + return true; + }, + onCancel: () => { + triggerOpen(false); + setSelectedValue(mergedValue.value); + resetText(); + }, + onKeyDown: (e, preventDefault) => { + props.onKeyDown?.(e, preventDefault); + }, + onFocus: (e: FocusEvent) => { + props.onFocus?.(e); + }, + onBlur: (e: FocusEvent) => { + props.onBlur?.(e); + }, + }); + + return () => { + return null; + }; + }, + }); } -// Wrap with class component to enable pass generic with instance method -class Picker extends React.Component> { - pickerRef = React.createRef(); - - focus = () => { - if (this.pickerRef.current) { - this.pickerRef.current.focus(); - } - }; - - blur = () => { - if (this.pickerRef.current) { - this.pickerRef.current.blur(); - } - }; - - render() { - return ( - - {...this.props} - pickerRef={this.pickerRef as React.MutableRefObject} - /> - ); - } -} - -export default Picker; +export default Picker(); diff --git a/components/vc-picker/PickerPanel.tsx b/components/vc-picker/PickerPanel.tsx index 035c90acb..2ba28d7e7 100644 --- a/components/vc-picker/PickerPanel.tsx +++ b/components/vc-picker/PickerPanel.tsx @@ -4,12 +4,6 @@ * click will trigger `onSelect` (if value changed trigger `onChange` also). * Panel change will not trigger `onSelect` but trigger `onPanelChange` */ - -import * as React from 'react'; -import classNames from 'classnames'; -import KeyCode from 'rc-util/lib/KeyCode'; -import warning from 'rc-util/lib/warning'; -import useMergedState from 'rc-util/lib/hooks/useMergedState'; import type { SharedTimeProps } from './panels/TimePanel'; import TimePanel from './panels/TimePanel'; import DatetimePanel from './panels/DatetimePanel'; @@ -30,22 +24,28 @@ import type { Components, } from './interface'; import { isEqual } from './utils/dateUtil'; -import PanelContext from './PanelContext'; +import { useInjectPanel, useProvidePanel } from './PanelContext'; import type { DateRender } from './panels/DatePanel/DateBody'; import { PickerModeMap } from './utils/uiUtil'; import type { MonthCellRender } from './panels/MonthPanel/MonthBody'; -import RangeContext from './RangeContext'; +import { useInjectRange } from './RangeContext'; import getExtraFooter from './utils/getExtraFooter'; import getRanges from './utils/getRanges'; import { getLowerBoundTime, setDateTime, setTime } from './utils/timeUtil'; +import { VueNode } from '../_util/type'; +import { computed, defineComponent, ref, toRef, watch, watchEffect } from 'vue'; +import useMergedState from '../_util/hooks/useMergedState'; +import { warning } from '../vc-util/warning'; +import KeyCode from '../_util/KeyCode'; +import classNames from '../_util/classNames'; export type PickerPanelSharedProps = { prefixCls?: string; - className?: string; - style?: React.CSSProperties; + // className?: string; + // style?: React.CSSProperties; /** @deprecated Will be removed in next big version. Please use `mode` instead */ mode?: PanelMode; - tabIndex?: number; + tabindex?: number; // Locale locale: Locale; @@ -65,13 +65,13 @@ export type PickerPanelSharedProps = { // Render dateRender?: DateRender; monthCellRender?: MonthCellRender; - renderExtraFooter?: (mode: PanelMode) => React.ReactNode; + renderExtraFooter?: (mode: PanelMode) => VueNode; // Event onSelect?: (value: DateType) => void; onChange?: (value: DateType) => void; onPanelChange?: OnPanelChange; - onMouseDown?: React.MouseEventHandler; + onMouseDown?: (e: MouseEvent) => void; onOk?: (date: DateType) => void; direction?: 'ltr' | 'rtl'; @@ -101,7 +101,8 @@ export type PickerPanelDateProps = { export type PickerPanelTimeProps = { picker: 'time'; -} & PickerPanelSharedProps & SharedTimeProps; +} & PickerPanelSharedProps & + SharedTimeProps; export type PickerPanelProps = | PickerPanelBaseProps @@ -116,454 +117,486 @@ type MergedPickerPanelProps = { picker?: PickerMode; } & OmitType; -function PickerPanel(props: PickerPanelProps) { - const { - prefixCls = 'rc-picker', - className, - style, - locale, - generateConfig, - value, - defaultValue, - pickerValue, - defaultPickerValue, - disabledDate, - mode, - picker = 'date', - tabIndex = 0, - showNow, - showTime, - showToday, - renderExtraFooter, - hideHeader, - onSelect, - onChange, - onPanelChange, - onMouseDown, - onPickerValueChange, - onOk, - components, - direction, - hourStep = 1, - minuteStep = 1, - secondStep = 1, - } = props as MergedPickerPanelProps; +function PickerPanel() { + return defineComponent>({ + name: 'PickerPanel', + inheritAttrs: false, + props: [ + 'prefixCls', + 'locale', + 'generateConfig', + 'value', + 'defaultValue', + 'pickerValue', + 'defaultPickerValue', + 'disabledDate', + 'mode', + { picker: { default: 'date' } }, + { tabindex: { default: 0 } }, + 'showNow', + 'showTime', + 'showToday', + 'renderExtraFooter', + 'hideHeader', + 'onSelect', + 'onChange', + 'onPanelChange', + 'onMouseDown', + 'onPickerValueChange', + 'onOk', + 'components', + 'direction', + { hourStep: { default: 1 } }, + { minuteStep: { default: 1 } }, + { secondStep: { default: 1 } }, + ] as any, + setup(props, { attrs }) { + const needConfirmButton = computed( + () => (props.picker === 'date' && !!props.showTime) || props.picker === 'time', + ); - const needConfirmButton: boolean = (picker === 'date' && !!showTime) || picker === 'time'; - - const isHourStepValid = 24 % hourStep === 0; - const isMinuteStepValid = 60 % minuteStep === 0; - const isSecondStepValid = 60 % secondStep === 0; - - if (process.env.NODE_ENV !== 'production') { - warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `value`.'); - warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `defaultValue`.'); - warning(isHourStepValid, `\`hourStep\` ${hourStep} is invalid. It should be a factor of 24.`); - warning( - isMinuteStepValid, - `\`minuteStep\` ${minuteStep} is invalid. It should be a factor of 60.`, - ); - warning( - isSecondStepValid, - `\`secondStep\` ${secondStep} is invalid. It should be a factor of 60.`, - ); - } - - // ============================ State ============================= - - const panelContext = React.useContext(PanelContext); - const { - operationRef, - panelRef: panelDivRef, - onSelect: onContextSelect, - hideRanges, - defaultOpenValue, - } = panelContext; - - const { inRange, panelPosition, rangedValue, hoverRangedValue } = React.useContext(RangeContext); - const panelRef = React.useRef({}); - - // Handle init logic - const initRef = React.useRef(true); - - // Value - const [mergedValue, setInnerValue] = useMergedState(null, { - value, - defaultValue, - postState: (val) => { - if (!val && defaultOpenValue && picker === 'time') { - return defaultOpenValue; + const isHourStepValid = computed(() => 24 % props.hourStep === 0); + const isMinuteStepValid = computed(() => 60 % props.minuteStep === 0); + const isSecondStepValid = computed(() => 60 % props.secondStep === 0); + if (process.env.NODE_ENV !== 'production') { + watchEffect(() => { + const { generateConfig, value, hourStep = 1, minuteStep = 1, secondStep = 1 } = props; + warning(!value || generateConfig.isValidate(value), 'Invalidate date pass to `value`.'); + warning( + !value || generateConfig.isValidate(value), + 'Invalidate date pass to `defaultValue`.', + ); + warning( + isHourStepValid.value, + `\`hourStep\` ${hourStep} is invalid. It should be a factor of 24.`, + ); + warning( + isMinuteStepValid.value, + `\`minuteStep\` ${minuteStep} is invalid. It should be a factor of 60.`, + ); + warning( + isSecondStepValid.value, + `\`secondStep\` ${secondStep} is invalid. It should be a factor of 60.`, + ); + }); } - return val; - }, - }); - // View date control - const [viewDate, setInnerViewDate] = useMergedState(null, { - value: pickerValue, - defaultValue: defaultPickerValue || mergedValue, - postState: (date) => { - const now = generateConfig.getNow(); - if (!date) return now; - // When value is null and set showTime - if (!mergedValue && showTime) { - if (typeof showTime === 'object') { - return setDateTime(generateConfig, date, showTime.defaultValue || now); + const panelContext = useInjectPanel(); + const { + operationRef, + panelRef: panelDivRef, + onSelect: onContextSelect, + hideRanges, + defaultOpenValue, + } = panelContext; + const { inRange, panelPosition, rangedValue, hoverRangedValue } = useInjectRange(); + const panelRef = ref({}); + // Value + const [mergedValue, setInnerValue] = useMergedState(null, { + value: toRef(props, 'value'), + defaultValue: props.defaultValue, + postState: val => { + if (!val && defaultOpenValue.value && props.picker === 'time') { + return defaultOpenValue.value; + } + return val; + }, + }); + + // View date control + const [viewDate, setInnerViewDate] = useMergedState(null, { + value: toRef(props, 'pickerValue'), + defaultValue: props.defaultPickerValue || mergedValue.value, + postState: date => { + const { generateConfig, showTime, defaultValue } = props; + const now = generateConfig.getNow(); + if (!date) return now; + // When value is null and set showTime + if (!mergedValue && props.showTime) { + if (typeof showTime === 'object') { + return setDateTime(generateConfig, date, showTime.defaultValue || now); + } + if (defaultValue) { + return setDateTime(generateConfig, date, defaultValue); + } + return setDateTime(generateConfig, date, now); + } + return date; + }, + }); + + const setViewDate = (date: DateType) => { + setInnerViewDate(date); + if (props.onPickerValueChange) { + props.onPickerValueChange(date); } - if (defaultValue) { - return setDateTime(generateConfig, date, defaultValue); + }; + + // Panel control + const getInternalNextMode = (nextMode: PanelMode): PanelMode => { + const getNextMode = PickerModeMap[props.picker!]; + if (getNextMode) { + return getNextMode(nextMode); } - return setDateTime(generateConfig, date, now); - } - return date; - }, - }); - const setViewDate = (date: DateType) => { - setInnerViewDate(date); - if (onPickerValueChange) { - onPickerValueChange(date); - } - }; + return nextMode; + }; - // Panel control - const getInternalNextMode = (nextMode: PanelMode): PanelMode => { - const getNextMode = PickerModeMap[picker!]; - if (getNextMode) { - return getNextMode(nextMode); - } - - return nextMode; - }; - - // Save panel is changed from which panel - const [mergedMode, setInnerMode] = useMergedState( - () => { - if (picker === 'time') { - return 'time'; - } - return getInternalNextMode('date'); - }, - { - value: mode, - }, - ); - - React.useEffect(() => { - setInnerMode(picker); - }, [picker]); - - const [sourceMode, setSourceMode] = React.useState(() => mergedMode); - - const onInternalPanelChange = (newMode: PanelMode | null, viewValue: DateType) => { - const nextMode = getInternalNextMode(newMode || mergedMode); - setSourceMode(mergedMode); - setInnerMode(nextMode); - - if (onPanelChange && (mergedMode !== nextMode || isEqual(generateConfig, viewDate, viewDate))) { - onPanelChange(viewValue, nextMode); - } - }; - - const triggerSelect = ( - date: DateType, - type: 'key' | 'mouse' | 'submit', - forceTriggerSelect: boolean = false, - ) => { - if (mergedMode === picker || forceTriggerSelect) { - setInnerValue(date); - - if (onSelect) { - onSelect(date); - } - - if (onContextSelect) { - onContextSelect(date, type); - } - - if (onChange && !isEqual(generateConfig, date, mergedValue) && !disabledDate?.(date)) { - onChange(date); - } - } - }; - - // ========================= Interactive ========================== - const onInternalKeyDown = (e: React.KeyboardEvent) => { - if (panelRef.current && panelRef.current.onKeyDown) { - if ( - [ - KeyCode.LEFT, - KeyCode.RIGHT, - KeyCode.UP, - KeyCode.DOWN, - KeyCode.PAGE_UP, - KeyCode.PAGE_DOWN, - KeyCode.ENTER, - ].includes(e.which) - ) { - e.preventDefault(); - } - return panelRef.current.onKeyDown(e); - } - - /* istanbul ignore next */ - /* eslint-disable no-lone-blocks */ - { - warning( - false, - 'Panel not correct handle keyDown event. Please help to fire issue about this.', + // Save panel is changed from which panel + const [mergedMode, setInnerMode] = useMergedState( + () => { + if (props.picker === 'time') { + return 'time'; + } + return getInternalNextMode('date'); + }, + { + value: toRef(props, 'mode'), + }, + ); + watch( + () => props.picker, + () => { + setInnerMode(props.picker); + }, ); - return false; - } - /* eslint-enable no-lone-blocks */ - }; - const onInternalBlur: React.FocusEventHandler = (e) => { - if (panelRef.current && panelRef.current.onBlur) { - panelRef.current.onBlur(e); - } - }; + const sourceMode = ref(mergedMode.value); + const setSourceMode = (val: PanelMode) => { + sourceMode.value = val; + }; - if (operationRef && panelPosition !== 'right') { - operationRef.current = { - onKeyDown: onInternalKeyDown, - onClose: () => { - if (panelRef.current && panelRef.current.onClose) { - panelRef.current.onClose(); + const onInternalPanelChange = (newMode: PanelMode | null, viewValue: DateType) => { + const { onPanelChange, generateConfig } = props; + const nextMode = getInternalNextMode(newMode || mergedMode.value); + setSourceMode(mergedMode.value); + setInnerMode(nextMode); + + if ( + onPanelChange && + (mergedMode.value !== nextMode || isEqual(generateConfig, viewDate.value, viewDate.value)) + ) { + onPanelChange(viewValue, nextMode); } - }, - }; - } + }; - // ============================ Effect ============================ - React.useEffect(() => { - if (value && !initRef.current) { - setInnerViewDate(value); - } - }, [value]); + const triggerSelect = ( + date: DateType, + type: 'key' | 'mouse' | 'submit', + forceTriggerSelect: boolean = false, + ) => { + const { picker, generateConfig, onSelect, onChange, disabledDate } = props; + if (mergedMode.value === picker || forceTriggerSelect) { + setInnerValue(date); - React.useEffect(() => { - initRef.current = false; - }, []); + if (onSelect) { + onSelect(date); + } - // ============================ Panels ============================ - let panelNode: React.ReactNode; + if (onContextSelect) { + onContextSelect(date, type); + } - const pickerProps = { - ...(props as MergedPickerPanelProps), - operationRef: panelRef, - prefixCls, - viewDate, - value: mergedValue, - onViewDateChange: setViewDate, - sourceMode, - onPanelChange: onInternalPanelChange, - disabledDate, - }; - delete pickerProps.onChange; - delete pickerProps.onSelect; - - switch (mergedMode) { - case 'decade': - panelNode = ( - - {...pickerProps} - onSelect={(date, type) => { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - break; - - case 'year': - panelNode = ( - - {...pickerProps} - onSelect={(date, type) => { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - break; - - case 'month': - panelNode = ( - - {...pickerProps} - onSelect={(date, type) => { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - break; - - case 'quarter': - panelNode = ( - - {...pickerProps} - onSelect={(date, type) => { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - break; - - case 'week': - panelNode = ( - { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - break; - - case 'time': - delete pickerProps.showTime; - panelNode = ( - - {...pickerProps} - {...(typeof showTime === 'object' ? showTime : null)} - onSelect={(date, type) => { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - break; - - default: - if (showTime) { - panelNode = ( - { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - } else { - panelNode = ( - - {...pickerProps} - onSelect={(date, type) => { - setViewDate(date); - triggerSelect(date, type); - }} - /> - ); - } - } - - // ============================ Footer ============================ - let extraFooter: React.ReactNode; - let rangesNode: React.ReactNode; - - const onNow = () => { - const now = generateConfig.getNow(); - const lowerBoundTime = getLowerBoundTime( - generateConfig.getHour(now), - generateConfig.getMinute(now), - generateConfig.getSecond(now), - isHourStepValid ? hourStep : 1, - isMinuteStepValid ? minuteStep : 1, - isSecondStepValid ? secondStep : 1, - ); - const adjustedNow = setTime( - generateConfig, - now, - lowerBoundTime[0], // hour - lowerBoundTime[1], // minute - lowerBoundTime[2], // second - ); - triggerSelect(adjustedNow, 'submit'); - }; - - if (!hideRanges) { - extraFooter = getExtraFooter(prefixCls, mergedMode, renderExtraFooter); - rangesNode = getRanges({ - prefixCls, - components, - needConfirmButton, - okDisabled: !mergedValue || (disabledDate && disabledDate(mergedValue)), - locale, - showNow, - onNow: needConfirmButton && onNow, - onOk: () => { - if (mergedValue) { - triggerSelect(mergedValue, 'submit', true); - if (onOk) { - onOk(mergedValue); + if ( + onChange && + !isEqual(generateConfig, date, mergedValue.value) && + !disabledDate?.(date) + ) { + onChange(date); } } - }, - }); - } + }; - let todayNode: React.ReactNode; - - if (showToday && mergedMode === 'date' && picker === 'date' && !showTime) { - const now = generateConfig.getNow(); - const todayCls = `${prefixCls}-today-btn`; - const disabled = disabledDate && disabledDate(now); - todayNode = ( - { - if (!disabled) { - triggerSelect(now, 'mouse', true); + // ========================= Interactive ========================== + const onInternalKeyDown = (e: KeyboardEvent) => { + if (panelRef.value && panelRef.value.onKeyDown) { + if ( + [ + KeyCode.LEFT, + KeyCode.RIGHT, + KeyCode.UP, + KeyCode.DOWN, + KeyCode.PAGE_UP, + KeyCode.PAGE_DOWN, + KeyCode.ENTER, + ].includes(e.which) + ) { + e.preventDefault(); } - }} - > - {locale.today} - - ); - } + return panelRef.value.onKeyDown(e); + } - return ( - { + if (panelRef.value && panelRef.value.onBlur) { + panelRef.value.onBlur(e); + } + }; + const onNow = () => { + const { generateConfig, hourStep, minuteStep, secondStep } = props; + const now = generateConfig.getNow(); + const lowerBoundTime = getLowerBoundTime( + generateConfig.getHour(now), + generateConfig.getMinute(now), + generateConfig.getSecond(now), + isHourStepValid ? hourStep : 1, + isMinuteStepValid ? minuteStep : 1, + isSecondStepValid ? secondStep : 1, + ); + const adjustedNow = setTime( + generateConfig, + now, + lowerBoundTime[0], // hour + lowerBoundTime[1], // minute + lowerBoundTime[2], // second + ); + triggerSelect(adjustedNow, 'submit'); + }; + + const classString = computed(() => { + const { prefixCls, direction } = props; + return classNames(`${prefixCls}-panel`, { + [`${prefixCls}-panel-has-range`]: + rangedValue && rangedValue.value && rangedValue.value[0] && rangedValue.value[1], + [`${prefixCls}-panel-has-range-hover`]: + hoverRangedValue && + hoverRangedValue.value && + hoverRangedValue.value[0] && + hoverRangedValue.value[1], + [`${prefixCls}-panel-rtl`]: direction === 'rtl', + }); + }); + useProvidePanel({ ...panelContext, mode: mergedMode, - hideHeader: 'hideHeader' in props ? hideHeader : panelContext.hideHeader, - hidePrevBtn: inRange && panelPosition === 'right', - hideNextBtn: inRange && panelPosition === 'left', - }} - > -
- {panelNode} - {extraFooter || rangesNode || todayNode ? ( -
- {extraFooter} - {rangesNode} - {todayNode} + hideHeader: computed(() => + props.hideHeader !== undefined ? props.hideHeader : panelContext.hideHeader?.value, + ), + hidePrevBtn: computed(() => inRange.value && panelPosition.value === 'right'), + hideNextBtn: computed(() => inRange.value && panelPosition.value === 'left'), + }); + return () => { + const { + prefixCls = 'ant-picker', + locale, + generateConfig, + disabledDate, + picker = 'date', + tabindex = 0, + showNow, + showTime, + showToday, + renderExtraFooter, + onMouseDown, + onOk, + components, + } = props; + if (operationRef && panelPosition.value !== 'right') { + operationRef.value = { + onKeyDown: onInternalKeyDown, + onClose: () => { + if (panelRef.value && panelRef.value.onClose) { + panelRef.value.onClose(); + } + }, + }; + } + + // ============================ Panels ============================ + let panelNode: VueNode; + + const pickerProps = { + ...(props as MergedPickerPanelProps), + operationRef: panelRef, + prefixCls, + viewDate: viewDate.value, + value: mergedValue.value, + onViewDateChange: setViewDate, + sourceMode: sourceMode.value, + onPanelChange: onInternalPanelChange, + disabledDate, + }; + delete pickerProps.onChange; + delete pickerProps.onSelect; + + switch (mergedMode.value) { + case 'decade': + panelNode = ( + + {...pickerProps} + onSelect={(date, type) => { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + break; + + case 'year': + panelNode = ( + + {...pickerProps} + onSelect={(date, type) => { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + break; + + case 'month': + panelNode = ( + + {...pickerProps} + onSelect={(date, type) => { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + break; + + case 'quarter': + panelNode = ( + + {...pickerProps} + onSelect={(date, type) => { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + break; + + case 'week': + panelNode = ( + { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + break; + + case 'time': + delete pickerProps.showTime; + panelNode = ( + + {...pickerProps} + {...(typeof showTime === 'object' ? showTime : null)} + onSelect={(date, type) => { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + break; + + default: + if (showTime) { + panelNode = ( + { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + } else { + panelNode = ( + + {...pickerProps} + onSelect={(date, type) => { + setViewDate(date); + triggerSelect(date, type); + }} + /> + ); + } + } + + // ============================ Footer ============================ + let extraFooter: VueNode; + let rangesNode: VueNode; + + if (!hideRanges) { + extraFooter = getExtraFooter(prefixCls, mergedMode.value, renderExtraFooter); + rangesNode = getRanges({ + prefixCls, + components, + needConfirmButton: needConfirmButton.value, + okDisabled: !mergedValue || (disabledDate && disabledDate(mergedValue.value)), + locale, + showNow, + onNow: needConfirmButton.value && onNow, + onOk: () => { + if (mergedValue) { + triggerSelect(mergedValue.value, 'submit', true); + if (onOk) { + onOk(mergedValue.value); + } + } + }, + }); + } + + let todayNode: VueNode; + + if (showToday && mergedMode.value === 'date' && picker === 'date' && !showTime) { + const now = generateConfig.getNow(); + const todayCls = `${prefixCls}-today-btn`; + const disabled = disabledDate && disabledDate(now); + todayNode = ( + { + if (!disabled) { + triggerSelect(now, 'mouse', true); + } + }} + > + {locale.today} + + ); + } + return ( +
+ {panelNode} + {extraFooter || rangesNode || todayNode ? ( +
+ {extraFooter} + {rangesNode} + {todayNode} +
+ ) : null}
- ) : null} -
- - ); + ); + }; + }, + }); } -export default PickerPanel; -/* eslint-enable */ +export default PickerPanel(); diff --git a/components/vc-picker/PickerTrigger.tsx b/components/vc-picker/PickerTrigger.tsx index a20fc760f..a1f5f28a8 100644 --- a/components/vc-picker/PickerTrigger.tsx +++ b/components/vc-picker/PickerTrigger.tsx @@ -3,6 +3,7 @@ import { AlignType } from '../vc-align/interface'; import Trigger from '../vc-trigger'; import classNames from '../_util/classNames'; import { VueNode } from '../_util/type'; +import useMergeProps from './hooks/useMergeProps'; const BUILT_IN_PLACEMENTS = { bottomLeft: { @@ -56,8 +57,8 @@ export type PickerTriggerProps = { direction?: 'ltr' | 'rtl'; }; -function PickerTrigger( - { +function PickerTrigger(props: PickerTriggerProps, { slots }) { + const { prefixCls, popupElement, popupStyle, @@ -69,9 +70,7 @@ function PickerTrigger( range, popupPlacement, direction, - }: PickerTriggerProps, - { slots }, -) { + } = useMergeProps(props); const dropdownPrefixCls = `${prefixCls}-dropdown`; const getPopupPlacement = () => { diff --git a/components/vc-picker/RangeContext.tsx b/components/vc-picker/RangeContext.tsx index ef64a6195..b9a4ac8e7 100644 --- a/components/vc-picker/RangeContext.tsx +++ b/components/vc-picker/RangeContext.tsx @@ -1,4 +1,4 @@ -import { inject, InjectionKey, provide } from 'vue'; +import { inject, InjectionKey, provide, Ref } from 'vue'; import type { NullableDateType, RangeValue } from './interface'; export type RangeContextProps = { @@ -6,13 +6,12 @@ export type RangeContextProps = { * Set displayed range value style. * Panel only has one value, this is only style effect. */ - rangedValue?: [NullableDateType, NullableDateType] | null; - hoverRangedValue?: RangeValue; - inRange?: boolean; - panelPosition?: 'left' | 'right' | false; + rangedValue?: Ref<[NullableDateType, NullableDateType] | null>; + hoverRangedValue?: Ref>; + inRange?: Ref; + panelPosition?: Ref<'left' | 'right' | false>; }; - const RangeContextKey: InjectionKey = Symbol('RangeContextProps'); export const useProvideRange = (props: RangeContextProps) => { @@ -23,5 +22,4 @@ export const useInjectRange = () => { return inject(RangeContextKey); }; - export default RangeContextKey; diff --git a/components/vc-picker/hooks/useCellClassName.ts b/components/vc-picker/hooks/useCellClassName.ts index 7aeb2b5f7..5c9bc4b38 100644 --- a/components/vc-picker/hooks/useCellClassName.ts +++ b/components/vc-picker/hooks/useCellClassName.ts @@ -16,10 +16,7 @@ export default function useCellClassName({ }: { cellPrefixCls: string; generateConfig: GenerateConfig; - isSameCell: ( - current: NullableDateType, - target: NullableDateType, - ) => boolean; + isSameCell: (current: NullableDateType, target: NullableDateType) => boolean; offsetCell: (date: DateType, offset: number) => DateType; isInView: (date: DateType) => boolean; rangedValue?: RangeValue; @@ -37,12 +34,7 @@ export default function useCellClassName({ const hoverStart = getValue(hoverRangedValue, 0); const hoverEnd = getValue(hoverRangedValue, 1); - const isRangeHovered = isInRange( - generateConfig, - hoverStart, - hoverEnd, - currentDate, - ); + const isRangeHovered = isInRange(generateConfig, hoverStart, hoverEnd, currentDate); function isRangeStart(date: DateType) { return isSameCell(rangeStart, date); @@ -54,11 +46,9 @@ export default function useCellClassName({ const isHoverEnd = isSameCell(hoverEnd, currentDate); const isHoverEdgeStart = - (isRangeHovered || isHoverEnd) && - (!isInView(prevDate) || isRangeEnd(prevDate)); + (isRangeHovered || isHoverEnd) && (!isInView(prevDate) || isRangeEnd(prevDate)); const isHoverEdgeEnd = - (isRangeHovered || isHoverStart) && - (!isInView(nextDate) || isRangeStart(nextDate)); + (isRangeHovered || isHoverStart) && (!isInView(nextDate) || isRangeStart(nextDate)); return { // In view @@ -73,10 +63,8 @@ export default function useCellClassName({ ), [`${cellPrefixCls}-range-start`]: isRangeStart(currentDate), [`${cellPrefixCls}-range-end`]: isRangeEnd(currentDate), - [`${cellPrefixCls}-range-start-single`]: - isRangeStart(currentDate) && !rangeEnd, - [`${cellPrefixCls}-range-end-single`]: - isRangeEnd(currentDate) && !rangeStart, + [`${cellPrefixCls}-range-start-single`]: isRangeStart(currentDate) && !rangeEnd, + [`${cellPrefixCls}-range-end-single`]: isRangeEnd(currentDate) && !rangeStart, [`${cellPrefixCls}-range-start-near-hover`]: isRangeStart(currentDate) && (isSameCell(prevDate, hoverStart) || diff --git a/components/vc-picker/hooks/useHoverValue.ts b/components/vc-picker/hooks/useHoverValue.ts index e24cda03e..345afa277 100644 --- a/components/vc-picker/hooks/useHoverValue.ts +++ b/components/vc-picker/hooks/useHoverValue.ts @@ -9,7 +9,7 @@ export default function useHoverValue( const [value, internalSetValue] = useState(null); const raf = useRef(null); - function setValue(val: DateType, immediately: boolean = false) { + function setValue(val: DateType, immediately = false) { cancelAnimationFrame(raf.current); if (immediately) { internalSetValue(val); @@ -30,7 +30,7 @@ export default function useHoverValue( setValue(date); } - function onLeave(immediately: boolean = false) { + function onLeave(immediately = false) { setValue(null, immediately); } diff --git a/components/vc-picker/hooks/useMergeProps.ts b/components/vc-picker/hooks/useMergeProps.ts new file mode 100644 index 000000000..54450bd6d --- /dev/null +++ b/components/vc-picker/hooks/useMergeProps.ts @@ -0,0 +1,8 @@ +import type { HTMLAttributes } from 'vue'; +import { useAttrs } from 'vue'; + +// 仅用在函数式组件中,不用考虑响应式问题 +export default function useMergeProps(props: T) { + const attrs: HTMLAttributes = useAttrs(); + return { ...props, ...attrs }; +} diff --git a/components/vc-picker/hooks/usePickerInput.ts b/components/vc-picker/hooks/usePickerInput.ts index 0d54e3fcf..dcdfb67b5 100644 --- a/components/vc-picker/hooks/usePickerInput.ts +++ b/components/vc-picker/hooks/usePickerInput.ts @@ -1,6 +1,11 @@ -import type * as React from 'react'; -import { useState, useEffect, useRef } from 'react'; -import KeyCode from 'rc-util/lib/KeyCode'; +import type { ComputedRef, HTMLAttributes, Ref } from 'vue'; +import { onBeforeUnmount } from 'vue'; +import { watchEffect } from 'vue'; +import { watch } from 'vue'; +import { ref } from 'vue'; +import { computed } from 'vue'; +import type { FocusEventHandler } from '../../_util/EventInterface'; +import KeyCode from '../../_util/KeyCode'; import { addGlobalMouseDownEvent, getTargetFromEvent } from '../utils/uiUtil'; export default function usePickerInput({ @@ -16,51 +21,51 @@ export default function usePickerInput({ onFocus, onBlur, }: { - open: boolean; - value: string; + open: Ref; + value: Ref; isClickOutside: (clickElement: EventTarget | null) => boolean; triggerOpen: (open: boolean) => void; - forwardKeyDown: (e: React.KeyboardEvent) => boolean; - onKeyDown: (e: React.KeyboardEvent, preventDefault: () => void) => void; - blurToCancel?: boolean; + forwardKeyDown: (e: KeyboardEvent) => boolean; + onKeyDown: (e: KeyboardEvent, preventDefault: () => void) => void; + blurToCancel?: ComputedRef; onSubmit: () => void | boolean; onCancel: () => void; - onFocus?: React.FocusEventHandler; - onBlur?: React.FocusEventHandler; -}): [React.DOMAttributes, { focused: boolean; typing: boolean }] { - const [typing, setTyping] = useState(false); - const [focused, setFocused] = useState(false); + onFocus?: FocusEventHandler; + onBlur?: FocusEventHandler; +}): [ComputedRef, { focused: Ref; typing: Ref }] { + const typing = ref(false); + const focused = ref(false); /** * We will prevent blur to handle open event when user click outside, * since this will repeat trigger `onOpenChange` event. */ - const preventBlurRef = useRef(false); + const preventBlurRef = ref(false); - const valueChangedRef = useRef(false); + const valueChangedRef = ref(false); - const preventDefaultRef = useRef(false); + const preventDefaultRef = ref(false); - const inputProps: React.DOMAttributes = { - onMouseDown: () => { - setTyping(true); + const inputProps = computed(() => ({ + onMousedown: () => { + typing.value = true; triggerOpen(true); }, - onKeyDown: (e) => { + onKeydown: e => { const preventDefault = (): void => { - preventDefaultRef.current = true; + preventDefaultRef.value = true; }; onKeyDown(e, preventDefault); - if (preventDefaultRef.current) return; + if (preventDefaultRef.value) return; switch (e.which) { case KeyCode.ENTER: { - if (!open) { + if (!open.value) { triggerOpen(true); } else if (onSubmit() !== false) { - setTyping(true); + typing.value = true; } e.preventDefault(); @@ -68,12 +73,12 @@ export default function usePickerInput({ } case KeyCode.TAB: { - if (typing && open && !e.shiftKey) { - setTyping(false); + if (typing.value && open.value && !e.shiftKey) { + typing.value = false; e.preventDefault(); - } else if (!typing && open) { + } else if (!typing.value && open.value) { if (!forwardKeyDown(e) && e.shiftKey) { - setTyping(true); + typing.value = true; e.preventDefault(); } } @@ -81,32 +86,32 @@ export default function usePickerInput({ } case KeyCode.ESC: { - setTyping(true); + typing.value = true; onCancel(); return; } } - if (!open && ![KeyCode.SHIFT].includes(e.which)) { + if (!open.value && ![KeyCode.SHIFT].includes(e.which)) { triggerOpen(true); - } else if (!typing) { + } else if (!typing.value) { // Let popup panel handle keyboard forwardKeyDown(e); } }, - onFocus: (e) => { - setTyping(true); - setFocused(true); + onFocus: e => { + typing.value = true; + focused.value = true; if (onFocus) { onFocus(e); } }, - onBlur: (e) => { - if (preventBlurRef.current || !isClickOutside(document.activeElement)) { - preventBlurRef.current = false; + onBlur: e => { + if (preventBlurRef.value || !isClickOutside(document.activeElement)) { + preventBlurRef.value = false; return; } @@ -121,51 +126,58 @@ export default function usePickerInput({ onCancel(); } }, 0); - } else if (open) { + } else if (open.value) { triggerOpen(false); - if (valueChangedRef.current) { + if (valueChangedRef.value) { onSubmit(); } } - setFocused(false); + focused.value = false; if (onBlur) { onBlur(e); } }, - }; + })); // check if value changed - useEffect(() => { - valueChangedRef.current = false; - }, [open]); - - useEffect(() => { - valueChangedRef.current = true; - }, [value]); + watch(open, () => { + valueChangedRef.value = false; + }); + watch(value, () => { + valueChangedRef.value = true; + }); + const globalMouseDownEvent = ref(); // Global click handler - useEffect(() => - addGlobalMouseDownEvent((e: MouseEvent) => { - const target = getTargetFromEvent(e); + watchEffect( + () => + globalMouseDownEvent.value && + globalMouseDownEvent.value()( + (globalMouseDownEvent.value = addGlobalMouseDownEvent((e: MouseEvent) => { + const target = getTargetFromEvent(e); - if (open) { - const clickedOutside = isClickOutside(target); + if (open) { + const clickedOutside = isClickOutside(target); - if (!clickedOutside) { - preventBlurRef.current = true; + if (!clickedOutside) { + preventBlurRef.value = true; - // Always set back in case `onBlur` prevented by user - requestAnimationFrame(() => { - preventBlurRef.current = false; - }); - } else if (!focused || clickedOutside) { - triggerOpen(false); - } - } - }), + // Always set back in case `onBlur` prevented by user + requestAnimationFrame(() => { + preventBlurRef.value = false; + }); + } else if (!focused.value || clickedOutside) { + triggerOpen(false); + } + } + })), + ), ); + onBeforeUnmount(() => { + globalMouseDownEvent.value && globalMouseDownEvent.value(); + }); return [inputProps, { focused, typing }]; } diff --git a/components/vc-picker/hooks/useTextValueMapping.ts b/components/vc-picker/hooks/useTextValueMapping.ts index af63b47a4..235bfa867 100644 --- a/components/vc-picker/hooks/useTextValueMapping.ts +++ b/components/vc-picker/hooks/useTextValueMapping.ts @@ -1,31 +1,36 @@ -import * as React from 'react'; +import type { ComputedRef, Ref } from 'vue'; +import { ref, watch } from 'vue'; export default function useTextValueMapping({ valueTexts, onTextChange, }: { /** Must useMemo, to assume that `valueTexts` only match on the first change */ - valueTexts: string[]; + valueTexts: ComputedRef; onTextChange: (text: string) => void; -}): [string, (text: string) => void, () => void] { - const [text, setInnerText] = React.useState(''); - const valueTextsRef = React.useRef([]); - valueTextsRef.current = valueTexts; +}): [Ref, (text: string) => void, () => void] { + const text = ref(''); function triggerTextChange(value: string) { - setInnerText(value); + text.value = value; onTextChange(value); } function resetText() { - setInnerText(valueTextsRef.current[0]); + text.value = valueTexts.value[0]; } - React.useEffect(() => { - if (valueTexts.every(valText => valText !== text)) { - resetText(); - } - }, [valueTexts.join('||')]); + watch( + () => [...valueTexts.value], + (cur, pre) => { + if ( + cur.join('||') !== pre.join('||') && + valueTexts.value.every(valText => valText !== text.value) + ) { + resetText(); + } + }, + ); return [text, triggerTextChange, resetText]; } diff --git a/components/vc-picker/hooks/useValueTexts.ts b/components/vc-picker/hooks/useValueTexts.ts index 89f01f5d9..b836ba93f 100644 --- a/components/vc-picker/hooks/useValueTexts.ts +++ b/components/vc-picker/hooks/useValueTexts.ts @@ -1,32 +1,37 @@ -import shallowEqual from 'shallowequal'; -import useMemo from 'rc-util/lib/hooks/useMemo'; +import type { ComputedRef, Ref } from 'vue'; +import useMemo from '../../_util/hooks/useMemo'; +import shallowequal from '../../_util/shallowequal'; import type { GenerateConfig } from '../generate'; import type { CustomFormat, Locale } from '../interface'; import { formatValue } from '../utils/dateUtil'; export type ValueTextConfig = { - formatList: (string | CustomFormat)[]; - generateConfig: GenerateConfig; - locale: Locale; + formatList: ComputedRef<(string | CustomFormat)[]>; + generateConfig: Ref>; + locale: Ref; }; export default function useValueTexts( - value: DateType | null, + value: Ref, { formatList, generateConfig, locale }: ValueTextConfig, ) { return useMemo<[string[], string]>( () => { - if (!value) { + if (!value.value) { return [[''], '']; } // We will convert data format back to first format - let firstValueText: string = ''; + let firstValueText = ''; const fullValueTexts: string[] = []; - for (let i = 0; i < formatList.length; i += 1) { + for (let i = 0; i < formatList.value.length; i += 1) { const format = formatList[i]; - const formatStr = formatValue(value, { generateConfig, locale, format }); + const formatStr = formatValue(value.value, { + generateConfig: generateConfig.value, + locale: locale.value, + format, + }); fullValueTexts.push(formatStr); if (i === 0) { @@ -37,6 +42,6 @@ export default function useValueTexts( return [fullValueTexts, firstValueText]; }, [value, formatList], - (prev, next) => prev[0] !== next[0] || !shallowEqual(prev[1], next[1]), + (next, prev) => prev[0] !== next[0] || !shallowequal(prev[1], next[1]), ); } diff --git a/components/vc-picker/interface.ts b/components/vc-picker/interface.ts index 2eba561be..6bf906f79 100644 --- a/components/vc-picker/interface.ts +++ b/components/vc-picker/interface.ts @@ -1,3 +1,4 @@ +import type { Ref } from 'vue'; import type { GenerateConfig } from './generate'; export type Locale = { @@ -46,7 +47,7 @@ export type PickerMode = Exclude; export type PanelRefProps = { onKeyDown?: (e: KeyboardEvent) => boolean; - onBlur?: (e: FocusEvent)=> void; + onBlur?: (e: FocusEvent) => void; onClose?: () => void; }; @@ -74,7 +75,7 @@ export type PanelSharedProps = { // * Thus, move ref into operationRef. // * This is little hack which should refactor after typescript support. // */ - // operationRef: React.MutableRefObject; + operationRef: Ref; onSelect: OnSelect; onViewDateChange: (value: DateType) => void; diff --git a/components/vc-picker/panels/DatePanel/DateBody.tsx b/components/vc-picker/panels/DatePanel/DateBody.tsx index 800d2ea06..24fa0f437 100644 --- a/components/vc-picker/panels/DatePanel/DateBody.tsx +++ b/components/vc-picker/panels/DatePanel/DateBody.tsx @@ -1,4 +1,3 @@ - import type { GenerateConfig } from '../../generate'; import { WEEK_DAY_COUNT, @@ -12,6 +11,7 @@ import useCellClassName from '../../hooks/useCellClassName'; import PanelBody from '../PanelBody'; import { VueNode } from '../../../_util/type'; import { useInjectRange } from '../../RangeContext'; +import useMergeProps from '../../hooks/useMergeProps'; export type DateRender = (currentDate: DateType, today: DateType) => VueNode; @@ -34,19 +34,12 @@ export type DateBodyProps = { onSelect: (value: DateType) => void; } & DateBodyPassProps; -function DateBody(props: DateBodyProps) { - const { - prefixCls, - generateConfig, - prefixColumn, - locale, - rowCount, - viewDate, - value, - dateRender, - } = props; +function DateBody(_props: DateBodyProps) { + const props = useMergeProps(_props); + const { prefixCls, generateConfig, prefixColumn, locale, rowCount, viewDate, value, dateRender } = + props; - const { rangedValue, hoverRangedValue } =useInjectRange() + const { rangedValue, hoverRangedValue } = useInjectRange(); const baseDate = getWeekStartDate(locale.locale, generateConfig, viewDate); const cellPrefixCls = `${prefixCls}-cell`; @@ -74,8 +67,8 @@ function DateBody(props: DateBodyProps) { today, value, generateConfig, - rangedValue: prefixColumn ? null : rangedValue, - hoverRangedValue: prefixColumn ? null : hoverRangedValue, + rangedValue: prefixColumn ? null : rangedValue.value, + hoverRangedValue: prefixColumn ? null : hoverRangedValue.value, isSameCell: (current, target) => isSameDate(generateConfig, current, target), isInView: date => isSameMonth(generateConfig, date, viewDate), offsetCell: (date, offset) => generateConfig.addDate(date, offset), @@ -105,7 +98,20 @@ function DateBody(props: DateBodyProps) { ); } -DateBody.displayName = 'DateBody' +DateBody.displayName = 'DateBody'; DateBody.inheritAttrs = false; - +DateBody.props = [ + 'prefixCls', + 'generateConfig', + 'value?', + 'viewDate', + 'locale', + 'rowCount', + 'onSelect', + 'dateRender?', + 'disabledDate?', + // Used for week panel + 'prefixColumn?', + 'rowClassName?', +]; export default DateBody; diff --git a/components/vc-picker/panels/DatePanel/DateHeader.tsx b/components/vc-picker/panels/DatePanel/DateHeader.tsx index e74b5581f..909d50b67 100644 --- a/components/vc-picker/panels/DatePanel/DateHeader.tsx +++ b/components/vc-picker/panels/DatePanel/DateHeader.tsx @@ -1,10 +1,10 @@ - import Header from '../Header'; import type { Locale } from '../../interface'; import type { GenerateConfig } from '../../generate'; import { useInjectPanel } from '../../PanelContext'; import { formatValue } from '../../utils/dateUtil'; import { VueNode } from '../../../_util/type'; +import useMergeProps from '../../hooks/useMergeProps'; export type DateHeaderProps = { prefixCls: string; @@ -21,7 +21,8 @@ export type DateHeaderProps = { onMonthClick: () => void; }; -function DateHeader(props: DateHeaderProps) { +function DateHeader(_props: DateHeaderProps) { + const props = useMergeProps(_props); const { prefixCls, generateConfig, @@ -35,8 +36,8 @@ function DateHeader(props: DateHeaderProps) { onMonthClick, } = props; - const { hideHeader } = useInjectPanel() - if (hideHeader) { + const { hideHeader } = useInjectPanel(); + if (hideHeader.value) { return null; } @@ -100,6 +101,6 @@ function DateHeader(props: DateHeaderProps) { ); } -DateHeader.displayName = 'DateHeader' +DateHeader.displayName = 'DateHeader'; DateHeader.inheritAttrs = false; export default DateHeader; diff --git a/components/vc-picker/panels/DatePanel/index.tsx b/components/vc-picker/panels/DatePanel/index.tsx index 819dd5ae3..05c0f3b9b 100644 --- a/components/vc-picker/panels/DatePanel/index.tsx +++ b/components/vc-picker/panels/DatePanel/index.tsx @@ -1,4 +1,3 @@ - import type { DateBodyPassProps, DateRender } from './DateBody'; import DateBody from './DateBody'; import DateHeader from './DateHeader'; @@ -8,6 +7,7 @@ import type { KeyboardConfig } from '../../utils/uiUtil'; import { createKeyDownHandler } from '../../utils/uiUtil'; import classNames from '../../../_util/classNames'; import { ref } from '@vue/reactivity'; +import useMergeProps from '../../hooks/useMergeProps'; const DATE_ROW_COUNT = 6; @@ -18,14 +18,17 @@ export type DatePanelProps = { // Used for week panel panelName?: string; keyboardConfig?: KeyboardConfig; -} & PanelSharedProps & DateBodyPassProps; +} & PanelSharedProps & + DateBodyPassProps; -function DatePanel(props: DatePanelProps) { +function DatePanel(_props: DatePanelProps) { + const props = useMergeProps(_props); const { prefixCls, panelName = 'date', keyboardConfig, active, + operationRef, generateConfig, value, viewDate, @@ -34,10 +37,9 @@ function DatePanel(props: DatePanelProps) { onSelect, } = props; const panelPrefixCls = `${prefixCls}-${panelName}-panel`; - const operationRef = ref() // ======================= Keyboard ======================= operationRef.value = { - onKeyDown: event => + onKeyDown: (event: KeyboardEvent) => createKeyDownHandler(event, { onLeftRight: diff => { onSelect(generateConfig.addDate(value || viewDate, diff), 'key'); @@ -110,7 +112,7 @@ function DatePanel(props: DatePanelProps) { ); } -DatePanel.displayName ='DatePanel' +DatePanel.displayName = 'DatePanel'; DatePanel.inheritAttrs = false; export default DatePanel; diff --git a/components/vc-picker/panels/DatetimePanel/index.tsx b/components/vc-picker/panels/DatetimePanel/index.tsx index 98d3a9e7e..1f8048c87 100644 --- a/components/vc-picker/panels/DatetimePanel/index.tsx +++ b/components/vc-picker/panels/DatetimePanel/index.tsx @@ -1,4 +1,3 @@ - import type { DatePanelProps } from '../DatePanel'; import DatePanel from '../DatePanel'; import type { SharedTimeProps } from '../TimePanel'; @@ -9,20 +8,19 @@ import type { PanelRefProps, DisabledTime } from '../../interface'; import KeyCode from '../../../_util/KeyCode'; import classNames from '../../../_util/classNames'; import { ref } from '@vue/reactivity'; +import useMergeProps from '../../hooks/useMergeProps'; export type DatetimePanelProps = { disabledTime?: DisabledTime; showTime?: boolean | SharedTimeProps; defaultValue?: DateType; -} & Omit< - DatePanelProps, - 'disabledHours' | 'disabledMinutes' | 'disabledSeconds' - >; +} & Omit, 'disabledHours' | 'disabledMinutes' | 'disabledSeconds'>; const ACTIVE_PANEL = tuple('date', 'time'); type ActivePanelType = typeof ACTIVE_PANEL[number]; -function DatetimePanel(props: DatetimePanelProps) { +function DatetimePanel(_props: DatetimePanelProps) { + const props = useMergeProps(_props); const { prefixCls, operationRef, @@ -34,9 +32,7 @@ function DatetimePanel(props: DatetimePanelProps) { onSelect, } = props; const panelPrefixCls = `${prefixCls}-datetime-panel`; - const activePanel = ref( - null, - ); + const activePanel = ref(null); const dateOperationRef = ref({}); const timeOperationRef = ref({}); @@ -57,12 +53,12 @@ function DatetimePanel(props: DatetimePanelProps) { activePanel.value = null; }; - operationRef.current = { - onKeyDown: event => { + operationRef.value = { + onKeyDown: (event: KeyboardEvent) => { // Switch active panel if (event.which === KeyCode.TAB) { const nextActivePanel = getNextActive(event.shiftKey ? -1 : 1); - activePanel.value = nextActivePanel + activePanel.value = nextActivePanel; if (nextActivePanel) { event.preventDefault(); @@ -73,8 +69,7 @@ function DatetimePanel(props: DatetimePanelProps) { // Operate on current active panel if (activePanel.value) { - const ref = - activePanel.value === 'date' ? dateOperationRef : timeOperationRef; + const ref = activePanel.value === 'date' ? dateOperationRef : timeOperationRef; if (ref.value && ref.value.onKeyDown) { ref.value.onKeyDown(event); @@ -84,12 +79,8 @@ function DatetimePanel(props: DatetimePanelProps) { } // Switch first active panel if operate without panel - if ( - [KeyCode.LEFT, KeyCode.RIGHT, KeyCode.UP, KeyCode.DOWN].includes( - event.which, - ) - ) { - activePanel.value = 'date' + if ([KeyCode.LEFT, KeyCode.RIGHT, KeyCode.UP, KeyCode.DOWN].includes(event.which)) { + activePanel.value = 'date'; return true; } @@ -118,18 +109,9 @@ function DatetimePanel(props: DatetimePanelProps) { generateConfig.getSecond(timeProps.defaultValue), ); } else if (source === 'time' && !value && defaultValue) { - selectedDate = generateConfig.setYear( - selectedDate, - generateConfig.getYear(defaultValue), - ); - selectedDate = generateConfig.setMonth( - selectedDate, - generateConfig.getMonth(defaultValue), - ); - selectedDate = generateConfig.setDate( - selectedDate, - generateConfig.getDate(defaultValue), - ); + selectedDate = generateConfig.setYear(selectedDate, generateConfig.getYear(defaultValue)); + selectedDate = generateConfig.setMonth(selectedDate, generateConfig.getMonth(defaultValue)); + selectedDate = generateConfig.setDate(selectedDate, generateConfig.getDate(defaultValue)); } if (onSelect) { @@ -155,9 +137,7 @@ function DatetimePanel(props: DatetimePanelProps) { setTime( generateConfig, date, - showTime && typeof showTime === 'object' - ? showTime.defaultValue - : null, + showTime && typeof showTime === 'object' ? showTime.defaultValue : null, ), 'date', ); @@ -179,8 +159,7 @@ function DatetimePanel(props: DatetimePanelProps) { ); } - -DatetimePanel.displayName ='DatetimePanel' +DatetimePanel.displayName = 'DatetimePanel'; DatetimePanel.inheritAttrs = false; export default DatetimePanel; diff --git a/components/vc-picker/panels/DecadePanel/DecadeBody.tsx b/components/vc-picker/panels/DecadePanel/DecadeBody.tsx index 2b216abf1..3da59f5cf 100644 --- a/components/vc-picker/panels/DecadePanel/DecadeBody.tsx +++ b/components/vc-picker/panels/DecadePanel/DecadeBody.tsx @@ -1,7 +1,7 @@ - import type { GenerateConfig } from '../../generate'; import { DECADE_DISTANCE_COUNT, DECADE_UNIT_DIFF } from '.'; import PanelBody from '../PanelBody'; +import useMergeProps from '../../hooks/useMergeProps'; export const DECADE_COL_COUNT = 3; const DECADE_ROW_COUNT = 4; @@ -14,7 +14,8 @@ export type YearBodyProps = { onSelect: (value: DateType) => void; }; -function DecadeBody(props: YearBodyProps) { +function DecadeBody(_props: YearBodyProps) { + const props = useMergeProps(_props); const DECADE_UNIT_DIFF_DES = DECADE_UNIT_DIFF - 1; const { prefixCls, viewDate, generateConfig } = props; @@ -61,8 +62,7 @@ function DecadeBody(props: YearBodyProps) { ); } - -DecadeBody.displayName ='DecadeBody' +DecadeBody.displayName = 'DecadeBody'; DecadeBody.inheritAttrs = false; export default DecadeBody; diff --git a/components/vc-picker/panels/DecadePanel/DecadeHeader.tsx b/components/vc-picker/panels/DecadePanel/DecadeHeader.tsx index 0f23113e6..1b56f722e 100644 --- a/components/vc-picker/panels/DecadePanel/DecadeHeader.tsx +++ b/components/vc-picker/panels/DecadePanel/DecadeHeader.tsx @@ -1,8 +1,8 @@ - import Header from '../Header'; import type { GenerateConfig } from '../../generate'; import { DECADE_DISTANCE_COUNT } from '.'; import { useInjectPanel } from '../../PanelContext'; +import useMergeProps from '../../hooks/useMergeProps'; export type YearHeaderProps = { prefixCls: string; @@ -13,15 +13,10 @@ export type YearHeaderProps = { onNextDecades: () => void; }; -function DecadeHeader(props: YearHeaderProps) { - const { - prefixCls, - generateConfig, - viewDate, - onPrevDecades, - onNextDecades, - } = props; - const { hideHeader } =useInjectPanel() +function DecadeHeader(_props: YearHeaderProps) { + const props = useMergeProps(_props); + const { prefixCls, generateConfig, viewDate, onPrevDecades, onNextDecades } = props; + const { hideHeader } = useInjectPanel(); if (hideHeader) { return null; } @@ -29,8 +24,7 @@ function DecadeHeader(props: YearHeaderProps) { const headerPrefixCls = `${prefixCls}-header`; const yearNumber = generateConfig.getYear(viewDate); - const startYear = - Math.floor(yearNumber / DECADE_DISTANCE_COUNT) * DECADE_DISTANCE_COUNT; + const startYear = Math.floor(yearNumber / DECADE_DISTANCE_COUNT) * DECADE_DISTANCE_COUNT; const endYear = startYear + DECADE_DISTANCE_COUNT - 1; return ( @@ -45,7 +39,7 @@ function DecadeHeader(props: YearHeaderProps) { ); } -DecadeHeader.displayName ='DecadeHeader' +DecadeHeader.displayName = 'DecadeHeader'; DecadeHeader.inheritAttrs = false; export default DecadeHeader; diff --git a/components/vc-picker/panels/DecadePanel/index.tsx b/components/vc-picker/panels/DecadePanel/index.tsx index fa4f0f3e4..f7e465417 100644 --- a/components/vc-picker/panels/DecadePanel/index.tsx +++ b/components/vc-picker/panels/DecadePanel/index.tsx @@ -1,15 +1,16 @@ - import DecadeHeader from './DecadeHeader'; import DecadeBody, { DECADE_COL_COUNT } from './DecadeBody'; import type { PanelSharedProps } from '../../interface'; import { createKeyDownHandler } from '../../utils/uiUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type DecadePanelProps = PanelSharedProps; export const DECADE_UNIT_DIFF = 10; export const DECADE_DISTANCE_COUNT = DECADE_UNIT_DIFF * 10; -function DecadePanel(props: DecadePanelProps) { +function DecadePanel(_props: DecadePanelProps) { + const props = useMergeProps(_props); const { prefixCls, onViewDateChange, @@ -23,27 +24,18 @@ function DecadePanel(props: DecadePanelProps) { const panelPrefixCls = `${prefixCls}-decade-panel`; // ======================= Keyboard ======================= - operationRef.current = { - onKeyDown: event => + operationRef.value = { + onKeyDown: (event: KeyboardEvent) => createKeyDownHandler(event, { onLeftRight: diff => { - onSelect( - generateConfig.addYear(viewDate, diff * DECADE_UNIT_DIFF), - 'key', - ); + onSelect(generateConfig.addYear(viewDate, diff * DECADE_UNIT_DIFF), 'key'); }, onCtrlLeftRight: diff => { - onSelect( - generateConfig.addYear(viewDate, diff * DECADE_DISTANCE_COUNT), - 'key', - ); + onSelect(generateConfig.addYear(viewDate, diff * DECADE_DISTANCE_COUNT), 'key'); }, onUpDown: diff => { onSelect( - generateConfig.addYear( - viewDate, - diff * DECADE_UNIT_DIFF * DECADE_COL_COUNT, - ), + generateConfig.addYear(viewDate, diff * DECADE_UNIT_DIFF * DECADE_COL_COUNT), 'key', ); }, @@ -55,10 +47,7 @@ function DecadePanel(props: DecadePanelProps) { // ==================== View Operation ==================== const onDecadesChange = (diff: number) => { - const newDate = generateConfig.addYear( - viewDate, - diff * DECADE_DISTANCE_COUNT, - ); + const newDate = generateConfig.addYear(viewDate, diff * DECADE_DISTANCE_COUNT); onViewDateChange(newDate); onPanelChange(null, newDate); }; @@ -80,17 +69,12 @@ function DecadePanel(props: DecadePanelProps) { onDecadesChange(1); }} /> - +
); } - -DecadePanel.displayName ='DecadePanel' +DecadePanel.displayName = 'DecadePanel'; DecadePanel.inheritAttrs = false; export default DecadePanel; diff --git a/components/vc-picker/panels/Header.tsx b/components/vc-picker/panels/Header.tsx index 6b8102738..9b3b9fa6e 100644 --- a/components/vc-picker/panels/Header.tsx +++ b/components/vc-picker/panels/Header.tsx @@ -1,5 +1,6 @@ -import { CSSProperties } from '@vue/runtime-dom'; +import { CSSProperties } from 'vue'; import { VueNode } from '../../_util/type'; +import useMergeProps from '../hooks/useMergeProps'; import { useInjectPanel } from '../PanelContext'; const HIDDEN_STYLE: CSSProperties = { @@ -27,8 +28,9 @@ export type HeaderProps = { children?: VueNode; }; -function Header( - { +function Header(_props: HeaderProps, { slots }) { + const props = useMergeProps(_props); + const { prefixCls, prevIcon = '\u2039', nextIcon = '\u203A', @@ -38,9 +40,7 @@ function Header( onSuperNext, onPrev, onNext, - }: HeaderProps, - { slots }, -) { + } = props; const { hideNextBtn, hidePrevBtn } = useInjectPanel(); return ( @@ -51,7 +51,7 @@ function Header( onClick={onSuperPrev} tabindex={-1} class={`${prefixCls}-super-prev-btn`} - style={hidePrevBtn ? HIDDEN_STYLE : {}} + style={hidePrevBtn.value ? HIDDEN_STYLE : {}} > {superPrevIcon} @@ -62,7 +62,7 @@ function Header( onClick={onPrev} tabindex={-1} class={`${prefixCls}-prev-btn`} - style={hidePrevBtn ? HIDDEN_STYLE : {}} + style={hidePrevBtn.value ? HIDDEN_STYLE : {}} > {prevIcon} @@ -74,7 +74,7 @@ function Header( onClick={onNext} tabindex={-1} class={`${prefixCls}-next-btn`} - style={hideNextBtn ? HIDDEN_STYLE : {}} + style={hideNextBtn.value ? HIDDEN_STYLE : {}} > {nextIcon} @@ -85,7 +85,7 @@ function Header( onClick={onSuperNext} tabindex={-1} class={`${prefixCls}-super-next-btn`} - style={hideNextBtn ? HIDDEN_STYLE : {}} + style={hideNextBtn.value ? HIDDEN_STYLE : {}} > {superNextIcon} diff --git a/components/vc-picker/panels/MonthPanel/MonthBody.tsx b/components/vc-picker/panels/MonthPanel/MonthBody.tsx index c553d6c93..599f0de2d 100644 --- a/components/vc-picker/panels/MonthPanel/MonthBody.tsx +++ b/components/vc-picker/panels/MonthPanel/MonthBody.tsx @@ -1,4 +1,3 @@ - import type { GenerateConfig } from '../../generate'; import type { Locale } from '../../interface'; import { formatValue, isSameMonth } from '../../utils/dateUtil'; @@ -6,6 +5,7 @@ import { useInjectRange } from '../../RangeContext'; import useCellClassName from '../../hooks/useCellClassName'; import PanelBody from '../PanelBody'; import { VueNode } from '../../../_util/type'; +import useMergeProps from '../../hooks/useMergeProps'; export const MONTH_COL_COUNT = 3; const MONTH_ROW_COUNT = 4; @@ -23,10 +23,11 @@ export type MonthBodyProps = { onSelect: (value: DateType) => void; }; -function MonthBody(props: MonthBodyProps) { +function MonthBody(_props: MonthBodyProps) { + const props = useMergeProps(_props); const { prefixCls, locale, value, viewDate, generateConfig, monthCellRender } = props; - const { rangedValue, hoverRangedValue } = useInjectRange() + const { rangedValue, hoverRangedValue } = useInjectRange(); const cellPrefixCls = `${prefixCls}-cell`; @@ -34,8 +35,8 @@ function MonthBody(props: MonthBodyProps) { cellPrefixCls, value, generateConfig, - rangedValue, - hoverRangedValue, + rangedValue: rangedValue.value, + hoverRangedValue: hoverRangedValue.value, isSameCell: (current, target) => isSameMonth(generateConfig, current, target), isInView: () => true, offsetCell: (date, offset) => generateConfig.addMonth(date, offset), @@ -82,8 +83,7 @@ function MonthBody(props: MonthBodyProps) { ); } - -MonthBody.displayName ='MonthBody' +MonthBody.displayName = 'MonthBody'; MonthBody.inheritAttrs = false; export default MonthBody; diff --git a/components/vc-picker/panels/MonthPanel/MonthHeader.tsx b/components/vc-picker/panels/MonthPanel/MonthHeader.tsx index e4ad37239..cc010ffe5 100644 --- a/components/vc-picker/panels/MonthPanel/MonthHeader.tsx +++ b/components/vc-picker/panels/MonthPanel/MonthHeader.tsx @@ -1,9 +1,9 @@ - import Header from '../Header'; import type { Locale } from '../../interface'; import type { GenerateConfig } from '../../generate'; import { useInjectPanel } from '../../PanelContext'; import { formatValue } from '../../utils/dateUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type MonthHeaderProps = { prefixCls: string; @@ -16,18 +16,12 @@ export type MonthHeaderProps = { onYearClick: () => void; }; -function MonthHeader(props: MonthHeaderProps) { - const { - prefixCls, - generateConfig, - locale, - viewDate, - onNextYear, - onPrevYear, - onYearClick, - } = props; - const { hideHeader } = useInjectPanel() - if (hideHeader) { +function MonthHeader(_props: MonthHeaderProps) { + const props = useMergeProps(_props); + const { prefixCls, generateConfig, locale, viewDate, onNextYear, onPrevYear, onYearClick } = + props; + const { hideHeader } = useInjectPanel(); + if (hideHeader.value) { return null; } @@ -51,8 +45,7 @@ function MonthHeader(props: MonthHeaderProps) { ); } - -MonthHeader.displayName ='MonthHeader' +MonthHeader.displayName = 'MonthHeader'; MonthHeader.inheritAttrs = false; export default MonthHeader; diff --git a/components/vc-picker/panels/MonthPanel/index.tsx b/components/vc-picker/panels/MonthPanel/index.tsx index dd57ef9c7..1cf7b427e 100644 --- a/components/vc-picker/panels/MonthPanel/index.tsx +++ b/components/vc-picker/panels/MonthPanel/index.tsx @@ -1,15 +1,16 @@ - import MonthHeader from './MonthHeader'; import type { MonthCellRender } from './MonthBody'; import MonthBody, { MONTH_COL_COUNT } from './MonthBody'; import type { PanelSharedProps } from '../../interface'; import { createKeyDownHandler } from '../../utils/uiUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type MonthPanelProps = { monthCellContentRender?: MonthCellRender; } & PanelSharedProps; -function MonthPanel(props: MonthPanelProps) { +function MonthPanel(_props: MonthPanelProps) { + const props = useMergeProps(_props); const { prefixCls, operationRef, @@ -24,8 +25,8 @@ function MonthPanel(props: MonthPanelProps) { const panelPrefixCls = `${prefixCls}-month-panel`; // ======================= Keyboard ======================= - operationRef.current = { - onKeyDown: event => + operationRef.value = { + onKeyDown: (event: KeyboardEvent) => createKeyDownHandler(event, { onLeftRight: diff => { onSelect(generateConfig.addMonth(value || viewDate, diff), 'key'); @@ -34,10 +35,7 @@ function MonthPanel(props: MonthPanelProps) { onSelect(generateConfig.addYear(value || viewDate, diff), 'key'); }, onUpDown: diff => { - onSelect( - generateConfig.addMonth(value || viewDate, diff * MONTH_COL_COUNT), - 'key', - ); + onSelect(generateConfig.addMonth(value || viewDate, diff * MONTH_COL_COUNT), 'key'); }, onEnter: () => { onPanelChange('date', value || viewDate); @@ -79,8 +77,7 @@ function MonthPanel(props: MonthPanelProps) { ); } - -MonthPanel.displayName ='MonthPanel' +MonthPanel.displayName = 'MonthPanel'; MonthPanel.inheritAttrs = false; export default MonthPanel; diff --git a/components/vc-picker/panels/PanelBody.tsx b/components/vc-picker/panels/PanelBody.tsx index e4a95d669..ee0ba27b8 100644 --- a/components/vc-picker/panels/PanelBody.tsx +++ b/components/vc-picker/panels/PanelBody.tsx @@ -1,4 +1,3 @@ - import { useInjectPanel } from '../PanelContext'; import type { GenerateConfig } from '../generate'; import { getLastDay } from '../utils/timeUtil'; @@ -6,6 +5,7 @@ import type { PanelMode } from '../interface'; import { getCellDateDisabled } from '../utils/dateUtil'; import { VueNode } from '../../_util/type'; import classNames from '../../_util/classNames'; +import useMergeProps from '../hooks/useMergeProps'; export type PanelBodyProps = { prefixCls: string; @@ -30,25 +30,26 @@ export type PanelBodyProps = { rowClassName?: (date: DateType) => string; }; -function PanelBody({ - prefixCls, - disabledDate, - onSelect, - picker, - rowNum, - colNum, - prefixColumn, - rowClassName, - baseDate, - getCellClassName, - getCellText, - getCellNode, - getCellDate, - generateConfig, - titleCell, - headerCells, -}: PanelBodyProps) { - const { onDateMouseEnter, onDateMouseLeave, mode } = useInjectPanel() +function PanelBody(_props: PanelBodyProps) { + const { + prefixCls, + disabledDate, + onSelect, + picker, + rowNum, + colNum, + prefixColumn, + rowClassName, + baseDate, + getCellClassName, + getCellText, + getCellNode, + getCellDate, + generateConfig, + titleCell, + headerCells, + } = useMergeProps(_props); + const { onDateMouseEnter, onDateMouseLeave, mode } = useInjectPanel(); const cellPrefixCls = `${prefixCls}-cell`; @@ -64,7 +65,7 @@ function PanelBody({ const currentDate = getCellDate(baseDate, offset); const disabled = getCellDateDisabled({ cellDate: currentDate, - mode, + mode: mode.value, disabledDate, generateConfig, }); diff --git a/components/vc-picker/panels/QuarterPanel/QuarterBody.tsx b/components/vc-picker/panels/QuarterPanel/QuarterBody.tsx index 1bc6d23be..a1f825108 100644 --- a/components/vc-picker/panels/QuarterPanel/QuarterBody.tsx +++ b/components/vc-picker/panels/QuarterPanel/QuarterBody.tsx @@ -1,10 +1,10 @@ - import type { GenerateConfig } from '../../generate'; import type { Locale } from '../../interface'; import { formatValue, isSameQuarter } from '../../utils/dateUtil'; import RangeContext, { useInjectRange } from '../../RangeContext'; import useCellClassName from '../../hooks/useCellClassName'; import PanelBody from '../PanelBody'; +import useMergeProps from '../../hooks/useMergeProps'; export const QUARTER_COL_COUNT = 4; const QUARTER_ROW_COUNT = 1; @@ -19,10 +19,11 @@ export type QuarterBodyProps = { onSelect: (value: DateType) => void; }; -function QuarterBody(props: QuarterBodyProps) { +function QuarterBody(_props: QuarterBodyProps) { + const props = useMergeProps(_props); const { prefixCls, locale, value, viewDate, generateConfig } = props; - const { rangedValue, hoverRangedValue } = useInjectRange() + const { rangedValue, hoverRangedValue } = useInjectRange(); const cellPrefixCls = `${prefixCls}-cell`; @@ -30,8 +31,8 @@ function QuarterBody(props: QuarterBodyProps) { cellPrefixCls, value, generateConfig, - rangedValue, - hoverRangedValue, + rangedValue: rangedValue.value, + hoverRangedValue: hoverRangedValue.value, isSameCell: (current, target) => isSameQuarter(generateConfig, current, target), isInView: () => true, offsetCell: (date, offset) => generateConfig.addMonth(date, offset * 3), @@ -65,7 +66,6 @@ function QuarterBody(props: QuarterBodyProps) { ); } - -QuarterBody.displayName ='QuarterBody' +QuarterBody.displayName = 'QuarterBody'; QuarterBody.inheritAttrs = false; export default QuarterBody; diff --git a/components/vc-picker/panels/QuarterPanel/QuarterHeader.tsx b/components/vc-picker/panels/QuarterPanel/QuarterHeader.tsx index d2727a0d5..223f5d1b0 100644 --- a/components/vc-picker/panels/QuarterPanel/QuarterHeader.tsx +++ b/components/vc-picker/panels/QuarterPanel/QuarterHeader.tsx @@ -1,9 +1,9 @@ - import Header from '../Header'; import type { Locale } from '../../interface'; import type { GenerateConfig } from '../../generate'; import { useInjectPanel } from '../../PanelContext'; import { formatValue } from '../../utils/dateUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type QuarterHeaderProps = { prefixCls: string; @@ -16,18 +16,12 @@ export type QuarterHeaderProps = { onYearClick: () => void; }; -function QuarterHeader(props: QuarterHeaderProps) { - const { - prefixCls, - generateConfig, - locale, - viewDate, - onNextYear, - onPrevYear, - onYearClick, - } = props; - const { hideHeader } =useInjectPanel() - if (hideHeader) { +function QuarterHeader(_props: QuarterHeaderProps) { + const props = useMergeProps(_props); + const { prefixCls, generateConfig, locale, viewDate, onNextYear, onPrevYear, onYearClick } = + props; + const { hideHeader } = useInjectPanel(); + if (hideHeader.value) { return null; } @@ -50,8 +44,7 @@ function QuarterHeader(props: QuarterHeaderProps) { ); } - -QuarterHeader.displayName ='QuarterHeader' +QuarterHeader.displayName = 'QuarterHeader'; QuarterHeader.inheritAttrs = false; export default QuarterHeader; diff --git a/components/vc-picker/panels/QuarterPanel/index.tsx b/components/vc-picker/panels/QuarterPanel/index.tsx index 320b1b968..49825ca75 100644 --- a/components/vc-picker/panels/QuarterPanel/index.tsx +++ b/components/vc-picker/panels/QuarterPanel/index.tsx @@ -1,12 +1,13 @@ - import QuarterHeader from './QuarterHeader'; import QuarterBody from './QuarterBody'; import type { PanelSharedProps } from '../../interface'; import { createKeyDownHandler } from '../../utils/uiUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type QuarterPanelProps = {} & PanelSharedProps; -function QuarterPanel(props: QuarterPanelProps) { +function QuarterPanel(_props: QuarterPanelProps) { + const props = useMergeProps(_props); const { prefixCls, operationRef, @@ -21,8 +22,8 @@ function QuarterPanel(props: QuarterPanelProps) { const panelPrefixCls = `${prefixCls}-quarter-panel`; // ======================= Keyboard ======================= - operationRef.current = { - onKeyDown: event => + operationRef.value = { + onKeyDown: (event: KeyboardEvent) => createKeyDownHandler(event, { onLeftRight: diff => { onSelect(generateConfig.addMonth(value || viewDate, diff * 3), 'key'); @@ -69,8 +70,7 @@ function QuarterPanel(props: QuarterPanelProps) { ); } - -QuarterPanel.displayName ='QuarterPanel' +QuarterPanel.displayName = 'QuarterPanel'; QuarterPanel.inheritAttrs = false; export default QuarterPanel; diff --git a/components/vc-picker/panels/TimePanel/TimeBody.tsx b/components/vc-picker/panels/TimePanel/TimeBody.tsx index c220a5370..bab96faf4 100644 --- a/components/vc-picker/panels/TimePanel/TimeBody.tsx +++ b/components/vc-picker/panels/TimePanel/TimeBody.tsx @@ -1,4 +1,3 @@ - import type { GenerateConfig } from '../../generate'; import type { Locale, OnSelect } from '../../interface'; import type { Unit } from './TimeUnitColumn'; @@ -8,17 +7,8 @@ import type { SharedTimeProps } from '.'; import { setTime as utilSetTime } from '../../utils/timeUtil'; import { cloneElement } from '../../../_util/vnode'; import { VueNode } from '../../../_util/type'; -import { ref, Ref } from '@vue/reactivity'; -import { computed, defineComponent, watchEffect } from '@vue/runtime-core'; - -function shouldUnitsUpdate(prevUnits: Unit[], nextUnits: Unit[]) { - if (prevUnits.length !== nextUnits.length) return true; - // if any unit's disabled status is different, the units should be re-evaluted - for (let i = 0; i < prevUnits.length; i += 1) { - if (prevUnits[i].disabled !== nextUnits[i].disabled) return true; - } - return false; -} +import type { Ref } from 'vue'; +import { computed, defineComponent } from 'vue'; function generateUnits( start: number, @@ -51,7 +41,6 @@ export type TimeBodyProps = { operationRef: Ref; } & SharedTimeProps; - const TimeBody = defineComponent({ name: 'TimeBody', inheritAttrs: false, @@ -75,26 +64,26 @@ const TimeBody = defineComponent({ 'onSelect', ], setup(props) { - const originHour = computed(() => props.value ? props.generateConfig.getHour(props.value) : -1); - const isPM = computed(()=> { + const originHour = computed(() => + props.value ? props.generateConfig.getHour(props.value) : -1, + ); + const isPM = computed(() => { if (props.use12Hours) { return originHour.value >= 12; // -1 means should display AM } else { - return false - } - }) - let hour = computed(()=> { - // Should additional logic to handle 12 hours - if (props.use12Hours) { - return originHour.value % 12 - } else { - return originHour.value + return false; } }); - const minute = computed(()=> props.value ? props.generateConfig.getMinute(props.value) : -1); - const second = computed(()=> props.value ? props.generateConfig.getSecond(props.value) : -1); - - + let hour = computed(() => { + // Should additional logic to handle 12 hours + if (props.use12Hours) { + return originHour.value % 12; + } else { + return originHour.value; + } + }); + const minute = computed(() => (props.value ? props.generateConfig.getMinute(props.value) : -1)); + const second = computed(() => (props.value ? props.generateConfig.getSecond(props.value) : -1)); const setTime = ( isNewPM: boolean | undefined, @@ -120,7 +109,9 @@ const TimeBody = defineComponent({ }; // ========================= Unit ========================= - const rawHours = computed(()=> generateUnits(0, 23, props.hourStep ?? 1, props.disabledHours && props.disabledHours())); + const rawHours = computed(() => + generateUnits(0, 23, props.hourStep ?? 1, props.disabledHours && props.disabledHours()), + ); // const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate); @@ -155,16 +146,25 @@ const TimeBody = defineComponent({ }); }); - const minutes = computed(()=> generateUnits(0, 59, props.minuteStep ?? 1, props.disabledMinutes && props.disabledMinutes(originHour.value))); + const minutes = computed(() => + generateUnits( + 0, + 59, + props.minuteStep ?? 1, + props.disabledMinutes && props.disabledMinutes(originHour.value), + ), + ); - const seconds = computed(()=> generateUnits( - 0, - 59, - props.secondStep ?? 1, - props.disabledSeconds && props.disabledSeconds(originHour.value, minute), - )); + const seconds = computed(() => + generateUnits( + 0, + 59, + props.secondStep ?? 1, + props.disabledSeconds && props.disabledSeconds(originHour.value, minute), + ), + ); - return ()=> { + return () => { const { prefixCls, operationRef, @@ -188,7 +188,7 @@ const TimeBody = defineComponent({ // ====================== Operations ====================== operationRef.value = { - onUpDown: diff => { + onUpDown: (diff: number) => { const column = columns[activeColumnIndex]; if (column) { const valueIndex = column.units.findIndex(unit => unit.value === column.value); @@ -237,14 +237,26 @@ const TimeBody = defineComponent({ }); // Minute - addColumnNode(showMinute, , minute.value, minutes.value, num => { - onSelect(setTime(isPM.value, hour.value, num, second.value), 'mouse'); - }); + addColumnNode( + showMinute, + , + minute.value, + minutes.value, + num => { + onSelect(setTime(isPM.value, hour.value, num, second.value), 'mouse'); + }, + ); // Second - addColumnNode(showSecond, , second.value, seconds.value, num => { - onSelect(setTime(isPM.value, hour.value, minute.value, num), 'mouse'); - }); + addColumnNode( + showSecond, + , + second.value, + seconds.value, + num => { + onSelect(setTime(isPM.value, hour.value, minute.value, num), 'mouse'); + }, + ); // 12 Hours let PMIndex = -1; @@ -266,9 +278,8 @@ const TimeBody = defineComponent({ ); return
{columns.map(({ node }) => node)}
; - } - } -}) - + }; + }, +}); export default TimeBody; diff --git a/components/vc-picker/panels/TimePanel/TimeHeader.tsx b/components/vc-picker/panels/TimePanel/TimeHeader.tsx index 1e9fe954f..977ef408a 100644 --- a/components/vc-picker/panels/TimePanel/TimeHeader.tsx +++ b/components/vc-picker/panels/TimePanel/TimeHeader.tsx @@ -1,9 +1,9 @@ - import Header from '../Header'; import type { Locale } from '../../interface'; import type { GenerateConfig } from '../../generate'; import { useInjectPanel } from '../../PanelContext'; import { formatValue } from '../../utils/dateUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type TimeHeaderProps = { prefixCls: string; @@ -13,9 +13,10 @@ export type TimeHeaderProps = { format: string; }; -function TimeHeader(props: TimeHeaderProps) { - const { hideHeader } = useInjectPanel() - if (hideHeader) { +function TimeHeader(_props: TimeHeaderProps) { + const props = useMergeProps(_props); + const { hideHeader } = useInjectPanel(); + if (hideHeader.value) { return null; } @@ -35,8 +36,7 @@ function TimeHeader(props: TimeHeaderProps) { ); } - -TimeHeader.displayName ='TimeHeader' +TimeHeader.displayName = 'TimeHeader'; TimeHeader.inheritAttrs = false; export default TimeHeader; diff --git a/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx b/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx index 18327abb8..ea7ae7ad2 100644 --- a/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx +++ b/components/vc-picker/panels/TimePanel/TimeUnitColumn.tsx @@ -32,7 +32,7 @@ function TimeUnitColumn(props: TimeUnitColumnProps) { () => props.value, () => { const li = liRefs.value.get(value!); - if (li && open !== false) { + if (li && open.value !== false) { scrollTo(ulRef.value!, li.offsetTop, 120); } }, @@ -43,7 +43,7 @@ function TimeUnitColumn(props: TimeUnitColumnProps) { watch(open, () => { scrollRef.value?.(); - if (open) { + if (open.value) { const li = liRefs.value.get(value!); if (li) { scrollRef.value = waitElementReady(li, () => { diff --git a/components/vc-picker/panels/TimePanel/index.tsx b/components/vc-picker/panels/TimePanel/index.tsx index 92c86c428..915e3f4e9 100644 --- a/components/vc-picker/panels/TimePanel/index.tsx +++ b/components/vc-picker/panels/TimePanel/index.tsx @@ -1,4 +1,3 @@ - import TimeHeader from './TimeHeader'; import type { BodyOperationRef } from './TimeBody'; import TimeBody from './TimeBody'; @@ -6,6 +5,7 @@ import type { PanelSharedProps, DisabledTimes } from '../../interface'; import { createKeyDownHandler } from '../../utils/uiUtil'; import classNames from '../../../_util/classNames'; import { ref } from '@vue/reactivity'; +import useMergeProps from '../../hooks/useMergeProps'; export type SharedTimeProps = { format?: string; @@ -24,12 +24,14 @@ export type SharedTimeProps = { export type TimePanelProps = { format?: string; active?: boolean; -} & PanelSharedProps & SharedTimeProps; +} & PanelSharedProps & + SharedTimeProps; const countBoolean = (boolList: (boolean | undefined)[]) => boolList.filter(bool => bool !== false).length; -function TimePanel(props: TimePanelProps) { +function TimePanel(_props: TimePanelProps) { + const props = useMergeProps(_props); const { generateConfig, format = 'HH:mm:ss', @@ -50,27 +52,27 @@ function TimePanel(props: TimePanelProps) { const activeColumnIndex = ref(-1); const columnsCount = countBoolean([showHour, showMinute, showSecond, use12Hours]); - operationRef.current = { - onKeyDown: event => + operationRef.value = { + onKeyDown: (event: KeyboardEvent) => createKeyDownHandler(event, { onLeftRight: diff => { activeColumnIndex.value = (activeColumnIndex.value + diff + columnsCount) % columnsCount; }, onUpDown: diff => { if (activeColumnIndex.value === -1) { - activeColumnIndex.value = 0 + activeColumnIndex.value = 0; } else if (bodyOperationRef.value) { bodyOperationRef.value.onUpDown(diff); } }, onEnter: () => { onSelect(value || generateConfig.getNow(), 'key'); - activeColumnIndex.value = -1 + activeColumnIndex.value = -1; }, }), onBlur: () => { - activeColumnIndex.value = -1 + activeColumnIndex.value = -1; }, }; @@ -91,8 +93,7 @@ function TimePanel(props: TimePanelProps) { ); } - -TimePanel.displayName ='TimePanel' +TimePanel.displayName = 'TimePanel'; TimePanel.inheritAttrs = false; export default TimePanel; diff --git a/components/vc-picker/panels/WeekPanel/index.tsx b/components/vc-picker/panels/WeekPanel/index.tsx index c48bbb3f2..b0c203f22 100644 --- a/components/vc-picker/panels/WeekPanel/index.tsx +++ b/components/vc-picker/panels/WeekPanel/index.tsx @@ -1,21 +1,19 @@ - import DatePanel from '../DatePanel'; import type { PanelSharedProps } from '../../interface'; import { isSameWeek } from '../../utils/dateUtil'; import classNames from '../../../_util/classNames'; +import useMergeProps from '../../hooks/useMergeProps'; export type WeekPanelProps = PanelSharedProps; -function WeekPanel(props: WeekPanelProps) { +function WeekPanel(_props: WeekPanelProps) { + const props = useMergeProps(_props); const { prefixCls, generateConfig, locale, value } = props; // Render additional column const cellPrefixCls = `${prefixCls}-cell`; const prefixColumn = (date: DateType) => ( - + {generateConfig.locale.getWeek(locale.locale, date)} ); @@ -24,12 +22,7 @@ function WeekPanel(props: WeekPanelProps) { const rowPrefixCls = `${prefixCls}-week-panel-row`; const rowClassName = (date: DateType) => classNames(rowPrefixCls, { - [`${rowPrefixCls}-selected`]: isSameWeek( - generateConfig, - locale.locale, - value, - date, - ), + [`${rowPrefixCls}-selected`]: isSameWeek(generateConfig, locale.locale, value, date), }); return ( @@ -45,7 +38,6 @@ function WeekPanel(props: WeekPanelProps) { ); } - WeekPanel.displayName = 'WeekPanel'; WeekPanel.inheritAttrs = false; diff --git a/components/vc-picker/panels/YearPanel/YearBody.tsx b/components/vc-picker/panels/YearPanel/YearBody.tsx index 56b68d319..18bc46e8e 100644 --- a/components/vc-picker/panels/YearPanel/YearBody.tsx +++ b/components/vc-picker/panels/YearPanel/YearBody.tsx @@ -1,11 +1,11 @@ - import type { GenerateConfig } from '../../generate'; import { YEAR_DECADE_COUNT } from '.'; import type { Locale, NullableDateType } from '../../interface'; import useCellClassName from '../../hooks/useCellClassName'; import { formatValue, isSameYear } from '../../utils/dateUtil'; -import RangeContext, { useInjectRange } from '../../RangeContext'; +import { useInjectRange } from '../../RangeContext'; import PanelBody from '../PanelBody'; +import useMergeProps from '../../hooks/useMergeProps'; export const YEAR_COL_COUNT = 3; const YEAR_ROW_COUNT = 4; @@ -20,9 +20,10 @@ export type YearBodyProps = { onSelect: (value: DateType) => void; }; -function YearBody(props: YearBodyProps) { +function YearBody(_props: YearBodyProps) { + const props = useMergeProps(_props); const { prefixCls, value, viewDate, locale, generateConfig } = props; - const { rangedValue, hoverRangedValue } = useInjectRange() + const { rangedValue, hoverRangedValue } = useInjectRange(); const yearPrefixCls = `${prefixCls}-cell`; @@ -44,8 +45,8 @@ function YearBody(props: YearBodyProps) { cellPrefixCls: yearPrefixCls, value, generateConfig, - rangedValue, - hoverRangedValue, + rangedValue: rangedValue.value, + hoverRangedValue: hoverRangedValue.value, isSameCell: (current, target) => isSameYear(generateConfig, current, target), isInView, offsetCell: (date, offset) => generateConfig.addYear(date, offset), @@ -71,8 +72,6 @@ function YearBody(props: YearBodyProps) { ); } - - YearBody.displayName = 'YearBody'; YearBody.inheritAttrs = false; diff --git a/components/vc-picker/panels/YearPanel/YearHeader.tsx b/components/vc-picker/panels/YearPanel/YearHeader.tsx index d7d3d52e0..79d95fb7d 100644 --- a/components/vc-picker/panels/YearPanel/YearHeader.tsx +++ b/components/vc-picker/panels/YearPanel/YearHeader.tsx @@ -1,8 +1,8 @@ - import Header from '../Header'; import type { GenerateConfig } from '../../generate'; import { YEAR_DECADE_COUNT } from '.'; import { useInjectPanel } from '../../PanelContext'; +import useMergeProps from '../../hooks/useMergeProps'; export type YearHeaderProps = { prefixCls: string; @@ -15,10 +15,11 @@ export type YearHeaderProps = { onDecadeClick: () => void; }; -function YearHeader(props: YearHeaderProps) { +function YearHeader(_props: YearHeaderProps) { + const props = useMergeProps(_props); const { prefixCls, generateConfig, viewDate, onPrevDecade, onNextDecade, onDecadeClick } = props; - const { hideHeader } = useInjectPanel() - if (hideHeader) { + const { hideHeader } = useInjectPanel(); + if (hideHeader.value) { return null; } @@ -42,8 +43,6 @@ function YearHeader(props: YearHeaderProps) { ); } - - YearHeader.displayName = 'YearHeader'; YearHeader.inheritAttrs = false; diff --git a/components/vc-picker/panels/YearPanel/index.tsx b/components/vc-picker/panels/YearPanel/index.tsx index b8191cf41..046b3e62e 100644 --- a/components/vc-picker/panels/YearPanel/index.tsx +++ b/components/vc-picker/panels/YearPanel/index.tsx @@ -1,8 +1,8 @@ - import YearHeader from './YearHeader'; import YearBody, { YEAR_COL_COUNT } from './YearBody'; import type { PanelSharedProps, PanelMode } from '../../interface'; import { createKeyDownHandler } from '../../utils/uiUtil'; +import useMergeProps from '../../hooks/useMergeProps'; export type YearPanelProps = { sourceMode: PanelMode; @@ -10,7 +10,8 @@ export type YearPanelProps = { export const YEAR_DECADE_COUNT = 10; -function YearPanel(props: YearPanelProps) { +function YearPanel(_props: YearPanelProps) { + const props = useMergeProps(_props); const { prefixCls, operationRef, @@ -26,29 +27,20 @@ function YearPanel(props: YearPanelProps) { const panelPrefixCls = `${prefixCls}-year-panel`; // ======================= Keyboard ======================= - operationRef.current = { - onKeyDown: event => + operationRef.value = { + onKeyDown: (event: KeyboardEvent) => createKeyDownHandler(event, { onLeftRight: diff => { onSelect(generateConfig.addYear(value || viewDate, diff), 'key'); }, onCtrlLeftRight: diff => { - onSelect( - generateConfig.addYear(value || viewDate, diff * YEAR_DECADE_COUNT), - 'key', - ); + onSelect(generateConfig.addYear(value || viewDate, diff * YEAR_DECADE_COUNT), 'key'); }, onUpDown: diff => { - onSelect( - generateConfig.addYear(value || viewDate, diff * YEAR_COL_COUNT), - 'key', - ); + onSelect(generateConfig.addYear(value || viewDate, diff * YEAR_COL_COUNT), 'key'); }, onEnter: () => { - onPanelChange( - sourceMode === 'date' ? 'date' : 'month', - value || viewDate, - ); + onPanelChange(sourceMode === 'date' ? 'date' : 'month', value || viewDate); }, }), }; @@ -87,8 +79,6 @@ function YearPanel(props: YearPanelProps) { ); } - - YearPanel.displayName = 'YearPanel'; YearPanel.inheritAttrs = false; diff --git a/components/vc-picker/utils/getExtraFooter.tsx b/components/vc-picker/utils/getExtraFooter.tsx index 2a03db7ee..b536adea8 100644 --- a/components/vc-picker/utils/getExtraFooter.tsx +++ b/components/vc-picker/utils/getExtraFooter.tsx @@ -1,4 +1,3 @@ - import type { PanelMode } from '../interface'; export default function getExtraFooter( @@ -10,7 +9,5 @@ export default function getExtraFooter( return null; } - return ( - - ); + return ; } diff --git a/components/vc-picker/utils/getRanges.tsx b/components/vc-picker/utils/getRanges.tsx index 20fcb140f..3bb9a20e4 100644 --- a/components/vc-picker/utils/getRanges.tsx +++ b/components/vc-picker/utils/getRanges.tsx @@ -1,4 +1,3 @@ - import { VueNode } from '../../_util/type'; import type { Components, RangeList, Locale } from '../interface'; diff --git a/components/vc-picker/utils/miscUtil.ts b/components/vc-picker/utils/miscUtil.ts index ed62cb73b..4ba6c67f2 100644 --- a/components/vc-picker/utils/miscUtil.ts +++ b/components/vc-picker/utils/miscUtil.ts @@ -51,8 +51,8 @@ export function updateValues( typeof value === 'function' ? (value as UpdateValue)(newValues[index]) : value; if (!newValues[0] && !newValues[1]) { - return (null as unknown) as R; + return null as unknown as R; } - return (newValues as unknown) as R; + return newValues as unknown as R; } diff --git a/components/vc-picker/utils/timeUtil.ts b/components/vc-picker/utils/timeUtil.ts index 3b4051824..f666723ba 100644 --- a/components/vc-picker/utils/timeUtil.ts +++ b/components/vc-picker/utils/timeUtil.ts @@ -24,22 +24,12 @@ export function setDateTime( } let newDate = date; - newDate = generateConfig.setHour( - newDate, - generateConfig.getHour(defaultDate), - ); - newDate = generateConfig.setMinute( - newDate, - generateConfig.getMinute(defaultDate), - ); - newDate = generateConfig.setSecond( - newDate, - generateConfig.getSecond(defaultDate), - ); + newDate = generateConfig.setHour(newDate, generateConfig.getHour(defaultDate)); + newDate = generateConfig.setMinute(newDate, generateConfig.getMinute(defaultDate)); + newDate = generateConfig.setSecond(newDate, generateConfig.getSecond(defaultDate)); return newDate; } - export function getLowerBoundTime( hour: number, minute: number, diff --git a/components/vc-picker/utils/uiUtil.ts b/components/vc-picker/utils/uiUtil.ts index ae41a2874..6145af915 100644 --- a/components/vc-picker/utils/uiUtil.ts +++ b/components/vc-picker/utils/uiUtil.ts @@ -201,7 +201,7 @@ export function addGlobalMouseDownEvent(callback: ClickEventHandler) { if (!globalClickFunc && typeof window !== 'undefined' && window.addEventListener) { globalClickFunc = (e: MouseEvent) => { // Clone a new list to avoid repeat trigger events - [...clickCallbacks].forEach((queueFunc) => { + [...clickCallbacks].forEach(queueFunc => { queueFunc(e); }); }; @@ -272,5 +272,5 @@ export function elementsContains( elements: (HTMLElement | undefined | null)[], target: HTMLElement, ) { - return elements.some((ele) => ele && ele.contains(target)); + return elements.some(ele => ele && ele.contains(target)); }