import * as React from 'react'; import { PureComponent } from 'react'; import cx from 'classnames'; import { Input, Icon } from 'zent'; import getWidth from 'zent/es/utils/getWidth'; import Popover from '../popover'; import { I18nReceiver as Receiver } from '../i18n'; import HourPanel from './time/HourPanel'; import MinutePanel from './time/MinutePanel'; import SecondPanel from './time/SecondPanel'; import PanelFooter from './common/PanelFooter'; import { formatDate, parseDate, dayStart, padLeft } from './utils'; import { timeFnMap, popPositionMap, commonProps } from './constants'; import { DatePickers } from './common/types'; import noop from 'lodash-es/noop'; import warning from 'zent/es/utils/warning'; const DEFAULT_FORMAT = 'HH:mm:ss'; const DEFAULT_FORMAT_WITHOUT_SECOND = 'HH:mm'; enum TIME_KEY { HOUR = 'hour', MINUTE = 'minute', SECOND = 'second', } /** * 如果 props.format 有修改,则使用之,否则检查showSecond,判断是否要显示秒 * @param props * @returns {string} */ function getFormat(props) { const { format, showSecond } = props; const defaultFormat = showSecond ? DEFAULT_FORMAT : DEFAULT_FORMAT_WITHOUT_SECOND; return format === DEFAULT_FORMAT ? defaultFormat : format; } const disabledMap = { hour: 'disabledHour', minute: 'disabledMinute', second: 'disabledSecond', }; function extractStateFromProps(props: ITimePickerProps): ITimePickerState { const { value } = props; let parsedDate; if (value) { parsedDate = parseDate(value, getFormat(props)); if (!parsedDate) { warning(false, 'time and format mismatch'); } } else { // 利用传入的format解析value,失败则返回默认值 parsedDate = dayStart(); } return { value: parsedDate, isPanelOpen: false, prevProps: props, }; } export interface ITimePickerProps extends DatePickers.ICommonProps { isFooterVisible?: boolean; showSecond?: boolean; hourStep?: number; minuteStep?: number; secondStep?: number; onBeforeConfirm?: () => boolean; onBeforeClear?: () => boolean; disabledTime?: () => DatePickers.IDisabledTime; } export interface ITimePickerState { value: Date; tabKey?: TIME_KEY | null; isPanelOpen: boolean; prevProps: ITimePickerProps; } export class TimePicker extends PureComponent< ITimePickerProps, ITimePickerState > { static defaultProps = { ...commonProps, placeholder: '', format: 'HH:mm:ss', isFooterVisible: true, hourStep: 1, minuteStep: 1, secondStep: 1, disabledTime: () => {}, }; retType = 'string'; disabledTime: Partial; static getDerivedStateFromProps( props: ITimePickerProps, state: ITimePickerState ): Partial | null { if (props !== state.prevProps) { return extractStateFromProps(props); } return null; } constructor(props: ITimePickerProps) { super(props); const { value, valueType } = props; /** * 如果没有有明确指定 valueType,则返回和 value 一致的值,数字或日期或字符串 */ if (valueType) { this.retType = valueType.toLowerCase(); } else if (value) { if (typeof value === 'number') this.retType = 'number'; if (value instanceof Date) this.retType = 'date'; } const state = extractStateFromProps(props); state.tabKey = TIME_KEY.HOUR; this.state = state; this.disabledTime = (props.disabledTime && props.disabledTime()) || {}; } onChangeTime = (val, type) => { const fn = timeFnMap[type]; const tmp = new Date(this.state.value); tmp[fn](val); let nextTabKey = this.state.tabKey; switch (type) { case TIME_KEY.HOUR: nextTabKey = TIME_KEY.MINUTE; break; case TIME_KEY.MINUTE: { if (this.props.showSecond) { nextTabKey = TIME_KEY.SECOND; } break; } default: } this.setState({ value: tmp, tabKey: nextTabKey, }); }; onClearInput = evt => { evt.stopPropagation(); const { onChange, onBeforeClear, canClear } = this.props; if (onBeforeClear && !onBeforeClear()) return; // 用户可以通过这个函数返回 false 来阻止清空 if (!canClear) return; onChange(''); }; /** * 如果传入为数字,返回值也为数字 * 如果传入为 Date 的实例,返回值也为 Date 的实例 * 默认返回 format 格式的字符串 */ getReturnValue = date => { const format = getFormat(this.props); if (this.retType === 'number') { return date.getTime(); } if (this.retType === 'date') { return date; } return formatDate(date, format); }; onConfirm = () => { const { value } = this.state; const { min, onClose, onChange, onBeforeConfirm } = this.props; if (onBeforeConfirm && !onBeforeConfirm()) return; // // 如果没有选择日期则默认选中当前日期 let tmp = value || dayStart(); tmp = new Date( tmp.getFullYear(), tmp.getMonth(), tmp.getDate(), value.getHours(), value.getMinutes(), value.getSeconds() ); if (min) { const minDate = parseDate(min, getFormat(this.props)); minDate.setFullYear(tmp.getFullYear()); minDate.setMonth(tmp.getMonth()); minDate.setDate(tmp.getDate()); if (tmp < minDate) { tmp = new Date(minDate); } } this.setState({ value: tmp, isPanelOpen: false, }); const ret = this.getReturnValue(tmp); onChange(ret); onClose && onClose(); }; isCellDisabled = type => { const { min, max } = this.props; const { value } = this.state; const format = getFormat(this.props); let minDate = null; let minHour = 0; let minMinute = 0; let minSecond = 0; let maxDate = null; let maxHour = 23; let maxMinute = 59; let maxSecond = 59; if (min) { minDate = parseDate(min, format); minHour = minDate.getHours(); minMinute = minDate.getMinutes(); minSecond = minDate.getSeconds(); } if (max) { maxDate = parseDate(max, format); maxHour = maxDate.getHours(); maxMinute = maxDate.getMinutes(); maxSecond = maxDate.getSeconds(); } const defaultHandlers = { [TIME_KEY.HOUR]: h => h < minHour || h > maxHour, [TIME_KEY.MINUTE]: m => (value.getHours() === minHour && m < minMinute) || (value.getHours() === maxHour && m > maxMinute), [TIME_KEY.SECOND]: s => (value.getHours() === minHour && value.getMinutes() === minMinute && s < minSecond) || (value.getHours() === maxHour && value.getMinutes() === maxMinute && s > maxSecond), }; return this.disabledTime[disabledMap[type]] || defaultHandlers[type]; }; togglePicker = () => { const { onOpen, onClose, disabled } = this.props; const isPanelOpen = !this.state.isPanelOpen; if (disabled) return; isPanelOpen ? onOpen && onOpen() : onClose && onClose(); this.setState({ isPanelOpen, tabKey: isPanelOpen ? TIME_KEY.HOUR : null, }); }; switchTab = tabKey => { this.setState({ tabKey, }); }; resetTime = () => { this.setState({ value: extractStateFromProps(this.props).value, }); }; renderPanelContent = i18n => { const { value, tabKey } = this.state; switch (tabKey) { case TIME_KEY.HOUR: return ( this.onChangeTime(v, 'hour')} i18n={i18n} hideHeader /> ); case TIME_KEY.MINUTE: return ( this.onChangeTime(v, 'minute')} i18n={i18n} hideHeader /> ); case TIME_KEY.SECOND: return ( this.onChangeTime(v, 'second')} i18n={i18n} hideHeader /> ); default: return null; } }; renderPicker = i18n => { const { props: { confirmText, showSecond }, state: { value, isPanelOpen }, } = this; // 打开面板的时候才渲染 if (isPanelOpen) { const linkCls = cx({ 'link--current': true, }); const radioButtonGroup = [ { value: TIME_KEY.HOUR, content: `${padLeft(value.getHours())}${i18n.panel.hour}`, }, { value: TIME_KEY.MINUTE, content: `${padLeft(value.getMinutes())}${i18n.panel.minute}`, }, ] .concat( showSecond ? [ { value: TIME_KEY.SECOND, content: `${padLeft(value.getSeconds())}${i18n.panel.second}`, }, ] : [] ) .map(({ value: tabKey, content }) => { return ( this.switchTab(tabKey)} > {content} ); }); return (
{radioButtonGroup}
{this.renderPanelContent(i18n)}
{this.props.isFooterVisible ? (
) : null}
); } return null; }; render() { const { props: { prefix, className, disabled, width, popPosition, name, placeholder, value, canClear, autoComplete, }, state: { isPanelOpen }, } = this; const format = getFormat(this.props); const formattedValue = (value && formatDate(parseDate(value, format), format)) || ''; const wrapperCls = cx( `${prefix}-datetime-picker`, `${prefix}-time-picker`, className ); const inputCls = cx({ 'picker-input': true, 'picker-input--show-clear-icon': canClear && !!formattedValue, 'picker-input--disabled': disabled, 'time-picker-input': true, }); const widthStyle = getWidth(width); return (
{i18n => (
evt.preventDefault()} > {canClear && ( )}
{this.renderPicker(i18n)}
)}
); } } export default TimePicker;