2020-03-31 13:47:59 +08:00

489 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import moment from 'moment';
import Overlay from '../overlay';
import Input from '../input';
import Calendar from '../calendar';
import nextLocale from '../locale/zh-cn';
import { func, obj } from '../util';
import { checkDateValue, formatDateValue, onDateKeydown } from './util';
const { Popup } = Overlay;
/**
* DatePicker.YearPicker
*/
class YearPicker extends Component {
static propTypes = {
prefix: PropTypes.string,
rtl: PropTypes.bool,
/**
* 输入框内置标签
*/
label: PropTypes.node,
/**
* 输入框状态
*/
state: PropTypes.oneOf(['success', 'loading', 'error']),
/**
* 输入提示
*/
placeholder: PropTypes.string,
/**
* 日期值受控moment 对象
*/
value: checkDateValue,
/**
* 初始日期值moment 对象
*/
defaultValue: checkDateValue,
/**
* 日期值的格式(用于限定用户输入和展示)
*/
format: PropTypes.string,
/**
* 禁用日期函数
* @param {MomentObject} 日期值
* @param {String} view 当前视图类型year: 年, month: 月, date: 日
* @return {Boolean} 是否禁用
*/
disabledDate: PropTypes.func,
/**
* 自定义面板页脚
* @return {Node} 自定义的面板页脚组件
*/
footerRender: PropTypes.func,
/**
* 日期值改变时的回调
* @param {MomentObject|String} value 日期值
*/
onChange: PropTypes.func,
/**
* 输入框尺寸
*/
size: PropTypes.oneOf(['small', 'medium', 'large']),
/**
* 是否禁用
*/
disabled: PropTypes.bool,
/**
* 是否显示清空按钮
*/
hasClear: PropTypes.bool,
/**
* 弹层显示状态
*/
visible: PropTypes.bool,
/**
* 弹层默认是否显示
*/
defaultVisible: PropTypes.bool,
/**
* 弹层展示状态变化时的回调
* @param {Boolean} visible 弹层是否显示
* @param {String} reason 触发弹层显示和隐藏的来源 calendarSelect 表示由日期表盘的选择触发; fromTrigger 表示由trigger的点击触发 docClick 表示由document的点击触发
*/
onVisibleChange: PropTypes.func,
/**
* 弹层触发方式
*/
popupTriggerType: PropTypes.oneOf(['click', 'hover']),
/**
* 弹层对齐方式, 具体含义见 OverLay文档
*/
popupAlign: PropTypes.string,
/**
* 弹层容器
* @param {Element} target 目标元素
* @return {Element} 弹层的容器元素
*/
popupContainer: PropTypes.any,
/**
* 弹层自定义样式
*/
popupStyle: PropTypes.object,
/**
* 弹层自定义样式类
*/
popupClassName: PropTypes.string,
/**
* 弹层其他属性
*/
popupProps: PropTypes.object,
/**
* 是否跟随滚动
*/
followTrigger: PropTypes.bool,
/**
* 输入框其他属性
*/
inputProps: PropTypes.object,
yearCellRender: PropTypes.func, // 兼容 0.x yearCellRender
/**
* 日期输入框的 aria-label 属性
*/
dateInputAriaLabel: PropTypes.string,
/**
* 是否为预览态
*/
isPreview: PropTypes.bool,
/**
* 预览态模式下渲染的内容
* @param {MomentObject} value 年份
*/
renderPreview: PropTypes.func,
locale: PropTypes.object,
className: PropTypes.string,
name: PropTypes.string,
popupComponent: PropTypes.elementType,
popupContent: PropTypes.node,
};
static defaultProps = {
prefix: 'next-',
rtl: false,
format: 'YYYY',
size: 'medium',
disabledDate: () => false,
footerRender: () => null,
hasClear: true,
popupTriggerType: 'click',
popupAlign: 'tl tl',
locale: nextLocale.DatePicker,
onChange: func.noop,
onVisibleChange: func.noop,
};
constructor(props, context) {
super(props, context);
const value = formatDateValue(
props.value || props.defaultValue,
props.format
);
this.inputAsString =
typeof (props.value || props.defaultValue) === 'string'; // 判断用户输入是否是字符串
this.state = {
value,
dateInputStr: '',
inputing: false,
visible: props.visible || props.defaultVisible,
};
}
componentWillReceiveProps(nextProps) {
if ('value' in nextProps) {
const value = formatDateValue(
nextProps.value,
nextProps.format || this.props.format
);
this.setState({
value,
});
this.inputAsString = typeof nextProps.value === 'string';
}
if ('visible' in nextProps) {
this.setState({
visible: nextProps.visible,
});
}
}
onValueChange = newValue => {
const ret =
this.inputAsString && newValue
? newValue.format(this.props.format)
: newValue;
this.props.onChange(ret);
};
onSelectCalendarPanel = value => {
// const { format } = this.props;
const prevSelectedMonth = this.state.value;
const selectedMonth = value
.clone()
.month(0)
.date(1)
.hour(0)
.minute(0)
.second(0);
this.handleChange(
selectedMonth,
prevSelectedMonth,
{ inputing: false },
() => {
this.onVisibleChange(false, 'calendarSelect');
}
);
};
clearValue = () => {
this.setState({
dateInputStr: '',
});
this.handleChange(null, this.state.value);
};
onDateInputChange = (inputStr, e, eventType) => {
if (eventType === 'clear' || !inputStr) {
e.stopPropagation();
this.clearValue();
} else {
this.setState({
dateInputStr: inputStr,
inputing: true,
});
}
};
onDateInputBlur = () => {
const { dateInputStr } = this.state;
if (dateInputStr) {
const { disabledDate, format } = this.props;
const parsed = moment(dateInputStr, format, true);
this.setState({
dateInputStr: '',
inputing: false,
});
if (parsed.isValid() && !disabledDate(parsed, 'year')) {
this.handleChange(parsed, this.state.value);
}
}
};
onKeyDown = e => {
const { format } = this.props;
const { dateInputStr, value } = this.state;
const dateStr = onDateKeydown(
e,
{ format, dateInputStr, value },
'year'
);
if (!dateStr) return;
this.onDateInputChange(dateStr);
};
handleChange = (newValue, prevValue, others = {}, callback) => {
if (!('value' in this.props)) {
this.setState({
value: newValue,
...others,
});
} else {
this.setState({
...others,
});
}
const { format } = this.props;
const newValueOf = newValue ? newValue.format(format) : null;
const preValueOf = prevValue ? prevValue.format(format) : null;
if (newValueOf !== preValueOf) {
this.onValueChange(newValue);
if (typeof callback === 'function') {
return callback();
}
}
};
onVisibleChange = (visible, reason) => {
if (!('visible' in this.props)) {
this.setState({
visible,
});
}
this.props.onVisibleChange(visible, reason);
};
renderPreview(others) {
const { prefix, format, className, renderPreview } = this.props;
const { value } = this.state;
const previewCls = classnames(className, `${prefix}form-preview`);
const label = value ? value.format(format) : '';
if (typeof renderPreview === 'function') {
return (
<div {...others} className={previewCls}>
{renderPreview(value, this.props)}
</div>
);
}
return (
<p {...others} className={previewCls}>
{label}
</p>
);
}
render() {
const {
prefix,
rtl,
locale,
label,
state,
format,
disabledDate,
footerRender,
placeholder,
size,
disabled,
hasClear,
popupTriggerType,
popupAlign,
popupContainer,
popupStyle,
popupClassName,
popupProps,
popupComponent,
popupContent,
followTrigger,
className,
inputProps,
dateInputAriaLabel,
yearCellRender,
isPreview,
...others
} = this.props;
const { visible, value, dateInputStr, inputing } = this.state;
const yearPickerCls = classnames(
{
[`${prefix}year-picker`]: true,
},
className
);
const triggerInputCls = classnames({
[`${prefix}year-picker-input`]: true,
[`${prefix}error`]: false,
});
const panelBodyClassName = classnames({
[`${prefix}year-picker-body`]: true,
});
if (rtl) {
others.dir = 'rtl';
}
if (isPreview) {
return this.renderPreview(
obj.pickOthers(others, YearPicker.PropTypes)
);
}
const panelInputCls = `${prefix}year-picker-panel-input`;
const sharedInputProps = {
...inputProps,
size,
disabled,
onChange: this.onDateInputChange,
onBlur: this.onDateInputBlur,
onPressEnter: this.onDateInputBlur,
onKeyDown: this.onKeyDown,
};
const dateInputValue = inputing
? dateInputStr
: (value && value.format(format)) || '';
const triggerInputValue = dateInputValue;
const dateInput = (
<Input
{...sharedInputProps}
aria-label={dateInputAriaLabel}
value={dateInputValue}
placeholder={format}
className={panelInputCls}
/>
);
const datePanel = (
<Calendar
shape="panel"
modes={['year']}
value={value}
yearCellRender={yearCellRender}
onSelect={this.onSelectCalendarPanel}
disabledDate={disabledDate}
/>
);
const panelBody = datePanel;
const panelFooter = footerRender();
const allowClear = value && hasClear;
const trigger = (
<div className={`${prefix}year-picker-trigger`}>
<Input
{...sharedInputProps}
label={label}
state={state}
value={triggerInputValue}
role="combobox"
aria-expanded={visible}
readOnly
placeholder={placeholder || locale.yearPlaceholder}
hint="calendar"
hasClear={allowClear}
className={triggerInputCls}
/>
</div>
);
const PopupComponent = popupComponent ? popupComponent : Popup;
return (
<div
{...obj.pickOthers(YearPicker.propTypes, others)}
className={yearPickerCls}
>
<PopupComponent
autoFocus
align={popupAlign}
{...popupProps}
followTrigger={followTrigger}
disabled={disabled}
visible={visible}
onVisibleChange={this.onVisibleChange}
triggerType={popupTriggerType}
container={popupContainer}
style={popupStyle}
className={popupClassName}
trigger={trigger}
>
{popupContent ? (
popupContent
) : (
<div dir={others.dir} className={panelBodyClassName}>
<div
className={`${prefix}year-picker-panel-header`}
>
{dateInput}
</div>
{panelBody}
{panelFooter}
</div>
)}
</PopupComponent>
</div>
);
}
}
export default YearPicker;