feat: picker add disabledTime

feat-css-var
tangjinzhou 2022-02-26 16:50:44 +08:00
parent 2573fe41f5
commit afafbd62fc
8 changed files with 157 additions and 40 deletions

View File

@ -28,7 +28,7 @@ import usePickerInput from './hooks/usePickerInput';
import useTextValueMapping from './hooks/useTextValueMapping'; import useTextValueMapping from './hooks/useTextValueMapping';
import useValueTexts from './hooks/useValueTexts'; import useValueTexts from './hooks/useValueTexts';
import useHoverValue from './hooks/useHoverValue'; 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 { computed, defineComponent, ref, toRef, watch } from 'vue';
import type { ChangeEvent, FocusEventHandler, MouseEventHandler } from '../_util/EventInterface'; import type { ChangeEvent, FocusEventHandler, MouseEventHandler } from '../_util/EventInterface';
import type { VueNode } from '../_util/type'; import type { VueNode } from '../_util/type';
@ -38,6 +38,7 @@ import { warning } from '../vc-util/warning';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import type { SharedTimeProps } from './panels/TimePanel'; import type { SharedTimeProps } from './panels/TimePanel';
import { useProviderTrigger } from '../vc-trigger/context'; import { useProviderTrigger } from '../vc-trigger/context';
import { legacyPropsWarning } from './utils/warnUtil';
export type PickerRefConfig = { export type PickerRefConfig = {
focus: () => void; focus: () => void;
@ -72,6 +73,7 @@ export type PickerSharedProps<DateType> = {
superNextIcon?: VueNode; superNextIcon?: VueNode;
getPopupContainer?: (node: HTMLElement) => HTMLElement; getPopupContainer?: (node: HTMLElement) => HTMLElement;
panelRender?: (originPanel: VueNode) => VueNode; panelRender?: (originPanel: VueNode) => VueNode;
inputRender?: (props: HTMLAttributes) => VueNode;
// Events // Events
onChange?: (value: DateType | null, dateString: string) => void; onChange?: (value: DateType | null, dateString: string) => void;
@ -167,6 +169,7 @@ function Picker<DateType>() {
'placeholder', 'placeholder',
'getPopupContainer', 'getPopupContainer',
'panelRender', 'panelRender',
'inputRender',
'onChange', 'onChange',
'onOpenChange', 'onOpenChange',
'onFocus', 'onFocus',
@ -200,15 +203,19 @@ function Picker<DateType>() {
const needConfirmButton = computed( const needConfirmButton = computed(
() => (picker.value === 'date' && !!props.showTime) || picker.value === 'time', () => (picker.value === 'date' && !!props.showTime) || picker.value === 'time',
); );
// ============================ Warning ============================
if (process.env.NODE_ENV !== 'production') {
legacyPropsWarning(props);
}
// ============================= State ============================= // ============================= State =============================
const formatList = computed(() => const formatList = computed(() =>
toArray(getDefaultFormat(props.format, picker.value, props.showTime, props.use12Hours)), toArray(getDefaultFormat(props.format, picker.value, props.showTime, props.use12Hours)),
); );
// Panel ref // Panel ref
const panelDivRef = ref(null); const panelDivRef = ref<HTMLDivElement>(null);
const inputDivRef = ref(null); const inputDivRef = ref<HTMLDivElement>(null);
const containerRef = ref<HTMLDivElement>(null);
// Real value // Real value
const [mergedValue, setInnerValue] = useMergedState<DateType>(null, { const [mergedValue, setInnerValue] = useMergedState<DateType>(null, {
@ -318,9 +325,17 @@ function Picker<DateType>() {
triggerOpen, triggerOpen,
forwardKeydown, forwardKeydown,
isClickOutside: target => isClickOutside: target =>
!elementsContains([panelDivRef.value, inputDivRef.value], target as HTMLElement), !elementsContains(
[panelDivRef.value, inputDivRef.value, containerRef.value],
target as HTMLElement,
),
onSubmit: () => { 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; return false;
} }
@ -520,6 +535,31 @@ function Picker<DateType>() {
); );
} }
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)
) : (
<input {...mergedInputProps} />
);
// ============================ Warning ============================ // ============================ Warning ============================
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
warning( warning(
@ -547,6 +587,7 @@ function Picker<DateType>() {
}} }}
> >
<div <div
ref={containerRef}
class={classNames(prefixCls, attrs.class, { class={classNames(prefixCls, attrs.class, {
[`${prefixCls}-disabled`]: disabled, [`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-focused`]: focused.value, [`${prefixCls}-focused`]: focused.value,
@ -566,26 +607,7 @@ function Picker<DateType>() {
})} })}
ref={inputDivRef} ref={inputDivRef}
> >
<input {inputNode}
id={id}
tabindex={tabindex}
disabled={disabled}
readonly={
inputReadOnly || typeof formatList.value[0] === 'function' || !typing.value
}
value={hoverValue.value || text.value}
onInput={(e: ChangeEvent) => {
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}
/>
{suffixNode} {suffixNode}
{clearNode} {clearNode}
</div> </div>

View File

@ -215,12 +215,20 @@ function PickerPanel<DateType>() {
// When value is null and set showTime // When value is null and set showTime
if (!mergedValue.value && props.showTime) { if (!mergedValue.value && props.showTime) {
if (typeof showTime === 'object') { 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) { 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; return date;
}, },

View File

@ -36,6 +36,7 @@ import { warning } from '../vc-util/warning';
import useState from '../_util/hooks/useState'; import useState from '../_util/hooks/useState';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { useProviderTrigger } from '../vc-trigger/context'; import { useProviderTrigger } from '../vc-trigger/context';
import { legacyPropsWarning } from './utils/warnUtil';
function reorderValues<DateType>( function reorderValues<DateType>(
values: RangeValue<DateType>, values: RangeValue<DateType>,
@ -105,8 +106,11 @@ export type RangePickerSharedProps<DateType> = {
onPanelChange?: (values: RangeValue<DateType>, modes: [PanelMode, PanelMode]) => void; onPanelChange?: (values: RangeValue<DateType>, modes: [PanelMode, PanelMode]) => void;
onFocus?: FocusEventHandler; onFocus?: FocusEventHandler;
onBlur?: FocusEventHandler; onBlur?: FocusEventHandler;
onMousedown?: MouseEventHandler;
onMouseup?: MouseEventHandler;
onMouseenter?: MouseEventHandler; onMouseenter?: MouseEventHandler;
onMouseleave?: MouseEventHandler; onMouseleave?: MouseEventHandler;
onClick?: MouseEventHandler;
onOk?: (dates: RangeValue<DateType>) => void; onOk?: (dates: RangeValue<DateType>) => void;
direction?: 'ltr' | 'rtl'; direction?: 'ltr' | 'rtl';
autocomplete?: string; autocomplete?: string;
@ -216,8 +220,11 @@ function RangerPicker<DateType>() {
'onCalendarChange', 'onCalendarChange',
'onFocus', 'onFocus',
'onBlur', 'onBlur',
'onMousedown',
'onMouseup',
'onMouseenter', 'onMouseenter',
'onMouseleave', 'onMouseleave',
'onClick',
'onOk', 'onOk',
'onKeydown', 'onKeydown',
'components', 'components',
@ -241,6 +248,12 @@ function RangerPicker<DateType>() {
const separatorRef = ref<HTMLDivElement>(null); const separatorRef = ref<HTMLDivElement>(null);
const startInputRef = ref<HTMLInputElement>(null); const startInputRef = ref<HTMLInputElement>(null);
const endInputRef = ref<HTMLInputElement>(null); const endInputRef = ref<HTMLInputElement>(null);
const arrowRef = ref<HTMLDivElement>(null);
// ============================ Warning ============================
if (process.env.NODE_ENV !== 'production') {
legacyPropsWarning(props);
}
// ============================= Misc ============================== // ============================= Misc ==============================
const formatList = computed(() => const formatList = computed(() =>
@ -604,7 +617,7 @@ function RangerPicker<DateType>() {
}, },
isClickOutside: (target: EventTarget | null) => isClickOutside: (target: EventTarget | null) =>
!elementsContains( !elementsContains(
[panelDivRef.value, startInputDivRef.value, endInputDivRef.value], [panelDivRef.value, startInputDivRef.value, endInputDivRef.value, containerRef.value],
target as HTMLElement, target as HTMLElement,
), ),
onFocus: (e: FocusEvent) => { onFocus: (e: FocusEvent) => {
@ -615,6 +628,14 @@ function RangerPicker<DateType>() {
triggerOpen(newOpen, index); triggerOpen(newOpen, index);
}, },
onSubmit: () => { 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); triggerChange(selectedValue.value, index);
resetText(); resetText();
}, },
@ -649,6 +670,7 @@ function RangerPicker<DateType>() {
const onPickerClick = (e: MouseEvent) => { const onPickerClick = (e: MouseEvent) => {
// When click inside the picker & outside the picker's input elements // When click inside the picker & outside the picker's input elements
// the panel should still be opened // the panel should still be opened
props.onClick?.(e);
if ( if (
!mergedOpen.value && !mergedOpen.value &&
!startInputRef.value.contains(e.target as Node) && !startInputRef.value.contains(e.target as Node) &&
@ -664,6 +686,7 @@ function RangerPicker<DateType>() {
const onPickerMousedown = (e: MouseEvent) => { const onPickerMousedown = (e: MouseEvent) => {
// shouldn't affect input elements if picker is active // shouldn't affect input elements if picker is active
props.onMousedown?.(e);
if ( if (
mergedOpen.value && mergedOpen.value &&
(startFocused.value || endFocused.value) && (startFocused.value || endFocused.value) &&
@ -880,7 +903,6 @@ function RangerPicker<DateType>() {
? getValue(selectedValue.value, 1) ? getValue(selectedValue.value, 1)
: getValue(selectedValue.value, 0) : getValue(selectedValue.value, 0)
} }
defaultPickerValue={undefined}
/> />
</RangeContextProvider> </RangeContextProvider>
); );
@ -938,6 +960,7 @@ function RangerPicker<DateType>() {
renderExtraFooter, renderExtraFooter,
onMouseenter, onMouseenter,
onMouseleave, onMouseleave,
onMouseup,
onOk, onOk,
components, components,
direction, direction,
@ -954,7 +977,14 @@ function RangerPicker<DateType>() {
// Arrow offset // Arrow offset
arrowLeft = startInputDivRef.value.offsetWidth + separatorRef.value.offsetWidth; 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; panelLeft = arrowLeft;
} }
} }
@ -1066,7 +1096,7 @@ function RangerPicker<DateType>() {
class={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)} class={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)}
style={{ minWidth: `${popupMinWidth.value}px` }} style={{ minWidth: `${popupMinWidth.value}px` }}
> >
<div class={`${prefixCls}-range-arrow`} style={arrowPositionStyle} /> <div ref={arrowRef} class={`${prefixCls}-range-arrow`} style={arrowPositionStyle} />
{renderPanels()} {renderPanels()}
</div> </div>
); );
@ -1157,6 +1187,7 @@ function RangerPicker<DateType>() {
onMouseenter={onMouseenter} onMouseenter={onMouseenter}
onMouseleave={onMouseleave} onMouseleave={onMouseleave}
onMousedown={onPickerMousedown} onMousedown={onPickerMousedown}
onMouseup={onMouseup}
{...getDataOrAriaProps(props)} {...getDataOrAriaProps(props)}
> >
<div <div

View File

@ -1,4 +1,4 @@
// 2.5.12 // 2.6.4
import type { PickerProps } from './Picker'; import type { PickerProps } from './Picker';
import Picker from './Picker'; import Picker from './Picker';
import PickerPanel from './PickerPanel'; import PickerPanel from './PickerPanel';

View File

@ -137,7 +137,7 @@ function DatetimePanel<DateType>(_props: DatetimePanelProps<DateType>) {
setTime( setTime(
generateConfig, generateConfig,
date, date,
showTime && typeof showTime === 'object' ? showTime.defaultValue : null, !value && typeof showTime === 'object' ? showTime.defaultValue : null,
), ),
'date', 'date',
); );
@ -148,6 +148,7 @@ function DatetimePanel<DateType>(_props: DatetimePanelProps<DateType>) {
format={undefined} format={undefined}
{...timeProps} {...timeProps}
{...disabledTimes} {...disabledTimes}
disabledTime={null}
defaultValue={undefined} defaultValue={undefined}
operationRef={timeOperationRef} operationRef={timeOperationRef}
active={activePanel.value === 'time'} active={activePanel.value === 'time'}

View File

@ -8,7 +8,7 @@ import { setTime as utilSetTime } from '../../utils/timeUtil';
import { cloneElement } from '../../../_util/vnode'; import { cloneElement } from '../../../_util/vnode';
import type { VueNode } from '../../../_util/type'; import type { VueNode } from '../../../_util/type';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { computed, defineComponent } from 'vue'; import { onBeforeUpdate, ref, watchEffect, computed, defineComponent } from 'vue';
function generateUnits( function generateUnits(
start: number, start: number,
@ -60,6 +60,7 @@ const TimeBody = defineComponent({
'disabledHours', 'disabledHours',
'disabledMinutes', 'disabledMinutes',
'disabledSeconds', 'disabledSeconds',
'disabledTime',
'hideDisabledOptions', 'hideDisabledOptions',
'onSelect', 'onSelect',
], ],
@ -85,6 +86,29 @@ const TimeBody = defineComponent({
const minute = computed(() => (props.value ? props.generateConfig.getMinute(props.value) : -1)); const minute = computed(() => (props.value ? props.generateConfig.getMinute(props.value) : -1));
const second = computed(() => (props.value ? props.generateConfig.getSecond(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 = ( const setTime = (
isNewPM: boolean | undefined, isNewPM: boolean | undefined,
newHour: number, newHour: number,
@ -110,7 +134,12 @@ const TimeBody = defineComponent({
// ========================= Unit ========================= // ========================= Unit =========================
const rawHours = computed(() => 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); // const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate);
@ -151,7 +180,7 @@ const TimeBody = defineComponent({
0, 0,
59, 59,
props.minuteStep ?? 1, props.minuteStep ?? 1,
props.disabledMinutes && props.disabledMinutes(originHour.value), mergedDisabledMinutes.value && mergedDisabledMinutes.value(originHour.value),
), ),
); );
@ -160,7 +189,7 @@ const TimeBody = defineComponent({
0, 0,
59, 59,
props.secondStep ?? 1, props.secondStep ?? 1,
props.disabledSeconds && props.disabledSeconds(originHour.value, minute), mergedDisabledSeconds.value && mergedDisabledSeconds.value(originHour.value, minute),
), ),
); );

View File

@ -19,7 +19,16 @@ export type SharedTimeProps<DateType> = {
secondStep?: number; secondStep?: number;
hideDisabledOptions?: boolean; hideDisabledOptions?: boolean;
defaultValue?: DateType; 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<DateType> = { export type TimePanelProps<DateType> = {
format?: string; format?: string;

View File

@ -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.`,
);
}
}