refactor: date

pull/4499/head
tangjinzhou 2021-06-15 22:29:10 +08:00
parent a020c2f681
commit a8113d7c55
5 changed files with 265 additions and 237 deletions

View File

@ -381,7 +381,7 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
<PickerPanel<DateType>
{...panelProps}
generateConfig={generateConfig}
className={classNames({
class={classNames({
[`${prefixCls}-panel-focused`]: !typing,
})}
value={selectedValue}
@ -406,8 +406,8 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
const panel = (
<div
className={`${prefixCls}-panel-container`}
onMouseDown={(e) => {
class={`${prefixCls}-panel-container`}
onMousedown={(e) => {
e.preventDefault();
}}
>
@ -417,27 +417,27 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
let suffixNode: React.ReactNode;
if (suffixIcon) {
suffixNode = <span className={`${prefixCls}-suffix`}>{suffixIcon}</span>;
suffixNode = <span class={`${prefixCls}-suffix`}>{suffixIcon}</span>;
}
let clearNode: React.ReactNode;
if (allowClear && mergedValue && !disabled) {
clearNode = (
<span
onMouseDown={(e) => {
onMousedown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onMouseUp={(e) => {
onMouseup={(e) => {
e.preventDefault();
e.stopPropagation();
triggerChange(null);
triggerOpen(false);
}}
className={`${prefixCls}-clear`}
class={`${prefixCls}-clear`}
role="button"
>
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
{clearIcon || <span class={`${prefixCls}-clear-btn`} />}
</span>
);
}
@ -486,21 +486,21 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
direction={direction}
>
<div
className={classNames(prefixCls, className, {
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}
onMousedown={onMouseDown}
onMouseup={onInternalMouseUp}
onMouseenter={onMouseEnter}
onMouseleave={onMouseLeave}
onContextmenu={onContextMenu}
onClick={onClick}
>
<div
className={classNames(`${prefixCls}-input`, {
class={classNames(`${prefixCls}-input`, {
[`${prefixCls}-input-placeholder`]: !!hoverValue,
})}
ref={inputDivRef}
@ -509,19 +509,19 @@ function InnerPicker<DateType>(props: PickerProps<DateType>) {
id={id}
tabIndex={tabIndex}
disabled={disabled}
readOnly={inputReadOnly || typeof formatList[0] === 'function' || !typing}
readonly={inputReadOnly || typeof formatList[0] === 'function' || !typing}
value={hoverValue || text}
onChange={(e) => {
triggerTextChange(e.target.value);
}}
autoFocus={autoFocus}
autofocus={autoFocus}
placeholder={placeholder}
ref={inputRef}
title={text}
{...inputProps}
size={getInputSize(picker, formatList[0], generateConfig)}
{...getDataOrAriaProps(props)}
autoComplete={autoComplete}
autocomplete={autoComplete}
/>
{suffixNode}
{clearNode}

View File

@ -515,7 +515,7 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
const disabled = disabledDate && disabledDate(now);
todayNode = (
<a
className={classNames(todayCls, disabled && `${todayCls}-disabled`)}
class={classNames(todayCls, disabled && `${todayCls}-disabled`)}
aria-disabled={disabled}
onClick={() => {
if (!disabled) {
@ -539,22 +539,22 @@ function PickerPanel<DateType>(props: PickerPanelProps<DateType>) {
}}
>
<div
tabIndex={tabIndex}
className={classNames(`${prefixCls}-panel`, className, {
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}
onKeydown={onInternalKeyDown}
onBlur={onInternalBlur}
onMouseDown={onMouseDown}
onMousedown={onMouseDown}
ref={panelDivRef}
>
{panelNode}
{extraFooter || rangesNode || todayNode ? (
<div className={`${prefixCls}-footer`}>
<div class={`${prefixCls}-footer`}>
{extraFooter}
{rangesNode}
{todayNode}

View File

@ -1,7 +1,8 @@
import * as React from 'react';
import classNames from 'classnames';
import Trigger from 'rc-trigger';
import type { AlignType } from 'rc-trigger/lib/interface';
import { CSSProperties } from '@vue/runtime-dom';
import { AlignType } from '../vc-align/interface';
import Trigger from '../vc-trigger';
import classNames from '../_util/classNames';
import { VueNode } from '../_util/type';
const BUILT_IN_PLACEMENTS = {
bottomLeft: {
@ -43,9 +44,9 @@ type Placement = 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight';
export type PickerTriggerProps = {
prefixCls: string;
visible: boolean;
popupElement: React.ReactElement;
popupStyle?: React.CSSProperties;
children: React.ReactElement;
popupElement: VueNode;
popupStyle?: CSSProperties;
children: VueNode;
dropdownClassName?: string;
transitionName?: string;
getPopupContainer?: (node: HTMLElement) => HTMLElement;
@ -55,20 +56,22 @@ export type PickerTriggerProps = {
direction?: 'ltr' | 'rtl';
};
function PickerTrigger({
prefixCls,
popupElement,
popupStyle,
visible,
dropdownClassName,
dropdownAlign,
transitionName,
getPopupContainer,
children,
range,
popupPlacement,
direction,
}: PickerTriggerProps) {
function PickerTrigger(
{
prefixCls,
popupElement,
popupStyle,
visible,
dropdownClassName,
dropdownAlign,
transitionName,
getPopupContainer,
range,
popupPlacement,
direction,
}: PickerTriggerProps,
{ slots },
) {
const dropdownPrefixCls = `${prefixCls}-dropdown`;
const getPopupPlacement = () => {
@ -96,7 +99,7 @@ function PickerTrigger({
popupStyle={popupStyle}
getPopupContainer={getPopupContainer}
>
{children}
{slots.default?.()}
</Trigger>
);
}

View File

@ -811,7 +811,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
}
return false;
}}
className={classNames({
class={classNames({
[`${prefixCls}-panel-focused`]:
mergedActivePickerIndex === 0 ? !startTyping : !endTyping,
})}
@ -938,9 +938,9 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
let mergedNodes: React.ReactNode = (
<>
<div className={`${prefixCls}-panels`}>{panels}</div>
<div class={`${prefixCls}-panels`}>{panels}</div>
{(extraNode || rangesNode) && (
<div className={`${prefixCls}-footer`}>
<div class={`${prefixCls}-footer`}>
{extraNode}
{rangesNode}
</div>
@ -954,7 +954,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
return (
<div
className={`${prefixCls}-panel-container`}
class={`${prefixCls}-panel-container`}
style={{ marginLeft: panelLeft }}
ref={panelDivRef}
onMouseDown={e => {
@ -968,10 +968,10 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
const rangePanel = (
<div
className={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)}
class={classNames(`${prefixCls}-range-wrapper`, `${prefixCls}-${picker}-range-wrapper`)}
style={{ minWidth: popupMinWidth }}
>
<div className={`${prefixCls}-range-arrow`} style={arrowPositionStyle} />
<div class={`${prefixCls}-range-arrow`} style={arrowPositionStyle} />
{renderPanels()}
</div>
@ -980,7 +980,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
// ============================= Icons =============================
let suffixNode: React.ReactNode;
if (suffixIcon) {
suffixNode = <span className={`${prefixCls}-suffix`}>{suffixIcon}</span>;
suffixNode = <span class={`${prefixCls}-suffix`}>{suffixIcon}</span>;
}
let clearNode: React.ReactNode;
@ -1010,9 +1010,9 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
triggerChange(values, null);
triggerOpen(false, mergedActivePickerIndex);
}}
className={`${prefixCls}-clear`}
class={`${prefixCls}-clear`}
>
{clearIcon || <span className={`${prefixCls}-clear-btn`} />}
{clearIcon || <span class={`${prefixCls}-clear-btn`} />}
</span>
);
}
@ -1077,7 +1077,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
>
<div
ref={containerRef}
className={classNames(prefixCls, `${prefixCls}-range`, className, {
class={classNames(prefixCls, `${prefixCls}-range`, className, {
[`${prefixCls}-disabled`]: mergedDisabled[0] && mergedDisabled[1],
[`${prefixCls}-focused`]: mergedActivePickerIndex === 0 ? startFocused : endFocused,
[`${prefixCls}-rtl`]: direction === 'rtl',
@ -1090,7 +1090,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
{...getDataOrAriaProps(props)}
>
<div
className={classNames(`${prefixCls}-input`, {
class={classNames(`${prefixCls}-input`, {
[`${prefixCls}-input-active`]: mergedActivePickerIndex === 0,
[`${prefixCls}-input-placeholder`]: !!startHoverValue,
})}
@ -1112,11 +1112,11 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
autoComplete={autoComplete}
/>
</div>
<div className={`${prefixCls}-range-separator`} ref={separatorRef}>
<div class={`${prefixCls}-range-separator`} ref={separatorRef}>
{separator}
</div>
<div
className={classNames(`${prefixCls}-input`, {
class={classNames(`${prefixCls}-input`, {
[`${prefixCls}-input-active`]: mergedActivePickerIndex === 1,
[`${prefixCls}-input-placeholder`]: !!endHoverValue,
})}
@ -1137,7 +1137,7 @@ function InnerRangePicker<DateType>(props: RangePickerProps<DateType>) {
/>
</div>
<div
className={`${prefixCls}-active-bar`}
class={`${prefixCls}-active-bar`}
style={{
...activeBarPositionStyle,
width: activeBarWidth,

View File

@ -8,7 +8,8 @@ import type { SharedTimeProps } from '.';
import { setTime as utilSetTime } from '../../utils/timeUtil';
import { cloneElement } from '../../../_util/vnode';
import { VueNode } from '../../../_util/type';
import { Ref } from '@vue/reactivity';
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;
@ -50,200 +51,224 @@ export type TimeBodyProps<DateType> = {
operationRef: Ref<BodyOperationRef | undefined>;
} & SharedTimeProps<DateType>;
function TimeBody<DateType>(props: TimeBodyProps<DateType>) {
const {
generateConfig,
prefixCls,
operationRef,
activeColumnIndex,
value,
showHour,
showMinute,
showSecond,
use12Hours,
hourStep = 1,
minuteStep = 1,
secondStep = 1,
disabledHours,
disabledMinutes,
disabledSeconds,
hideDisabledOptions,
onSelect,
} = props;
const columns: {
node: VueNode;
value: number;
units: Unit[];
onSelect: (diff: number) => void;
}[] = [];
const contentPrefixCls = `${prefixCls}-content`;
const columnPrefixCls = `${prefixCls}-time-panel`;
let isPM: boolean | undefined;
const originHour = value ? generateConfig.getHour(value) : -1;
let hour = originHour;
const minute = value ? generateConfig.getMinute(value) : -1;
const second = value ? generateConfig.getSecond(value) : -1;
const setTime = (
isNewPM: boolean | undefined,
newHour: number,
newMinute: number,
newSecond: number,
) => {
let newDate = value || generateConfig.getNow();
const mergedHour = Math.max(0, newHour);
const mergedMinute = Math.max(0, newMinute);
const mergedSecond = Math.max(0, newSecond);
newDate = utilSetTime(
generateConfig,
newDate,
!use12Hours || !isNewPM ? mergedHour : mergedHour + 12,
mergedMinute,
mergedSecond,
);
return newDate;
};
// ========================= Unit =========================
const rawHours = generateUnits(0, 23, hourStep, disabledHours && disabledHours());
const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate);
// Should additional logic to handle 12 hours
if (use12Hours) {
isPM = hour >= 12; // -1 means should display AM
hour %= 12;
}
const [AMDisabled, PMDisabled] = React.useMemo(() => {
if (!use12Hours) {
return [false, false];
}
const AMPMDisabled = [true, true];
memorizedRawHours.forEach(({ disabled, value: hourValue }) => {
if (disabled) return;
if (hourValue >= 12) {
AMPMDisabled[1] = false;
const TimeBody = defineComponent({
name: 'TimeBody',
inheritAttrs: false,
props: [
'generateConfig',
'prefixCls',
'operationRef',
'activeColumnIndex',
'value',
'showHour',
'showMinute',
'showSecond',
'use12Hours',
'hourStep',
'minuteStep',
'secondStep',
'disabledHours',
'disabledMinutes',
'disabledSeconds',
'hideDisabledOptions',
'onSelect',
],
setup(props) {
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 {
AMPMDisabled[0] = false;
return false
}
})
let hour = computed(()=> {
// Should additional logic to handle 12 hours
if (props.use12Hours) {
return originHour.value % 12
} else {
return originHour.value
}
});
return AMPMDisabled;
}, [use12Hours, memorizedRawHours]);
const minute = computed(()=> props.value ? props.generateConfig.getMinute(props.value) : -1);
const second = computed(()=> props.value ? props.generateConfig.getSecond(props.value) : -1);
const hours = React.useMemo(() => {
if (!use12Hours) return memorizedRawHours;
return memorizedRawHours
.filter(isPM ? hourMeta => hourMeta.value >= 12 : hourMeta => hourMeta.value < 12)
.map(hourMeta => {
const hourValue = hourMeta.value % 12;
const hourLabel = hourValue === 0 ? '12' : leftPad(hourValue, 2);
return {
...hourMeta,
label: hourLabel,
value: hourValue,
};
const setTime = (
isNewPM: boolean | undefined,
newHour: number,
newMinute: number,
newSecond: number,
) => {
let newDate = props.value || props.generateConfig.getNow();
const mergedHour = Math.max(0, newHour);
const mergedMinute = Math.max(0, newMinute);
const mergedSecond = Math.max(0, newSecond);
newDate = utilSetTime(
props.generateConfig,
newDate,
!props.use12Hours || !isNewPM ? mergedHour : mergedHour + 12,
mergedMinute,
mergedSecond,
);
return newDate;
};
// ========================= Unit =========================
const rawHours = computed(()=> generateUnits(0, 23, props.hourStep ?? 1, props.disabledHours && props.disabledHours()));
// const memorizedRawHours = useMemo(() => rawHours, rawHours, shouldUnitsUpdate);
const AMPMDisabled = computed(() => {
if (!props.use12Hours) {
return [false, false];
}
const AMPMDisabled = [true, true];
rawHours.value.forEach(({ disabled, value: hourValue }) => {
if (disabled) return;
if (hourValue >= 12) {
AMPMDisabled[1] = false;
} else {
AMPMDisabled[0] = false;
}
});
}, [use12Hours, isPM, memorizedRawHours]);
return AMPMDisabled;
});
const minutes = generateUnits(0, 59, minuteStep, disabledMinutes && disabledMinutes(originHour));
const hours = computed(() => {
if (!props.use12Hours) return rawHours.value;
return rawHours.value
.filter(isPM ? hourMeta => hourMeta.value >= 12 : hourMeta => hourMeta.value < 12)
.map(hourMeta => {
const hourValue = hourMeta.value % 12;
const hourLabel = hourValue === 0 ? '12' : leftPad(hourValue, 2);
return {
...hourMeta,
label: hourLabel,
value: hourValue,
};
});
});
const seconds = generateUnits(
0,
59,
secondStep,
disabledSeconds && disabledSeconds(originHour, minute),
);
const minutes = computed(()=> generateUnits(0, 59, props.minuteStep ?? 1, props.disabledMinutes && props.disabledMinutes(originHour.value)));
// ====================== Operations ======================
operationRef.value = {
onUpDown: diff => {
const column = columns[activeColumnIndex];
if (column) {
const valueIndex = column.units.findIndex(unit => unit.value === column.value);
const seconds = computed(()=> generateUnits(
0,
59,
props.secondStep ?? 1,
props.disabledSeconds && props.disabledSeconds(originHour.value, minute),
));
const unitLen = column.units.length;
for (let i = 1; i < unitLen; i += 1) {
const nextUnit = column.units[(valueIndex + diff * i + unitLen) % unitLen];
return ()=> {
const {
prefixCls,
operationRef,
activeColumnIndex,
showHour,
showMinute,
showSecond,
use12Hours,
hideDisabledOptions,
onSelect,
} = props;
if (nextUnit.disabled !== true) {
column.onSelect(nextUnit.value);
break;
const columns: {
node: VueNode;
value: number;
units: Unit[];
onSelect: (diff: number) => void;
}[] = [];
const contentPrefixCls = `${prefixCls}-content`;
const columnPrefixCls = `${prefixCls}-time-panel`;
// ====================== Operations ======================
operationRef.value = {
onUpDown: diff => {
const column = columns[activeColumnIndex];
if (column) {
const valueIndex = column.units.findIndex(unit => unit.value === column.value);
const unitLen = column.units.length;
for (let i = 1; i < unitLen; i += 1) {
const nextUnit = column.units[(valueIndex + diff * i + unitLen) % unitLen];
if (nextUnit.disabled !== true) {
column.onSelect(nextUnit.value);
break;
}
}
}
},
};
// ======================== Render ========================
function addColumnNode(
condition: boolean | undefined,
node: VueNode,
columnValue: number,
units: Unit[],
onColumnSelect: (diff: number) => void,
) {
if (condition !== false) {
columns.push({
node: cloneElement(node, {
prefixCls: columnPrefixCls,
value: columnValue,
active: activeColumnIndex === columns.length,
onSelect: onColumnSelect,
units,
hideDisabledOptions,
}),
onSelect: onColumnSelect,
value: columnValue,
units,
});
}
}
},
};
// ======================== Render ========================
function addColumnNode(
condition: boolean | undefined,
node: VueNode,
columnValue: number,
units: Unit[],
onColumnSelect: (diff: number) => void,
) {
if (condition !== false) {
columns.push({
node: cloneElement(node, {
prefixCls: columnPrefixCls,
value: columnValue,
active: activeColumnIndex === columns.length,
onSelect: onColumnSelect,
units,
hideDisabledOptions,
}),
onSelect: onColumnSelect,
value: columnValue,
units,
// Hour
addColumnNode(showHour, <TimeUnitColumn key="hour" />, hour.value, hours.value, num => {
onSelect(setTime(isPM.value, num, minute.value, second.value), 'mouse');
});
// Minute
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');
});
// 12 Hours
let PMIndex = -1;
if (typeof isPM === 'boolean') {
PMIndex = isPM ? 1 : 0;
}
addColumnNode(
use12Hours === true,
<TimeUnitColumn key="12hours" />,
PMIndex,
[
{ label: 'AM', value: 0, disabled: AMPMDisabled.value[0] },
{ label: 'PM', value: 1, disabled: AMPMDisabled.value[1] },
],
num => {
onSelect(setTime(!!num, hour.value, minute.value, second.value), 'mouse');
},
);
return <div class={contentPrefixCls}>{columns.map(({ node }) => node)}</div>;
}
}
})
// Hour
addColumnNode(showHour, <TimeUnitColumn key="hour" />, hour, hours, num => {
onSelect(setTime(isPM, num, minute, second), 'mouse');
});
// Minute
addColumnNode(showMinute, <TimeUnitColumn key="minute" />, minute, minutes, num => {
onSelect(setTime(isPM, hour, num, second), 'mouse');
});
// Second
addColumnNode(showSecond, <TimeUnitColumn key="second" />, second, seconds, num => {
onSelect(setTime(isPM, hour, minute, num), 'mouse');
});
// 12 Hours
let PMIndex = -1;
if (typeof isPM === 'boolean') {
PMIndex = isPM ? 1 : 0;
}
addColumnNode(
use12Hours === true,
<TimeUnitColumn key="12hours" />,
PMIndex,
[
{ label: 'AM', value: 0, disabled: AMDisabled },
{ label: 'PM', value: 1, disabled: PMDisabled },
],
num => {
onSelect(setTime(!!num, hour, minute, second), 'mouse');
},
);
return <div class={contentPrefixCls}>{columns.map(({ node }) => node)}</div>;
}
TimeBody.displayName ='TimeBody'
TimeBody.inheritAttrs = false;
export default TimeBody;