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
pull/4499/head
tangjinzhou 2021-07-19 23:25:36 +08:00
parent a501b592a2
commit 16fc2a10a9
44 changed files with 1187 additions and 1341 deletions

View File

@ -0,0 +1,3 @@
export type FocusEventHandler = (e: FocusEvent) => void;
export type MouseEventHandler = (e: MouseEvent) => void;
export type KeyboardEventHandler = (e: KeyboardEvent) => void;

View File

@ -1,9 +1,9 @@
import type { Ref } from 'vue';
import type { Ref, WatchSource } from 'vue';
import { ref, watch } from 'vue';
export default function useMemo<T>(
getValue: () => T,
condition: any[],
condition: (WatchSource<unknown> | object)[],
shouldUpdate?: (prev: any[], next: any[]) => boolean,
) {
const cacheRef: Ref<T> = ref(getValue() as any);

View File

@ -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<T, R = Ref<T>>(
defaultStateValue: T | (() => T),
option?: {
defaultValue?: T | (() => T);
value?: Ref<T> | Ref<UnwrapRef<T>>;
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<T>;
const mergedValue = ref(initValue) as Ref<T>;
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];
}

View File

@ -0,0 +1,17 @@
import type { Ref } from 'vue';
import { ref } from 'vue';
export default function useState<T, R = Ref<T>>(
defaultStateValue: T | (() => T),
): [R, (val: T) => void] {
const initValue: T =
typeof defaultStateValue === 'function' ? (defaultStateValue as any)() : defaultStateValue;
const innerValue = ref(initValue) as Ref<T>;
function triggerChange(newValue: T) {
innerValue.value = newValue;
}
return [innerValue as unknown as R, triggerChange];
}

View File

@ -9,22 +9,21 @@ export type ContextOperationRefProps = {
export type PanelContextProps = {
operationRef?: Ref<ContextOperationRefProps | null>;
/** Only work with time panel */
hideHeader?: boolean;
hideHeader?: Ref<boolean>;
panelRef?: Ref<HTMLDivElement>;
hidePrevBtn?: boolean;
hideNextBtn?: boolean;
hidePrevBtn?: Ref<boolean>;
hideNextBtn?: Ref<boolean>;
onDateMouseEnter?: (date: any) => void;
onDateMouseLeave?: (date: any) => void;
onSelect?: OnSelect<any>;
hideRanges?: boolean;
open?: boolean;
mode?: PanelMode;
hideRanges?: Ref<boolean>;
open?: Ref<boolean>;
mode?: Ref<PanelMode>;
/** Only used for TimePicker and this is a deprecated prop */
defaultOpenValue?: any;
defaultOpenValue?: Ref<any>;
};
const PanelContextKey: InjectionKey<PanelContextProps> = Symbol('PanelContextProps');
export const useProvidePanel = (props: PanelContextProps) => {

View File

@ -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<DateType> = {
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<DateType> = {
format?: string | CustomFormat<DateType> | (string | CustomFormat<DateType>)[];
// 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<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseUp?: React.MouseEventHandler<HTMLDivElement>;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onClick?: React.MouseEventHandler<HTMLDivElement>;
onContextMenu?: React.MouseEventHandler<HTMLDivElement>;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>, 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<PickerRefConfig>;
pickerRef?: Ref<PickerRefConfig>;
// WAI-ARIA
role?: string;
name?: string;
autoComplete?: string;
autocomplete?: string;
direction?: 'ltr' | 'rtl';
} & React.AriaAttributes;
} & HtmlHTMLAttributes;
type OmitPanelProps<Props> = Omit<
Props,
@ -127,435 +138,224 @@ type MergedPickerProps<DateType> = {
picker?: PickerMode;
} & OmitType<DateType>;
function InnerPicker<DateType>(props: PickerProps<DateType>) {
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<DateType>;
function Picker<DateType>() {
return defineComponent<MergedPickerProps<DateType>>({
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<HTMLInputElement>(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<HTMLDivElement>(null);
const inputDivRef = React.useRef<HTMLDivElement>(null);
// Real value
const [mergedValue, setInnerValue] = useMergedState(null, {
value,
defaultValue,
});
// Selected value
const [selectedValue, setSelectedValue] = React.useState<DateType | null>(mergedValue);
// Operation ref
const operationRef: React.MutableRefObject<ContextOperationRefProps | null> =
React.useRef<ContextOperationRefProps>(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<DateType>(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<DateType>;
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<ContextOperationRefProps>(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<HTMLElement>) => {
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<HTMLDivElement> = (...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<MergedPickerProps<DateType>, '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 = (
<PickerPanel<DateType>
{...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 = (
<div
class={`${prefixCls}-panel-container`}
onMousedown={(e) => {
e.preventDefault();
}}
>
{panelNode}
</div>
);
if (inputRef.value) {
inputRef.value.focus();
triggerOpen(true);
}
};
let suffixNode: React.ReactNode;
if (suffixIcon) {
suffixNode = <span class={`${prefixCls}-suffix`}>{suffixIcon}</span>;
}
let clearNode: React.ReactNode;
if (allowClear && mergedValue && !disabled) {
clearNode = (
<span
onMousedown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseup={(e) => {
e.preventDefault();
e.stopPropagation();
triggerChange(null);
triggerOpen(false);
}}
class={`${prefixCls}-clear`}
role="button"
>
{clearIcon || <span class={`${prefixCls}-clear-btn`} />}
</span>
);
}
// ============================ 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 (
<PanelContext.Provider
value={{
operationRef,
hideHeader: picker === 'time',
panelRef: panelDivRef,
onSelect: onContextSelect,
// ============================= Input =============================
const [inputProps, { focused, typing }] = usePickerInput({
blurToCancel: needConfirmButton,
open: mergedOpen,
defaultOpenValue,
onDateMouseEnter: onEnter,
onDateMouseLeave: onLeave,
}}
>
<PickerTrigger
visible={mergedOpen}
popupElement={panel}
popupStyle={popupStyle}
prefixCls={prefixCls}
dropdownClassName={dropdownClassName}
dropdownAlign={dropdownAlign}
getPopupContainer={getPopupContainer}
transitionName={transitionName}
popupPlacement={popupPlacement}
direction={direction}
>
<div
class={classNames(prefixCls, className, {
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-focused`]: focused,
[`${prefixCls}-rtl`]: direction === 'rtl',
})}
style={style}
onMousedown={onMouseDown}
onMouseup={onInternalMouseUp}
onMouseenter={onMouseEnter}
onMouseleave={onMouseLeave}
onContextmenu={onContextMenu}
onClick={onClick}
>
<div
class={classNames(`${prefixCls}-input`, {
[`${prefixCls}-input-placeholder`]: !!hoverValue,
})}
ref={inputDivRef}
>
<input
id={id}
tabIndex={tabIndex}
disabled={disabled}
readonly={inputReadOnly || typeof formatList[0] === 'function' || !typing}
value={hoverValue || text}
onChange={(e) => {
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}
</div>
</div>
</PickerTrigger>
</PanelContext.Provider>
);
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<DateType> extends React.Component<PickerProps<DateType>> {
pickerRef = React.createRef<PickerRefConfig>();
focus = () => {
if (this.pickerRef.current) {
this.pickerRef.current.focus();
}
};
blur = () => {
if (this.pickerRef.current) {
this.pickerRef.current.blur();
}
};
render() {
return (
<InnerPicker<DateType>
{...this.props}
pickerRef={this.pickerRef as React.MutableRefObject<PickerRefConfig>}
/>
);
}
}
export default Picker;
export default Picker();

View File

@ -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<DateType> = {
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<DateType> = {
// Render
dateRender?: DateRender<DateType>;
monthCellRender?: MonthCellRender<DateType>;
renderExtraFooter?: (mode: PanelMode) => React.ReactNode;
renderExtraFooter?: (mode: PanelMode) => VueNode;
// Event
onSelect?: (value: DateType) => void;
onChange?: (value: DateType) => void;
onPanelChange?: OnPanelChange<DateType>;
onMouseDown?: React.MouseEventHandler<HTMLDivElement>;
onMouseDown?: (e: MouseEvent) => void;
onOk?: (date: DateType) => void;
direction?: 'ltr' | 'rtl';
@ -101,7 +101,8 @@ export type PickerPanelDateProps<DateType> = {
export type PickerPanelTimeProps<DateType> = {
picker: 'time';
} & PickerPanelSharedProps<DateType> & SharedTimeProps<DateType>;
} & PickerPanelSharedProps<DateType> &
SharedTimeProps<DateType>;
export type PickerPanelProps<DateType> =
| PickerPanelBaseProps<DateType>
@ -116,454 +117,486 @@ type MergedPickerPanelProps<DateType> = {
picker?: PickerMode;
} & OmitType<DateType>;
function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
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<DateType>;
function PickerPanel<DateType>() {
return defineComponent<MergedPickerPanelProps<DateType>>({
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<PanelRefProps>({});
// 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<DateType | null, DateType>(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<PanelRefProps>({});
// Value
const [mergedValue, setInnerValue] = useMergedState<DateType | null>(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<DateType | null>(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<PanelMode>(() => 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<HTMLElement>) => {
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<HTMLElement> = (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<DateType>),
operationRef: panelRef,
prefixCls,
viewDate,
value: mergedValue,
onViewDateChange: setViewDate,
sourceMode,
onPanelChange: onInternalPanelChange,
disabledDate,
};
delete pickerProps.onChange;
delete pickerProps.onSelect;
switch (mergedMode) {
case 'decade':
panelNode = (
<DecadePanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'year':
panelNode = (
<YearPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'month':
panelNode = (
<MonthPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'quarter':
panelNode = (
<QuarterPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'week':
panelNode = (
<WeekPanel
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'time':
delete pickerProps.showTime;
panelNode = (
<TimePanel<DateType>
{...pickerProps}
{...(typeof showTime === 'object' ? showTime : null)}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
default:
if (showTime) {
panelNode = (
<DatetimePanel
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
} else {
panelNode = (
<DatePanel<DateType>
{...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 = (
<a
class={classNames(todayCls, disabled && `${todayCls}-disabled`)}
aria-disabled={disabled}
onClick={() => {
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}
</a>
);
}
return panelRef.value.onKeyDown(e);
}
return (
<PanelContext.Provider
value={{
/* istanbul ignore next */
/* eslint-disable no-lone-blocks */
{
warning(
false,
'Panel not correct handle keyDown event. Please help to fire issue about this.',
);
return false;
}
/* eslint-enable no-lone-blocks */
};
const onInternalBlur = (e: FocusEvent) => {
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',
}}
>
<div
tabindex={tabIndex}
class={classNames(`${prefixCls}-panel`, className, {
[`${prefixCls}-panel-has-range`]: rangedValue && rangedValue[0] && rangedValue[1],
[`${prefixCls}-panel-has-range-hover`]:
hoverRangedValue && hoverRangedValue[0] && hoverRangedValue[1],
[`${prefixCls}-panel-rtl`]: direction === 'rtl',
})}
style={style}
onKeydown={onInternalKeyDown}
onBlur={onInternalBlur}
onMousedown={onMouseDown}
ref={panelDivRef}
>
{panelNode}
{extraFooter || rangesNode || todayNode ? (
<div class={`${prefixCls}-footer`}>
{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<DateType>),
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 = (
<DecadePanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'year':
panelNode = (
<YearPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'month':
panelNode = (
<MonthPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'quarter':
panelNode = (
<QuarterPanel<DateType>
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'week':
panelNode = (
<WeekPanel
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
case 'time':
delete pickerProps.showTime;
panelNode = (
<TimePanel<DateType>
{...pickerProps}
{...(typeof showTime === 'object' ? showTime : null)}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
break;
default:
if (showTime) {
panelNode = (
<DatetimePanel
{...pickerProps}
onSelect={(date, type) => {
setViewDate(date);
triggerSelect(date, type);
}}
/>
);
} else {
panelNode = (
<DatePanel<DateType>
{...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 = (
<a
class={classNames(todayCls, disabled && `${todayCls}-disabled`)}
aria-disabled={disabled}
onClick={() => {
if (!disabled) {
triggerSelect(now, 'mouse', true);
}
}}
>
{locale.today}
</a>
);
}
return (
<div
tabindex={tabindex}
class={classNames(classString.value, attrs.class)}
style={attrs.style}
onKeydown={onInternalKeyDown}
onBlur={onInternalBlur}
onMousedown={onMouseDown}
ref={panelDivRef}
>
{panelNode}
{extraFooter || rangesNode || todayNode ? (
<div class={`${prefixCls}-footer`}>
{extraFooter}
{rangesNode}
{todayNode}
</div>
) : null}
</div>
) : null}
</div>
</PanelContext.Provider>
);
);
};
},
});
}
export default PickerPanel;
/* eslint-enable */
export default PickerPanel();

View File

@ -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 = () => {

View File

@ -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<any>, NullableDateType<any>] | null;
hoverRangedValue?: RangeValue<any>;
inRange?: boolean;
panelPosition?: 'left' | 'right' | false;
rangedValue?: Ref<[NullableDateType<any>, NullableDateType<any>] | null>;
hoverRangedValue?: Ref<RangeValue<any>>;
inRange?: Ref<boolean>;
panelPosition?: Ref<'left' | 'right' | false>;
};
const RangeContextKey: InjectionKey<RangeContextProps> = Symbol('RangeContextProps');
export const useProvideRange = (props: RangeContextProps) => {
@ -23,5 +22,4 @@ export const useInjectRange = () => {
return inject(RangeContextKey);
};
export default RangeContextKey;

View File

@ -16,10 +16,7 @@ export default function useCellClassName<DateType>({
}: {
cellPrefixCls: string;
generateConfig: GenerateConfig<DateType>;
isSameCell: (
current: NullableDateType<DateType>,
target: NullableDateType<DateType>,
) => boolean;
isSameCell: (current: NullableDateType<DateType>, target: NullableDateType<DateType>) => boolean;
offsetCell: (date: DateType, offset: number) => DateType;
isInView: (date: DateType) => boolean;
rangedValue?: RangeValue<DateType>;
@ -37,12 +34,7 @@ export default function useCellClassName<DateType>({
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<DateType>({
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<DateType>({
),
[`${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) ||

View File

@ -9,7 +9,7 @@ export default function useHoverValue<DateType>(
const [value, internalSetValue] = useState<DateType>(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<DateType>(
setValue(date);
}
function onLeave(immediately: boolean = false) {
function onLeave(immediately = false) {
setValue(null, immediately);
}

View File

@ -0,0 +1,8 @@
import type { HTMLAttributes } from 'vue';
import { useAttrs } from 'vue';
// 仅用在函数式组件中,不用考虑响应式问题
export default function useMergeProps<T>(props: T) {
const attrs: HTMLAttributes = useAttrs();
return { ...props, ...attrs };
}

View File

@ -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<boolean>;
value: Ref<string>;
isClickOutside: (clickElement: EventTarget | null) => boolean;
triggerOpen: (open: boolean) => void;
forwardKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => boolean;
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>, preventDefault: () => void) => void;
blurToCancel?: boolean;
forwardKeyDown: (e: KeyboardEvent) => boolean;
onKeyDown: (e: KeyboardEvent, preventDefault: () => void) => void;
blurToCancel?: ComputedRef<boolean>;
onSubmit: () => void | boolean;
onCancel: () => void;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
}): [React.DOMAttributes<HTMLInputElement>, { focused: boolean; typing: boolean }] {
const [typing, setTyping] = useState(false);
const [focused, setFocused] = useState(false);
onFocus?: FocusEventHandler;
onBlur?: FocusEventHandler;
}): [ComputedRef<HTMLAttributes>, { focused: Ref<boolean>; typing: Ref<boolean> }] {
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<boolean>(false);
const preventBlurRef = ref<boolean>(false);
const valueChangedRef = useRef<boolean>(false);
const valueChangedRef = ref<boolean>(false);
const preventDefaultRef = useRef<boolean>(false);
const preventDefaultRef = ref<boolean>(false);
const inputProps: React.DOMAttributes<HTMLInputElement> = {
onMouseDown: () => {
setTyping(true);
const inputProps = computed<HTMLAttributes>(() => ({
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 }];
}

View File

@ -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<string[]>;
onTextChange: (text: string) => void;
}): [string, (text: string) => void, () => void] {
const [text, setInnerText] = React.useState('');
const valueTextsRef = React.useRef<string[]>([]);
valueTextsRef.current = valueTexts;
}): [Ref<string>, (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];
}

View File

@ -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<DateType> = {
formatList: (string | CustomFormat<DateType>)[];
generateConfig: GenerateConfig<DateType>;
locale: Locale;
formatList: ComputedRef<(string | CustomFormat<DateType>)[]>;
generateConfig: Ref<GenerateConfig<DateType>>;
locale: Ref<Locale>;
};
export default function useValueTexts<DateType>(
value: DateType | null,
value: Ref<DateType | null>,
{ formatList, generateConfig, locale }: ValueTextConfig<DateType>,
) {
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<DateType>(
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]),
);
}

View File

@ -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<PanelMode, 'datetime' | 'decade'>;
export type PanelRefProps = {
onKeyDown?: (e: KeyboardEvent) => boolean;
onBlur?: (e: FocusEvent)=> void;
onBlur?: (e: FocusEvent) => void;
onClose?: () => void;
};
@ -74,7 +75,7 @@ export type PanelSharedProps<DateType> = {
// * Thus, move ref into operationRef.
// * This is little hack which should refactor after typescript support.
// */
// operationRef: React.MutableRefObject<PanelRefProps>;
operationRef: Ref<PanelRefProps>;
onSelect: OnSelect<DateType>;
onViewDateChange: (value: DateType) => void;

View File

@ -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<DateType> = (currentDate: DateType, today: DateType) => VueNode;
@ -34,19 +34,12 @@ export type DateBodyProps<DateType> = {
onSelect: (value: DateType) => void;
} & DateBodyPassProps<DateType>;
function DateBody<DateType>(props: DateBodyProps<DateType>) {
const {
prefixCls,
generateConfig,
prefixColumn,
locale,
rowCount,
viewDate,
value,
dateRender,
} = props;
function DateBody<DateType>(_props: DateBodyProps<DateType>) {
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<DateType>(props: DateBodyProps<DateType>) {
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<DateType>(props: DateBodyProps<DateType>) {
);
}
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;

View File

@ -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<DateType> = {
prefixCls: string;
@ -21,7 +21,8 @@ export type DateHeaderProps<DateType> = {
onMonthClick: () => void;
};
function DateHeader<DateType>(props: DateHeaderProps<DateType>) {
function DateHeader<DateType>(_props: DateHeaderProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
generateConfig,
@ -35,8 +36,8 @@ function DateHeader<DateType>(props: DateHeaderProps<DateType>) {
onMonthClick,
} = props;
const { hideHeader } = useInjectPanel()
if (hideHeader) {
const { hideHeader } = useInjectPanel();
if (hideHeader.value) {
return null;
}
@ -100,6 +101,6 @@ function DateHeader<DateType>(props: DateHeaderProps<DateType>) {
);
}
DateHeader.displayName = 'DateHeader'
DateHeader.displayName = 'DateHeader';
DateHeader.inheritAttrs = false;
export default DateHeader;

View File

@ -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<DateType> = {
// Used for week panel
panelName?: string;
keyboardConfig?: KeyboardConfig;
} & PanelSharedProps<DateType> & DateBodyPassProps<DateType>;
} & PanelSharedProps<DateType> &
DateBodyPassProps<DateType>;
function DatePanel<DateType>(props: DatePanelProps<DateType>) {
function DatePanel<DateType>(_props: DatePanelProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
panelName = 'date',
keyboardConfig,
active,
operationRef,
generateConfig,
value,
viewDate,
@ -34,10 +37,9 @@ function DatePanel<DateType>(props: DatePanelProps<DateType>) {
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<DateType>(props: DatePanelProps<DateType>) {
);
}
DatePanel.displayName ='DatePanel'
DatePanel.displayName = 'DatePanel';
DatePanel.inheritAttrs = false;
export default DatePanel;

View File

@ -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<DateType> = {
disabledTime?: DisabledTime<DateType>;
showTime?: boolean | SharedTimeProps<DateType>;
defaultValue?: DateType;
} & Omit<
DatePanelProps<DateType>,
'disabledHours' | 'disabledMinutes' | 'disabledSeconds'
>;
} & Omit<DatePanelProps<DateType>, 'disabledHours' | 'disabledMinutes' | 'disabledSeconds'>;
const ACTIVE_PANEL = tuple('date', 'time');
type ActivePanelType = typeof ACTIVE_PANEL[number];
function DatetimePanel<DateType>(props: DatetimePanelProps<DateType>) {
function DatetimePanel<DateType>(_props: DatetimePanelProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
operationRef,
@ -34,9 +32,7 @@ function DatetimePanel<DateType>(props: DatetimePanelProps<DateType>) {
onSelect,
} = props;
const panelPrefixCls = `${prefixCls}-datetime-panel`;
const activePanel = ref<ActivePanelType | null>(
null,
);
const activePanel = ref<ActivePanelType | null>(null);
const dateOperationRef = ref<PanelRefProps>({});
const timeOperationRef = ref<PanelRefProps>({});
@ -57,12 +53,12 @@ function DatetimePanel<DateType>(props: DatetimePanelProps<DateType>) {
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<DateType>(props: DatetimePanelProps<DateType>) {
// 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<DateType>(props: DatetimePanelProps<DateType>) {
}
// 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<DateType>(props: DatetimePanelProps<DateType>) {
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<DateType>(props: DatetimePanelProps<DateType>) {
setTime(
generateConfig,
date,
showTime && typeof showTime === 'object'
? showTime.defaultValue
: null,
showTime && typeof showTime === 'object' ? showTime.defaultValue : null,
),
'date',
);
@ -179,8 +159,7 @@ function DatetimePanel<DateType>(props: DatetimePanelProps<DateType>) {
);
}
DatetimePanel.displayName ='DatetimePanel'
DatetimePanel.displayName = 'DatetimePanel';
DatetimePanel.inheritAttrs = false;
export default DatetimePanel;

View File

@ -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<DateType> = {
onSelect: (value: DateType) => void;
};
function DecadeBody<DateType>(props: YearBodyProps<DateType>) {
function DecadeBody<DateType>(_props: YearBodyProps<DateType>) {
const props = useMergeProps(_props);
const DECADE_UNIT_DIFF_DES = DECADE_UNIT_DIFF - 1;
const { prefixCls, viewDate, generateConfig } = props;
@ -61,8 +62,7 @@ function DecadeBody<DateType>(props: YearBodyProps<DateType>) {
);
}
DecadeBody.displayName ='DecadeBody'
DecadeBody.displayName = 'DecadeBody';
DecadeBody.inheritAttrs = false;
export default DecadeBody;

View File

@ -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<DateType> = {
prefixCls: string;
@ -13,15 +13,10 @@ export type YearHeaderProps<DateType> = {
onNextDecades: () => void;
};
function DecadeHeader<DateType>(props: YearHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
viewDate,
onPrevDecades,
onNextDecades,
} = props;
const { hideHeader } =useInjectPanel()
function DecadeHeader<DateType>(_props: YearHeaderProps<DateType>) {
const props = useMergeProps(_props);
const { prefixCls, generateConfig, viewDate, onPrevDecades, onNextDecades } = props;
const { hideHeader } = useInjectPanel();
if (hideHeader) {
return null;
}
@ -29,8 +24,7 @@ function DecadeHeader<DateType>(props: YearHeaderProps<DateType>) {
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<DateType>(props: YearHeaderProps<DateType>) {
);
}
DecadeHeader.displayName ='DecadeHeader'
DecadeHeader.displayName = 'DecadeHeader';
DecadeHeader.inheritAttrs = false;
export default DecadeHeader;

View File

@ -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<DateType> = PanelSharedProps<DateType>;
export const DECADE_UNIT_DIFF = 10;
export const DECADE_DISTANCE_COUNT = DECADE_UNIT_DIFF * 10;
function DecadePanel<DateType>(props: DecadePanelProps<DateType>) {
function DecadePanel<DateType>(_props: DecadePanelProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
onViewDateChange,
@ -23,27 +24,18 @@ function DecadePanel<DateType>(props: DecadePanelProps<DateType>) {
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<DateType>(props: DecadePanelProps<DateType>) {
// ==================== 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<DateType>(props: DecadePanelProps<DateType>) {
onDecadesChange(1);
}}
/>
<DecadeBody
{...props}
prefixCls={prefixCls}
onSelect={onInternalSelect}
/>
<DecadeBody {...props} prefixCls={prefixCls} onSelect={onInternalSelect} />
</div>
);
}
DecadePanel.displayName ='DecadePanel'
DecadePanel.displayName = 'DecadePanel';
DecadePanel.inheritAttrs = false;
export default DecadePanel;

View File

@ -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}
</button>
@ -62,7 +62,7 @@ function Header(
onClick={onPrev}
tabindex={-1}
class={`${prefixCls}-prev-btn`}
style={hidePrevBtn ? HIDDEN_STYLE : {}}
style={hidePrevBtn.value ? HIDDEN_STYLE : {}}
>
{prevIcon}
</button>
@ -74,7 +74,7 @@ function Header(
onClick={onNext}
tabindex={-1}
class={`${prefixCls}-next-btn`}
style={hideNextBtn ? HIDDEN_STYLE : {}}
style={hideNextBtn.value ? HIDDEN_STYLE : {}}
>
{nextIcon}
</button>
@ -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}
</button>

View File

@ -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<DateType> = {
onSelect: (value: DateType) => void;
};
function MonthBody<DateType>(props: MonthBodyProps<DateType>) {
function MonthBody<DateType>(_props: MonthBodyProps<DateType>) {
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<DateType>(props: MonthBodyProps<DateType>) {
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<DateType>(props: MonthBodyProps<DateType>) {
);
}
MonthBody.displayName ='MonthBody'
MonthBody.displayName = 'MonthBody';
MonthBody.inheritAttrs = false;
export default MonthBody;

View File

@ -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<DateType> = {
prefixCls: string;
@ -16,18 +16,12 @@ export type MonthHeaderProps<DateType> = {
onYearClick: () => void;
};
function MonthHeader<DateType>(props: MonthHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
locale,
viewDate,
onNextYear,
onPrevYear,
onYearClick,
} = props;
const { hideHeader } = useInjectPanel()
if (hideHeader) {
function MonthHeader<DateType>(_props: MonthHeaderProps<DateType>) {
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<DateType>(props: MonthHeaderProps<DateType>) {
);
}
MonthHeader.displayName ='MonthHeader'
MonthHeader.displayName = 'MonthHeader';
MonthHeader.inheritAttrs = false;
export default MonthHeader;

View File

@ -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<DateType> = {
monthCellContentRender?: MonthCellRender<DateType>;
} & PanelSharedProps<DateType>;
function MonthPanel<DateType>(props: MonthPanelProps<DateType>) {
function MonthPanel<DateType>(_props: MonthPanelProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
operationRef,
@ -24,8 +25,8 @@ function MonthPanel<DateType>(props: MonthPanelProps<DateType>) {
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<DateType>(props: MonthPanelProps<DateType>) {
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<DateType>(props: MonthPanelProps<DateType>) {
);
}
MonthPanel.displayName ='MonthPanel'
MonthPanel.displayName = 'MonthPanel';
MonthPanel.inheritAttrs = false;
export default MonthPanel;

View File

@ -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<DateType> = {
prefixCls: string;
@ -30,25 +30,26 @@ export type PanelBodyProps<DateType> = {
rowClassName?: (date: DateType) => string;
};
function PanelBody<DateType>({
prefixCls,
disabledDate,
onSelect,
picker,
rowNum,
colNum,
prefixColumn,
rowClassName,
baseDate,
getCellClassName,
getCellText,
getCellNode,
getCellDate,
generateConfig,
titleCell,
headerCells,
}: PanelBodyProps<DateType>) {
const { onDateMouseEnter, onDateMouseLeave, mode } = useInjectPanel()
function PanelBody<DateType>(_props: PanelBodyProps<DateType>) {
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<DateType>({
const currentDate = getCellDate(baseDate, offset);
const disabled = getCellDateDisabled({
cellDate: currentDate,
mode,
mode: mode.value,
disabledDate,
generateConfig,
});

View File

@ -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<DateType> = {
onSelect: (value: DateType) => void;
};
function QuarterBody<DateType>(props: QuarterBodyProps<DateType>) {
function QuarterBody<DateType>(_props: QuarterBodyProps<DateType>) {
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<DateType>(props: QuarterBodyProps<DateType>) {
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<DateType>(props: QuarterBodyProps<DateType>) {
);
}
QuarterBody.displayName ='QuarterBody'
QuarterBody.displayName = 'QuarterBody';
QuarterBody.inheritAttrs = false;
export default QuarterBody;

View File

@ -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<DateType> = {
prefixCls: string;
@ -16,18 +16,12 @@ export type QuarterHeaderProps<DateType> = {
onYearClick: () => void;
};
function QuarterHeader<DateType>(props: QuarterHeaderProps<DateType>) {
const {
prefixCls,
generateConfig,
locale,
viewDate,
onNextYear,
onPrevYear,
onYearClick,
} = props;
const { hideHeader } =useInjectPanel()
if (hideHeader) {
function QuarterHeader<DateType>(_props: QuarterHeaderProps<DateType>) {
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<DateType>(props: QuarterHeaderProps<DateType>) {
);
}
QuarterHeader.displayName ='QuarterHeader'
QuarterHeader.displayName = 'QuarterHeader';
QuarterHeader.inheritAttrs = false;
export default QuarterHeader;

View File

@ -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<DateType> = {} & PanelSharedProps<DateType>;
function QuarterPanel<DateType>(props: QuarterPanelProps<DateType>) {
function QuarterPanel<DateType>(_props: QuarterPanelProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
operationRef,
@ -21,8 +22,8 @@ function QuarterPanel<DateType>(props: QuarterPanelProps<DateType>) {
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<DateType>(props: QuarterPanelProps<DateType>) {
);
}
QuarterPanel.displayName ='QuarterPanel'
QuarterPanel.displayName = 'QuarterPanel';
QuarterPanel.inheritAttrs = false;
export default QuarterPanel;

View File

@ -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<DateType> = {
operationRef: Ref<BodyOperationRef | undefined>;
} & SharedTimeProps<DateType>;
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, <TimeUnitColumn key="minute" />, minute.value, minutes.value, num => {
onSelect(setTime(isPM.value, hour.value, num, second.value), 'mouse');
});
addColumnNode(
showMinute,
<TimeUnitColumn key="minute" />,
minute.value,
minutes.value,
num => {
onSelect(setTime(isPM.value, hour.value, num, second.value), 'mouse');
},
);
// Second
addColumnNode(showSecond, <TimeUnitColumn key="second" />, second.value, seconds.value, num => {
onSelect(setTime(isPM.value, hour.value, minute.value, num), 'mouse');
});
addColumnNode(
showSecond,
<TimeUnitColumn key="second" />,
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 <div class={contentPrefixCls}>{columns.map(({ node }) => node)}</div>;
}
}
})
};
},
});
export default TimeBody;

View File

@ -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<DateType> = {
prefixCls: string;
@ -13,9 +13,10 @@ export type TimeHeaderProps<DateType> = {
format: string;
};
function TimeHeader<DateType>(props: TimeHeaderProps<DateType>) {
const { hideHeader } = useInjectPanel()
if (hideHeader) {
function TimeHeader<DateType>(_props: TimeHeaderProps<DateType>) {
const props = useMergeProps(_props);
const { hideHeader } = useInjectPanel();
if (hideHeader.value) {
return null;
}
@ -35,8 +36,7 @@ function TimeHeader<DateType>(props: TimeHeaderProps<DateType>) {
);
}
TimeHeader.displayName ='TimeHeader'
TimeHeader.displayName = 'TimeHeader';
TimeHeader.inheritAttrs = false;
export default TimeHeader;

View File

@ -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, () => {

View File

@ -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<DateType> = {
format?: string;
@ -24,12 +24,14 @@ export type SharedTimeProps<DateType> = {
export type TimePanelProps<DateType> = {
format?: string;
active?: boolean;
} & PanelSharedProps<DateType> & SharedTimeProps<DateType>;
} & PanelSharedProps<DateType> &
SharedTimeProps<DateType>;
const countBoolean = (boolList: (boolean | undefined)[]) =>
boolList.filter(bool => bool !== false).length;
function TimePanel<DateType>(props: TimePanelProps<DateType>) {
function TimePanel<DateType>(_props: TimePanelProps<DateType>) {
const props = useMergeProps(_props);
const {
generateConfig,
format = 'HH:mm:ss',
@ -50,27 +52,27 @@ function TimePanel<DateType>(props: TimePanelProps<DateType>) {
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<DateType>(props: TimePanelProps<DateType>) {
);
}
TimePanel.displayName ='TimePanel'
TimePanel.displayName = 'TimePanel';
TimePanel.inheritAttrs = false;
export default TimePanel;

View File

@ -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<DateType> = PanelSharedProps<DateType>;
function WeekPanel<DateType>(props: WeekPanelProps<DateType>) {
function WeekPanel<DateType>(_props: WeekPanelProps<DateType>) {
const props = useMergeProps(_props);
const { prefixCls, generateConfig, locale, value } = props;
// Render additional column
const cellPrefixCls = `${prefixCls}-cell`;
const prefixColumn = (date: DateType) => (
<td
key="week"
class={classNames(cellPrefixCls, `${cellPrefixCls}-week`)}
>
<td key="week" class={classNames(cellPrefixCls, `${cellPrefixCls}-week`)}>
{generateConfig.locale.getWeek(locale.locale, date)}
</td>
);
@ -24,12 +22,7 @@ function WeekPanel<DateType>(props: WeekPanelProps<DateType>) {
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<DateType>(props: WeekPanelProps<DateType>) {
);
}
WeekPanel.displayName = 'WeekPanel';
WeekPanel.inheritAttrs = false;

View File

@ -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<DateType> = {
onSelect: (value: DateType) => void;
};
function YearBody<DateType>(props: YearBodyProps<DateType>) {
function YearBody<DateType>(_props: YearBodyProps<DateType>) {
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<DateType>(props: YearBodyProps<DateType>) {
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<DateType>(props: YearBodyProps<DateType>) {
);
}
YearBody.displayName = 'YearBody';
YearBody.inheritAttrs = false;

View File

@ -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<DateType> = {
prefixCls: string;
@ -15,10 +15,11 @@ export type YearHeaderProps<DateType> = {
onDecadeClick: () => void;
};
function YearHeader<DateType>(props: YearHeaderProps<DateType>) {
function YearHeader<DateType>(_props: YearHeaderProps<DateType>) {
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<DateType>(props: YearHeaderProps<DateType>) {
);
}
YearHeader.displayName = 'YearHeader';
YearHeader.inheritAttrs = false;

View File

@ -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<DateType> = {
sourceMode: PanelMode;
@ -10,7 +10,8 @@ export type YearPanelProps<DateType> = {
export const YEAR_DECADE_COUNT = 10;
function YearPanel<DateType>(props: YearPanelProps<DateType>) {
function YearPanel<DateType>(_props: YearPanelProps<DateType>) {
const props = useMergeProps(_props);
const {
prefixCls,
operationRef,
@ -26,29 +27,20 @@ function YearPanel<DateType>(props: YearPanelProps<DateType>) {
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<DateType>(props: YearPanelProps<DateType>) {
);
}
YearPanel.displayName = 'YearPanel';
YearPanel.inheritAttrs = false;

View File

@ -1,4 +1,3 @@
import type { PanelMode } from '../interface';
export default function getExtraFooter(
@ -10,7 +9,5 @@ export default function getExtraFooter(
return null;
}
return (
<div class={`${prefixCls}-footer-extra`}>{renderExtraFooter(mode)}</div>
);
return <div class={`${prefixCls}-footer-extra`}>{renderExtraFooter(mode)}</div>;
}

View File

@ -1,4 +1,3 @@
import { VueNode } from '../../_util/type';
import type { Components, RangeList, Locale } from '../interface';

View File

@ -51,8 +51,8 @@ export function updateValues<T, R = [T | null, T | null] | null>(
typeof value === 'function' ? (value as UpdateValue<T | null>)(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;
}

View File

@ -24,22 +24,12 @@ export function setDateTime<DateType>(
}
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,

View File

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