From afafbd62fcdcce9a12c20c746501c19eacbb2128 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Sat, 26 Feb 2022 16:50:44 +0800 Subject: [PATCH] feat: picker add disabledTime --- components/vc-picker/Picker.tsx | 74 ++++++++++++------- components/vc-picker/PickerPanel.tsx | 14 +++- components/vc-picker/RangePicker.tsx | 39 +++++++++- components/vc-picker/index.tsx | 2 +- .../vc-picker/panels/DatetimePanel/index.tsx | 3 +- .../vc-picker/panels/TimePanel/TimeBody.tsx | 37 +++++++++- .../vc-picker/panels/TimePanel/index.tsx | 11 ++- components/vc-picker/utils/warnUtil.ts | 17 +++++ 8 files changed, 157 insertions(+), 40 deletions(-) create mode 100644 components/vc-picker/utils/warnUtil.ts diff --git a/components/vc-picker/Picker.tsx b/components/vc-picker/Picker.tsx index 8a2f5da3e..dda74d140 100644 --- a/components/vc-picker/Picker.tsx +++ b/components/vc-picker/Picker.tsx @@ -28,7 +28,7 @@ import usePickerInput from './hooks/usePickerInput'; import useTextValueMapping from './hooks/useTextValueMapping'; import useValueTexts from './hooks/useValueTexts'; import useHoverValue from './hooks/useHoverValue'; -import type { CSSProperties, Ref } from 'vue'; +import type { CSSProperties, HTMLAttributes, Ref } from 'vue'; import { computed, defineComponent, ref, toRef, watch } from 'vue'; import type { ChangeEvent, FocusEventHandler, MouseEventHandler } from '../_util/EventInterface'; import type { VueNode } from '../_util/type'; @@ -38,6 +38,7 @@ import { warning } from '../vc-util/warning'; import classNames from '../_util/classNames'; import type { SharedTimeProps } from './panels/TimePanel'; import { useProviderTrigger } from '../vc-trigger/context'; +import { legacyPropsWarning } from './utils/warnUtil'; export type PickerRefConfig = { focus: () => void; @@ -72,6 +73,7 @@ export type PickerSharedProps = { superNextIcon?: VueNode; getPopupContainer?: (node: HTMLElement) => HTMLElement; panelRender?: (originPanel: VueNode) => VueNode; + inputRender?: (props: HTMLAttributes) => VueNode; // Events onChange?: (value: DateType | null, dateString: string) => void; @@ -167,6 +169,7 @@ function Picker() { 'placeholder', 'getPopupContainer', 'panelRender', + 'inputRender', 'onChange', 'onOpenChange', 'onFocus', @@ -200,15 +203,19 @@ function Picker() { const needConfirmButton = computed( () => (picker.value === 'date' && !!props.showTime) || picker.value === 'time', ); - + // ============================ Warning ============================ + if (process.env.NODE_ENV !== 'production') { + legacyPropsWarning(props); + } // ============================= State ============================= const formatList = computed(() => toArray(getDefaultFormat(props.format, picker.value, props.showTime, props.use12Hours)), ); // Panel ref - const panelDivRef = ref(null); - const inputDivRef = ref(null); + const panelDivRef = ref(null); + const inputDivRef = ref(null); + const containerRef = ref(null); // Real value const [mergedValue, setInnerValue] = useMergedState(null, { @@ -318,9 +325,17 @@ function Picker() { triggerOpen, forwardKeydown, isClickOutside: target => - !elementsContains([panelDivRef.value, inputDivRef.value], target as HTMLElement), + !elementsContains( + [panelDivRef.value, inputDivRef.value, containerRef.value], + target as HTMLElement, + ), onSubmit: () => { - if (props.disabledDate && props.disabledDate(selectedValue.value)) { + if ( + // When user typing disabledDate with keyboard and enter, this value will be empty + !selectedValue.value || + // Normal disabled check + (props.disabledDate && props.disabledDate(selectedValue.value)) + ) { return false; } @@ -520,6 +535,31 @@ function Picker() { ); } + const mergedInputProps: HTMLAttributes = { + id, + tabindex, + disabled, + readonly: inputReadOnly || typeof formatList.value[0] === 'function' || !typing.value, + value: hoverValue.value || text.value, + onInput: (e: ChangeEvent) => { + triggerTextChange(e.target.value); + }, + autofocus, + placeholder, + ref: inputRef, + title: text.value, + ...inputProps.value, + size: getInputSize(picker, formatList.value[0], generateConfig), + ...getDataOrAriaProps(props), + autocomplete, + }; + + const inputNode = props.inputRender ? ( + props.inputRender(mergedInputProps) + ) : ( + + ); + // ============================ Warning ============================ if (process.env.NODE_ENV !== 'production') { warning( @@ -547,6 +587,7 @@ function Picker() { }} >
() { })} ref={inputDivRef} > - { - triggerTextChange(e.target.value); - }} - autofocus={autofocus} - placeholder={placeholder} - ref={inputRef} - title={text.value} - {...inputProps.value} - size={getInputSize(picker, formatList.value[0], generateConfig)} - {...getDataOrAriaProps(props)} - autocomplete={autocomplete} - /> + {inputNode} {suffixNode} {clearNode}
diff --git a/components/vc-picker/PickerPanel.tsx b/components/vc-picker/PickerPanel.tsx index 9253d39a5..e7883dfa6 100644 --- a/components/vc-picker/PickerPanel.tsx +++ b/components/vc-picker/PickerPanel.tsx @@ -215,12 +215,20 @@ function PickerPanel() { // When value is null and set showTime if (!mergedValue.value && props.showTime) { if (typeof showTime === 'object') { - return setDateTime(generateConfig, date, showTime.defaultValue || now); + return setDateTime( + generateConfig, + Array.isArray(date) ? date[0] : date, + showTime.defaultValue || now, + ); } if (defaultValue) { - return setDateTime(generateConfig, date, defaultValue); + return setDateTime( + generateConfig, + Array.isArray(date) ? date[0] : date, + defaultValue, + ); } - return setDateTime(generateConfig, date, now); + return setDateTime(generateConfig, Array.isArray(date) ? date[0] : date, now); } return date; }, diff --git a/components/vc-picker/RangePicker.tsx b/components/vc-picker/RangePicker.tsx index 3f819333f..518252889 100644 --- a/components/vc-picker/RangePicker.tsx +++ b/components/vc-picker/RangePicker.tsx @@ -36,6 +36,7 @@ import { warning } from '../vc-util/warning'; import useState from '../_util/hooks/useState'; import classNames from '../_util/classNames'; import { useProviderTrigger } from '../vc-trigger/context'; +import { legacyPropsWarning } from './utils/warnUtil'; function reorderValues( values: RangeValue, @@ -105,8 +106,11 @@ export type RangePickerSharedProps = { onPanelChange?: (values: RangeValue, modes: [PanelMode, PanelMode]) => void; onFocus?: FocusEventHandler; onBlur?: FocusEventHandler; + onMousedown?: MouseEventHandler; + onMouseup?: MouseEventHandler; onMouseenter?: MouseEventHandler; onMouseleave?: MouseEventHandler; + onClick?: MouseEventHandler; onOk?: (dates: RangeValue) => void; direction?: 'ltr' | 'rtl'; autocomplete?: string; @@ -216,8 +220,11 @@ function RangerPicker() { 'onCalendarChange', 'onFocus', 'onBlur', + 'onMousedown', + 'onMouseup', 'onMouseenter', 'onMouseleave', + 'onClick', 'onOk', 'onKeydown', 'components', @@ -241,6 +248,12 @@ function RangerPicker() { const separatorRef = ref(null); const startInputRef = ref(null); const endInputRef = ref(null); + const arrowRef = ref(null); + + // ============================ Warning ============================ + if (process.env.NODE_ENV !== 'production') { + legacyPropsWarning(props); + } // ============================= Misc ============================== const formatList = computed(() => @@ -604,7 +617,7 @@ function RangerPicker() { }, isClickOutside: (target: EventTarget | null) => !elementsContains( - [panelDivRef.value, startInputDivRef.value, endInputDivRef.value], + [panelDivRef.value, startInputDivRef.value, endInputDivRef.value, containerRef.value], target as HTMLElement, ), onFocus: (e: FocusEvent) => { @@ -615,6 +628,14 @@ function RangerPicker() { triggerOpen(newOpen, index); }, onSubmit: () => { + if ( + // When user typing disabledDate with keyboard and enter, this value will be empty + !selectedValue.value || + // Normal disabled check + (props.disabledDate && props.disabledDate(selectedValue.value[index])) + ) { + return false; + } triggerChange(selectedValue.value, index); resetText(); }, @@ -649,6 +670,7 @@ function RangerPicker() { const onPickerClick = (e: MouseEvent) => { // When click inside the picker & outside the picker's input elements // the panel should still be opened + props.onClick?.(e); if ( !mergedOpen.value && !startInputRef.value.contains(e.target as Node) && @@ -664,6 +686,7 @@ function RangerPicker() { const onPickerMousedown = (e: MouseEvent) => { // shouldn't affect input elements if picker is active + props.onMousedown?.(e); if ( mergedOpen.value && (startFocused.value || endFocused.value) && @@ -880,7 +903,6 @@ function RangerPicker() { ? getValue(selectedValue.value, 1) : getValue(selectedValue.value, 0) } - defaultPickerValue={undefined} /> ); @@ -938,6 +960,7 @@ function RangerPicker() { renderExtraFooter, onMouseenter, onMouseleave, + onMouseup, onOk, components, direction, @@ -954,7 +977,14 @@ function RangerPicker() { // Arrow offset arrowLeft = startInputDivRef.value.offsetWidth + separatorRef.value.offsetWidth; - if (panelDivRef.value.offsetWidth && arrowLeft > panelDivRef.value.offsetWidth) { + if ( + panelDivRef.value.offsetWidth && + arrowRef.value.offsetWidth && + arrowLeft > + panelDivRef.value.offsetWidth - + arrowRef.value.offsetWidth - + (direction === 'rtl' ? 0 : arrowRef.value.offsetLeft) + ) { panelLeft = arrowLeft; } } @@ -1066,7 +1096,7 @@ function RangerPicker() { class={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)} style={{ minWidth: `${popupMinWidth.value}px` }} > -
+
{renderPanels()}
); @@ -1157,6 +1187,7 @@ function RangerPicker() { onMouseenter={onMouseenter} onMouseleave={onMouseleave} onMousedown={onPickerMousedown} + onMouseup={onMouseup} {...getDataOrAriaProps(props)} >
(_props: DatetimePanelProps) { setTime( generateConfig, date, - showTime && typeof showTime === 'object' ? showTime.defaultValue : null, + !value && typeof showTime === 'object' ? showTime.defaultValue : null, ), 'date', ); @@ -148,6 +148,7 @@ function DatetimePanel(_props: DatetimePanelProps) { format={undefined} {...timeProps} {...disabledTimes} + disabledTime={null} defaultValue={undefined} operationRef={timeOperationRef} active={activePanel.value === 'time'} diff --git a/components/vc-picker/panels/TimePanel/TimeBody.tsx b/components/vc-picker/panels/TimePanel/TimeBody.tsx index 2c99941e0..d00e27213 100644 --- a/components/vc-picker/panels/TimePanel/TimeBody.tsx +++ b/components/vc-picker/panels/TimePanel/TimeBody.tsx @@ -8,7 +8,7 @@ import { setTime as utilSetTime } from '../../utils/timeUtil'; import { cloneElement } from '../../../_util/vnode'; import type { VueNode } from '../../../_util/type'; import type { Ref } from 'vue'; -import { computed, defineComponent } from 'vue'; +import { onBeforeUpdate, ref, watchEffect, computed, defineComponent } from 'vue'; function generateUnits( start: number, @@ -60,6 +60,7 @@ const TimeBody = defineComponent({ 'disabledHours', 'disabledMinutes', 'disabledSeconds', + 'disabledTime', 'hideDisabledOptions', 'onSelect', ], @@ -85,6 +86,29 @@ const TimeBody = defineComponent({ const minute = computed(() => (props.value ? props.generateConfig.getMinute(props.value) : -1)); const second = computed(() => (props.value ? props.generateConfig.getSecond(props.value) : -1)); + const now = ref(props.generateConfig.getNow()); + const mergedDisabledHours = ref(); + const mergedDisabledMinutes = ref(); + const mergedDisabledSeconds = ref(); + onBeforeUpdate(() => { + now.value = props.generateConfig.getNow(); + }); + watchEffect(() => { + if (props.disabledTime) { + const disabledConfig = props.disabledTime(now); + [mergedDisabledHours.value, mergedDisabledMinutes.value, mergedDisabledSeconds.value] = [ + disabledConfig.disabledHours, + disabledConfig.disabledMinutes, + disabledConfig.disabledSeconds, + ]; + } + + [mergedDisabledHours.value, mergedDisabledMinutes.value, mergedDisabledSeconds.value] = [ + props.disabledHours, + props.disabledMinutes, + props.disabledSeconds, + ]; + }); const setTime = ( isNewPM: boolean | undefined, newHour: number, @@ -110,7 +134,12 @@ const TimeBody = defineComponent({ // ========================= Unit ========================= const rawHours = computed(() => - generateUnits(0, 23, props.hourStep ?? 1, props.disabledHours && props.disabledHours()), + generateUnits( + 0, + 23, + props.hourStep ?? 1, + mergedDisabledHours.value && mergedDisabledHours.value(), + ), ); // const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate); @@ -151,7 +180,7 @@ const TimeBody = defineComponent({ 0, 59, props.minuteStep ?? 1, - props.disabledMinutes && props.disabledMinutes(originHour.value), + mergedDisabledMinutes.value && mergedDisabledMinutes.value(originHour.value), ), ); @@ -160,7 +189,7 @@ const TimeBody = defineComponent({ 0, 59, props.secondStep ?? 1, - props.disabledSeconds && props.disabledSeconds(originHour.value, minute), + mergedDisabledSeconds.value && mergedDisabledSeconds.value(originHour.value, minute), ), ); diff --git a/components/vc-picker/panels/TimePanel/index.tsx b/components/vc-picker/panels/TimePanel/index.tsx index e2e0f56f7..8e2d86761 100644 --- a/components/vc-picker/panels/TimePanel/index.tsx +++ b/components/vc-picker/panels/TimePanel/index.tsx @@ -19,7 +19,16 @@ export type SharedTimeProps = { secondStep?: number; hideDisabledOptions?: boolean; defaultValue?: DateType; -} & DisabledTimes; + + /** @deprecated Please use `disabledTime` instead. */ + disabledHours?: DisabledTimes['disabledHours']; + /** @deprecated Please use `disabledTime` instead. */ + disabledMinutes?: DisabledTimes['disabledMinutes']; + /** @deprecated Please use `disabledTime` instead. */ + disabledSeconds?: DisabledTimes['disabledSeconds']; + + disabledTime?: (date: DateType) => DisabledTimes; +}; export type TimePanelProps = { format?: string; diff --git a/components/vc-picker/utils/warnUtil.ts b/components/vc-picker/utils/warnUtil.ts new file mode 100644 index 000000000..642c3331d --- /dev/null +++ b/components/vc-picker/utils/warnUtil.ts @@ -0,0 +1,17 @@ +import { warning } from '../../vc-util/warning'; +import type { DisabledTimes, PickerMode } from '../interface'; + +export interface WarningProps extends DisabledTimes { + picker?: PickerMode; +} + +export function legacyPropsWarning(props: WarningProps) { + const { picker, disabledHours, disabledMinutes, disabledSeconds } = props; + + if (picker === 'time' && (disabledHours || disabledMinutes || disabledSeconds)) { + warning( + false, + `'disabledHours', 'disabledMinutes', 'disabledSeconds' will be removed in the next major version, please use 'disabledTime' instead.`, + ); + } +}