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 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<DateType> = {
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<DateType>() {
'placeholder',
'getPopupContainer',
'panelRender',
'inputRender',
'onChange',
'onOpenChange',
'onFocus',
@ -200,15 +203,19 @@ function Picker<DateType>() {
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<HTMLDivElement>(null);
const inputDivRef = ref<HTMLDivElement>(null);
const containerRef = ref<HTMLDivElement>(null);
// Real value
const [mergedValue, setInnerValue] = useMergedState<DateType>(null, {
@ -318,9 +325,17 @@ function Picker<DateType>() {
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<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 ============================
if (process.env.NODE_ENV !== 'production') {
warning(
@ -547,6 +587,7 @@ function Picker<DateType>() {
}}
>
<div
ref={containerRef}
class={classNames(prefixCls, attrs.class, {
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-focused`]: focused.value,
@ -566,26 +607,7 @@ function Picker<DateType>() {
})}
ref={inputDivRef}
>
<input
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}
/>
{inputNode}
{suffixNode}
{clearNode}
</div>

View File

@ -215,12 +215,20 @@ function PickerPanel<DateType>() {
// 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;
},

View File

@ -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<DateType>(
values: RangeValue<DateType>,
@ -105,8 +106,11 @@ export type RangePickerSharedProps<DateType> = {
onPanelChange?: (values: RangeValue<DateType>, modes: [PanelMode, PanelMode]) => void;
onFocus?: FocusEventHandler;
onBlur?: FocusEventHandler;
onMousedown?: MouseEventHandler;
onMouseup?: MouseEventHandler;
onMouseenter?: MouseEventHandler;
onMouseleave?: MouseEventHandler;
onClick?: MouseEventHandler;
onOk?: (dates: RangeValue<DateType>) => void;
direction?: 'ltr' | 'rtl';
autocomplete?: string;
@ -216,8 +220,11 @@ function RangerPicker<DateType>() {
'onCalendarChange',
'onFocus',
'onBlur',
'onMousedown',
'onMouseup',
'onMouseenter',
'onMouseleave',
'onClick',
'onOk',
'onKeydown',
'components',
@ -241,6 +248,12 @@ function RangerPicker<DateType>() {
const separatorRef = ref<HTMLDivElement>(null);
const startInputRef = ref<HTMLInputElement>(null);
const endInputRef = ref<HTMLInputElement>(null);
const arrowRef = ref<HTMLDivElement>(null);
// ============================ Warning ============================
if (process.env.NODE_ENV !== 'production') {
legacyPropsWarning(props);
}
// ============================= Misc ==============================
const formatList = computed(() =>
@ -604,7 +617,7 @@ function RangerPicker<DateType>() {
},
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<DateType>() {
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<DateType>() {
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<DateType>() {
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<DateType>() {
? getValue(selectedValue.value, 1)
: getValue(selectedValue.value, 0)
}
defaultPickerValue={undefined}
/>
</RangeContextProvider>
);
@ -938,6 +960,7 @@ function RangerPicker<DateType>() {
renderExtraFooter,
onMouseenter,
onMouseleave,
onMouseup,
onOk,
components,
direction,
@ -954,7 +977,14 @@ function RangerPicker<DateType>() {
// 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<DateType>() {
class={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)}
style={{ minWidth: `${popupMinWidth.value}px` }}
>
<div class={`${prefixCls}-range-arrow`} style={arrowPositionStyle} />
<div ref={arrowRef} class={`${prefixCls}-range-arrow`} style={arrowPositionStyle} />
{renderPanels()}
</div>
);
@ -1157,6 +1187,7 @@ function RangerPicker<DateType>() {
onMouseenter={onMouseenter}
onMouseleave={onMouseleave}
onMousedown={onPickerMousedown}
onMouseup={onMouseup}
{...getDataOrAriaProps(props)}
>
<div

View File

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

View File

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

View File

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

View File

@ -19,7 +19,16 @@ export type SharedTimeProps<DateType> = {
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<DateType> = {
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.`,
);
}
}