mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-04 17:27:09 +00:00
Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3681622 * chore: remove unnecessary code * refactor: react-render using TypeScript * chore: build-script * refactor: editor-skeleton * refactor: designer * refactor: material-parser * refactor: editor-setters * refactor: js to ts for rax-provider Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3678180 * refactor: rax-provider * feat: add build command * chore: compilerOptions for rax-provider * refactor: JS to TS for Rax Renderer Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3678935 * refactor: rax-renderer * Merge remote-tracking branch 'origin/refactor/js-to-ts' into refactor/js2ts-rax-renderer * Merge remote-tracking branch 'origin/refactor/js-to-ts' into refactor/js2ts-rax-renderer * refactor: ts-nocheck * chore: ts compile error * fix: ts rootDir * fix: compile error * chore: using same tsconfig for rax component * refactor: ts compile rax-renderer && rax-provider * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/js-to-ts # Conflicts: # packages/rax-render/src/utils/appHelper.js # packages/rax-render/src/utils/appHelper.ts # packages/utils/src/appHelper.ts * refactor: no JS file * refactor: remove lint * feat: add xima * feat: add eslint ignore * style: fix by lint * feat: lint command * fix: using the same eslint config * style: eslint settings * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/plugin-event-bind-dialog/src/index.tsx # packages/plugin-source-editor/src/index.tsx # packages/runtime/src/core/container.ts # packages/runtime/src/core/provider.ts * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/designer/src/document/document-model.ts # packages/designer/src/document/node/node-children.ts # packages/designer/src/document/node/props/prop.ts # packages/plugin-source-editor/src/index.tsx * fix: 修改dataSource items -> list * Merge remote-tracking branch 'origin/relase/1.0.0' into refactor/code-style # Conflicts: # packages/react-renderer/package.json * refactor: component-panel plugin-component-pane 代码规范化 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3703771 * feat: support bizcomps * refactor: component-panel * style: eslint * Merge branch 'refactor/code-style' into fix/ducheng-source-style * style: code style * Merge branch 'fix/ducheng-source-style' into 'refactor/code-style' Code review title: Fix/ducheng source style Code review Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3705972 * style: for demo * style: for demo-server * style: for plugin-event-bind-dialog * style: for plugin-sample-preview * style: for plugin-undo-redo * style: plugin-variable-bind-dialog * style: types * style: for utils * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/editor-setters/src/expression-setter/locale/snippets.ts # packages/editor-setters/src/json-setter/locale/snippets.ts # packages/editor-setters/src/locale/snippets.ts # packages/plugin-components-pane/package.json # packages/rax-render/src/hoc/compWrapper.tsx # packages/rax-render/src/utils/index.ts # packages/react-renderer/src/context/appContext.ts * style: editor-preset-general editor-preset-general 代码规范化 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3707974 * style: editor-preset-general * fix: should set field * fix: should set field - demo-server * refactor(style): fix code style for designer refactor(style): fix code style for editor-core refactor(style): fix code style for editor-skeleton refactor(style): fix code style for react-simulator-renderer refactor(style): fix code style for rax-simulator-renderer * Merge branch 'refactor/lihao-code-style' into 'refactor/code-style' Code review title: refactor(style): fix code style for designer Code review description: refactor(style): fix code style for editor-core refactor(style): fix code style for editor-skeleton refactor(style): fix code style for react-simulator-renderer refactor(style): fix code style for rax-simulator-renderer Code review Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3709472 * style: react/no-multi-comp set to 0 for designer * style: ignore editor-prset-vision * style: fix for plugin-outline-pane * style: fix for rax-provider * style: react-provider * style: runtime * style: rax-render * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/editor-setters/src/expression-setter/index.tsx # packages/plugin-source-editor/src/index.tsx # packages/plugin-source-editor/src/transform.ts * refactor: material parser code style 1. 修复eslint问题 2. instanceOf => any 3. 修复node类型解析失败问题 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3716330 * refactor: material parser code style * refactor: code-generator code style 1. rax 出码合并 2. code style 修复 注:合并的代码中带了 datasource 的 Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3717159 * Merge branch 'feat/rax-code-generator' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into feat/rax-code-generator # Conflicts: # packages/code-generator/src/generator/ProjectBuilder.ts # packages/code-generator/src/parser/SchemaParser.ts # packages/code-generator/src/plugins/component/rax/jsx.ts # packages/code-generator/src/plugins/project/constants.ts # packages/code-generator/src/plugins/project/framework/rax/plugins/packageJSON.ts # packages/code-generator/src/plugins/project/i18n.ts # packages/code-generator/src/publisher/disk/index.ts # packages/code-generator/src/publisher/disk/utils.ts # packages/code-generator/src/types/core.ts # packages/code-generator/src/types/schema.ts # packages/code-generator/src/utils/compositeType.ts # packages/code-generator/src/utils/nodeToJSX.ts * refactor: code-generator * Merge remote-tracking branch 'origin/refactor/code-style' into refactor/code-style-code-generator # Conflicts: # .vscode/launch.json * Revert "refactor: code-generator code style " This reverts commit ebc78e8788f83e8fda0e146758af43b878125c10. * chore: eslintrc && eslintignore * style: for plugin-source-editor * style: fix by eslint --fix * style: scripts/ * style: datasource-engine * feat: pre-commit * Merge branch 'refactor/code-style' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into refactor/code-style # Conflicts: # .eslintignore # packages/code-generator/src/parser/SchemaParser.ts # packages/code-generator/src/plugins/component/rax/containerInitState.ts # packages/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts # packages/code-generator/src/plugins/component/rax/containerLifeCycle.ts # packages/code-generator/src/plugins/project/framework/rax/plugins/buildConfig.ts # packages/code-generator/src/utils/OrderedSet.ts # packages/code-generator/src/utils/ScopeBindings.ts # packages/code-generator/src/utils/expressionParser.ts # packages/code-generator/src/utils/schema.ts # packages/datasource-engine/src/core/DataSourceEngine.ts # packages/datasource-engine/src/core/RuntimeDataSource.ts # packages/datasource-engine/src/types/IRuntimeContext.ts # packages/datasource-engine/src/types/index.ts * refactor: code style code-generator 对 code style 分支上次合并的 code-generator 修改做了 revert 重新在 code style 分支基础上对代码样式做了修复 rax 合并分支会另行 fix 后向 release 分支提 MR Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3719702 * refactor: code style fix * style: for code-generator * Merge remote-tracking branch 'origin/release/1.0.0' into refactor/code-style # Conflicts: # packages/editor-skeleton/src/transducers/addon-combine.ts # packages/plugin-components-pane/package.json # packages/plugin-components-pane/src/components/base/index.tsx # packages/plugin-components-pane/src/components/component-list/index.tsx # packages/plugin-components-pane/src/i18n/index.ts # packages/plugin-components-pane/src/i18n/strings/index.ts # packages/plugin-components-pane/src/index.tsx # packages/plugin-event-bind-dialog/src/index.tsx # packages/plugin-source-editor/src/index.tsx # packages/plugin-source-editor/src/transform.ts * style: auto fix
1181 lines
32 KiB
JavaScript
1181 lines
32 KiB
JavaScript
import React, { Component } from 'react';
|
||
import PropTypes from 'prop-types';
|
||
import classnames from 'classnames';
|
||
import moment from 'moment';
|
||
import ConfigProvider from '../config-provider';
|
||
import Overlay from '../overlay';
|
||
import Input from '../input';
|
||
import Calendar from '../calendar';
|
||
import RangeCalendar from '../calendar/range-calendar';
|
||
import TimePickerPanel from '../time-picker/panel';
|
||
import nextLocale from '../locale/zh-cn';
|
||
import { func, obj } from '../util';
|
||
import {
|
||
PANEL,
|
||
resetValueTime,
|
||
formatDateValue,
|
||
extend,
|
||
getDateTimeFormat,
|
||
isFunction,
|
||
onDateKeydown,
|
||
onTimeKeydown,
|
||
} from './util';
|
||
import PanelFooter from './module/panel-footer';
|
||
|
||
const { Popup } = Overlay;
|
||
|
||
function mapInputStateName(name) {
|
||
return {
|
||
startValue: 'startDateInputStr',
|
||
endValue: 'endDateInputStr',
|
||
startTime: 'startTimeInputStr',
|
||
endTime: 'endTimeInputStr',
|
||
}[name];
|
||
}
|
||
|
||
function mapTimeToValue(name) {
|
||
return {
|
||
startTime: 'startValue',
|
||
endTime: 'endValue',
|
||
}[name];
|
||
}
|
||
|
||
function getFormatValues(values, format) {
|
||
if (!Array.isArray(values)) {
|
||
return [null, null];
|
||
}
|
||
return [
|
||
formatDateValue(values[0], format),
|
||
formatDateValue(values[1], format),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* DatePicker.RangePicker
|
||
*/
|
||
export default class RangePicker extends Component {
|
||
static propTypes = {
|
||
...ConfigProvider.propTypes,
|
||
prefix: PropTypes.string,
|
||
rtl: PropTypes.bool,
|
||
/**
|
||
* 日期范围类型
|
||
*/
|
||
type: PropTypes.oneOf(['date', 'month', 'year']),
|
||
/**
|
||
* 默认展示的起始月份
|
||
* @return {MomentObject} 返回包含指定月份的 moment 对象实例
|
||
*/
|
||
defaultVisibleMonth: PropTypes.func,
|
||
onVisibleMonthChange: PropTypes.func,
|
||
/**
|
||
* 日期范围值数组 [moment, moment]
|
||
*/
|
||
value: PropTypes.array,
|
||
/**
|
||
* 初始的日期范围值数组 [moment, moment]
|
||
*/
|
||
defaultValue: PropTypes.array,
|
||
/**
|
||
* 日期格式
|
||
*/
|
||
format: PropTypes.string,
|
||
/**
|
||
* 是否使用时间控件,支持传入 TimePicker 的属性
|
||
*/
|
||
showTime: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
|
||
/**
|
||
* 每次选择是否重置时间(仅在 showTime 开启时有效)
|
||
*/
|
||
resetTime: PropTypes.bool,
|
||
/**
|
||
* 禁用日期函数
|
||
* @param {MomentObject} 日期值
|
||
* @param {String} view 当前视图类型,year: 年, month: 月, date: 日
|
||
* @return {Boolean} 是否禁用
|
||
*/
|
||
disabledDate: PropTypes.func,
|
||
/**
|
||
* 自定义面板页脚
|
||
* @return {Node} 自定义的面板页脚组件
|
||
*/
|
||
footerRender: PropTypes.func,
|
||
/**
|
||
* 日期范围值改变时的回调 [ MomentObject|String, MomentObject|String ]
|
||
* @param {Array<MomentObject|String>} value 日期值
|
||
*/
|
||
onChange: PropTypes.func,
|
||
/**
|
||
* 点击确认按钮时的回调 返回开始时间和结束时间`[ MomentObject|String, MomentObject|String ]`
|
||
* @return {Array} 日期范围
|
||
*/
|
||
onOk: PropTypes.func,
|
||
/**
|
||
* 输入框内置标签
|
||
*/
|
||
label: PropTypes.node,
|
||
/**
|
||
* 输入框状态
|
||
*/
|
||
state: PropTypes.oneOf(['error', 'loading', 'success']),
|
||
/**
|
||
* 输入框尺寸
|
||
*/
|
||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||
/**
|
||
* 是否禁用
|
||
*/
|
||
disabled: PropTypes.bool,
|
||
/**
|
||
* 是否显示清空按钮
|
||
*/
|
||
hasClear: PropTypes.bool,
|
||
/**
|
||
* 弹层显示状态
|
||
*/
|
||
visible: PropTypes.bool,
|
||
/**
|
||
* 弹层默认是否显示
|
||
*/
|
||
defaultVisible: PropTypes.bool,
|
||
/**
|
||
* 弹层展示状态变化时的回调
|
||
* @param {Boolean} visible 弹层是否显示
|
||
* @param {String} type 触发弹层显示和隐藏的来源 okBtnClick 表示由确认按钮触发; 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,
|
||
/**
|
||
* 自定义日期单元格渲染
|
||
*/
|
||
dateCellRender: PropTypes.func,
|
||
/**
|
||
* 自定义月份渲染函数
|
||
* @param {Object} calendarDate 对应 Calendar 返回的自定义日期对象
|
||
* @returns {ReactNode}
|
||
*/
|
||
monthCellRender: PropTypes.func,
|
||
yearCellRender: PropTypes.func, // 兼容 0.x yearCellRender
|
||
/**
|
||
* 开始日期输入框的 aria-label 属性
|
||
*/
|
||
startDateInputAriaLabel: PropTypes.string,
|
||
/**
|
||
* 开始时间输入框的 aria-label 属性
|
||
*/
|
||
startTimeInputAriaLabel: PropTypes.string,
|
||
/**
|
||
* 结束日期输入框的 aria-label 属性
|
||
*/
|
||
endDateInputAriaLabel: PropTypes.string,
|
||
/**
|
||
* 结束时间输入框的 aria-label 属性
|
||
*/
|
||
endTimeInputAriaLabel: PropTypes.string,
|
||
/**
|
||
* 是否为预览态
|
||
*/
|
||
isPreview: PropTypes.bool,
|
||
/**
|
||
* 预览态模式下渲染的内容
|
||
* @param {Array<MomentObject, MomentObject>} value 日期区间
|
||
*/
|
||
renderPreview: PropTypes.func,
|
||
disableChangeMode: PropTypes.bool,
|
||
yearRange: PropTypes.arrayOf(PropTypes.number),
|
||
ranges: PropTypes.object, // 兼容0.x版本
|
||
locale: PropTypes.object,
|
||
className: PropTypes.string,
|
||
name: PropTypes.string,
|
||
popupComponent: PropTypes.elementType,
|
||
popupContent: PropTypes.node,
|
||
placeholder: PropTypes.oneOfType([
|
||
PropTypes.arrayOf(PropTypes.string),
|
||
PropTypes.string,
|
||
]),
|
||
};
|
||
|
||
static defaultProps = {
|
||
prefix: 'next-',
|
||
rtl: false,
|
||
type: 'date',
|
||
size: 'medium',
|
||
showTime: false,
|
||
resetTime: false,
|
||
format: 'YYYY-MM-DD',
|
||
disabledDate: () => false,
|
||
footerRender: () => null,
|
||
hasClear: true,
|
||
defaultVisible: false,
|
||
popupTriggerType: 'click',
|
||
popupAlign: 'tl tl',
|
||
locale: nextLocale.DatePicker,
|
||
disableChangeMode: false,
|
||
onChange: func.noop,
|
||
onOk: func.noop,
|
||
onVisibleChange: func.noop,
|
||
};
|
||
|
||
constructor(props, context) {
|
||
super(props, context);
|
||
const dateTimeFormat = getDateTimeFormat(
|
||
props.format,
|
||
props.showTime,
|
||
props.type,
|
||
);
|
||
extend(dateTimeFormat, this);
|
||
|
||
const val = props.value || props.defaultValue;
|
||
const values = getFormatValues(val, this.dateTimeFormat);
|
||
this.inputAsString =
|
||
val && (typeof val[0] === 'string' || typeof val[1] === 'string');
|
||
this.state = {
|
||
visible: props.visible || props.defaultVisible,
|
||
startValue: values[0],
|
||
endValue: values[1],
|
||
startDateInputStr: '',
|
||
endDateInputStr: '',
|
||
activeDateInput: 'startValue',
|
||
startTimeInputStr: '',
|
||
endTimeInputStr: '',
|
||
inputing: false, // 当前是否处于输入状态
|
||
panel: PANEL.DATE,
|
||
};
|
||
}
|
||
|
||
componentWillReceiveProps(nextProps) {
|
||
if ('showTime' in nextProps) {
|
||
const dateTimeFormat = getDateTimeFormat(
|
||
nextProps.format || this.props.format,
|
||
nextProps.showTime,
|
||
nextProps.type,
|
||
);
|
||
extend(dateTimeFormat, this);
|
||
}
|
||
|
||
if ('value' in nextProps) {
|
||
const values = getFormatValues(
|
||
nextProps.value,
|
||
this.dateTimeFormat,
|
||
);
|
||
this.setState({
|
||
startValue: values[0],
|
||
endValue: values[1],
|
||
});
|
||
this.inputAsString =
|
||
nextProps.value &&
|
||
(typeof nextProps.value[0] === 'string' ||
|
||
typeof nextProps.value[1] === 'string');
|
||
}
|
||
|
||
if ('visible' in nextProps) {
|
||
this.setState({
|
||
visible: nextProps.visible,
|
||
});
|
||
}
|
||
}
|
||
|
||
onValueChange = (values, handler = 'onChange') => {
|
||
let ret;
|
||
if (!values.length || !this.inputAsString) {
|
||
ret = values;
|
||
} else {
|
||
ret = [
|
||
values[0] ? values[0].format(this.dateTimeFormat) : null,
|
||
values[1] ? values[1].format(this.dateTimeFormat) : null,
|
||
];
|
||
}
|
||
this.props[handler](ret);
|
||
};
|
||
|
||
onSelectCalendarPanel = (value, active) => {
|
||
const { showTime, resetTime } = this.props;
|
||
const {
|
||
activeDateInput: prevActiveDateInput,
|
||
startValue: prevStartValue,
|
||
endValue: prevEndValue,
|
||
} = this.state;
|
||
|
||
const newState = {
|
||
activeDateInput: active || prevActiveDateInput,
|
||
inputing: false,
|
||
};
|
||
|
||
let newValue = value;
|
||
switch (active || prevActiveDateInput) {
|
||
case 'startValue': {
|
||
if (
|
||
!prevEndValue ||
|
||
value.valueOf() <= prevEndValue.valueOf()
|
||
) {
|
||
newState.activeDateInput = 'endValue';
|
||
}
|
||
|
||
if (showTime) {
|
||
if (!prevStartValue) {
|
||
// 第一次选择,如果设置了时间默认值,则使用该默认时间
|
||
if (showTime.defaultValue) {
|
||
const defaultTimeValue = formatDateValue(
|
||
Array.isArray(showTime.defaultValue)
|
||
? showTime.defaultValue[0]
|
||
: showTime.defaultValue,
|
||
this.timeFormat,
|
||
);
|
||
newValue = resetValueTime(value, defaultTimeValue);
|
||
}
|
||
} else if (!resetTime) {
|
||
// 非第一次选择,如果开启了 resetTime ,则记住之前选择的时间值
|
||
newValue = resetValueTime(value, prevStartValue);
|
||
}
|
||
}
|
||
|
||
newState.startValue = newValue;
|
||
|
||
if (prevEndValue && value.valueOf() > prevEndValue.valueOf()) {
|
||
newState.endValue = null;
|
||
newState.activeDateInput = 'endValue';
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 'endValue':
|
||
if (!prevStartValue) {
|
||
newState.activeDateInput = 'startValue';
|
||
}
|
||
|
||
if (showTime) {
|
||
if (!prevEndValue) {
|
||
// 第一次选择,如果设置了时间默认值,则使用该默认时间
|
||
if (showTime.defaultValue) {
|
||
const defaultTimeValue = formatDateValue(
|
||
Array.isArray(showTime.defaultValue)
|
||
? showTime.defaultValue[1] ||
|
||
showTime.defaultValue[0]
|
||
: showTime.defaultValue,
|
||
this.timeFormat,
|
||
);
|
||
newValue = resetValueTime(value, defaultTimeValue);
|
||
}
|
||
} else if (!resetTime) {
|
||
// 非第一次选择,如果开启了 resetTime ,则记住之前选择的时间值
|
||
newValue = resetValueTime(value, prevEndValue);
|
||
}
|
||
}
|
||
|
||
newState.endValue = newValue;
|
||
|
||
// 选择了一个比开始日期更小的结束日期,此时表示用户重新选择了
|
||
if (
|
||
prevStartValue &&
|
||
value.valueOf() < prevStartValue.valueOf()
|
||
) {
|
||
newState.startValue = value;
|
||
newState.endValue = null;
|
||
}
|
||
break;
|
||
}
|
||
|
||
const newStartValue =
|
||
'startValue' in newState ? newState.startValue : prevStartValue;
|
||
const newEndValue =
|
||
'endValue' in newState ? newState.endValue : prevEndValue;
|
||
|
||
// 受控状态选择不更新值
|
||
if ('value' in this.props) {
|
||
delete newState.startValue;
|
||
delete newState.endValue;
|
||
}
|
||
|
||
this.setState(newState);
|
||
|
||
this.onValueChange([newStartValue, newEndValue]);
|
||
};
|
||
|
||
clearRange = () => {
|
||
this.setState({
|
||
startDateInputStr: '',
|
||
endDateInputStr: '',
|
||
startTimeInputStr: '',
|
||
endTimeInputStr: '',
|
||
});
|
||
|
||
if (!('value' in this.props)) {
|
||
this.setState({
|
||
startValue: null,
|
||
endValue: null,
|
||
});
|
||
}
|
||
|
||
this.onValueChange([]);
|
||
};
|
||
|
||
onDateInputChange = (inputStr, e, eventType) => {
|
||
if (eventType === 'clear' || !inputStr) {
|
||
e.stopPropagation();
|
||
this.clearRange();
|
||
} else {
|
||
const stateName = mapInputStateName(this.state.activeDateInput);
|
||
this.setState({
|
||
[stateName]: inputStr,
|
||
inputing: this.state.activeDateInput,
|
||
});
|
||
}
|
||
};
|
||
|
||
onDateInputBlur = () => {
|
||
const stateName = mapInputStateName(this.state.activeDateInput);
|
||
const dateInputStr = this.state[stateName];
|
||
if (dateInputStr) {
|
||
const { format, disabledDate } = this.props;
|
||
const parsed = moment(dateInputStr, format, true);
|
||
|
||
this.setState({
|
||
[stateName]: '',
|
||
inputing: false,
|
||
});
|
||
|
||
if (parsed.isValid() && !disabledDate(parsed, 'date')) {
|
||
const valueName = this.state.activeDateInput;
|
||
const newValue = parsed;
|
||
|
||
this.handleChange(valueName, newValue);
|
||
}
|
||
}
|
||
};
|
||
|
||
onDateInputKeyDown = e => {
|
||
const { type } = this.props;
|
||
const { activeDateInput } = this.state;
|
||
const stateName = mapInputStateName(activeDateInput);
|
||
const dateInputStr = this.state[stateName];
|
||
const dateStr = onDateKeydown(
|
||
e,
|
||
{
|
||
format: this.format,
|
||
value: this.state[activeDateInput],
|
||
dateInputStr,
|
||
},
|
||
type === 'date' ? 'day' : type,
|
||
);
|
||
if (!dateStr) return;
|
||
|
||
return this.onDateInputChange(dateStr);
|
||
};
|
||
|
||
onFocusDateInput = type => {
|
||
if (type !== this.state.activeDateInput) {
|
||
this.setState({
|
||
activeDateInput: type,
|
||
});
|
||
}
|
||
if (this.state.panel !== PANEL.DATE) {
|
||
this.setState({
|
||
panel: PANEL.DATE,
|
||
});
|
||
}
|
||
};
|
||
|
||
onFocusTimeInput = type => {
|
||
if (type !== this.state.activeDateInput) {
|
||
this.setState({
|
||
activeDateInput: type,
|
||
});
|
||
}
|
||
|
||
if (this.state.panel !== PANEL.TIME) {
|
||
this.setState({
|
||
panel: PANEL.TIME,
|
||
});
|
||
}
|
||
};
|
||
|
||
onSelectStartTime = value => {
|
||
if (!('value' in this.props)) {
|
||
this.setState({
|
||
startValue: value,
|
||
inputing: false,
|
||
activeDateInput: 'startTime',
|
||
});
|
||
}
|
||
if (value.valueOf() !== this.state.startValue.valueOf()) {
|
||
this.onValueChange([value, this.state.endValue]);
|
||
}
|
||
};
|
||
|
||
onSelectEndTime = value => {
|
||
if (!('value' in this.props)) {
|
||
this.setState({
|
||
endValue: value,
|
||
inputing: false,
|
||
activeDateInput: 'endTime',
|
||
});
|
||
}
|
||
if (value.valueOf() !== this.state.endValue.valueOf()) {
|
||
this.onValueChange([this.state.startValue, value]);
|
||
}
|
||
};
|
||
|
||
onTimeInputChange = inputStr => {
|
||
const stateName = mapInputStateName(this.state.activeDateInput);
|
||
this.setState({
|
||
[stateName]: inputStr,
|
||
inputing: this.state.activeDateInput,
|
||
});
|
||
};
|
||
|
||
onTimeInputBlur = () => {
|
||
const stateName = mapInputStateName(this.state.activeDateInput);
|
||
const timeInputStr = this.state[stateName];
|
||
if (timeInputStr) {
|
||
const parsed = moment(timeInputStr, this.timeFormat, true);
|
||
|
||
this.setState({
|
||
[stateName]: '',
|
||
inputing: false,
|
||
});
|
||
|
||
if (parsed.isValid()) {
|
||
const hour = parsed.hour();
|
||
const minute = parsed.minute();
|
||
const second = parsed.second();
|
||
const valueName = mapTimeToValue(this.state.activeDateInput);
|
||
const newValue = this.state[valueName]
|
||
.clone()
|
||
.hour(hour)
|
||
.minute(minute)
|
||
.second(second);
|
||
|
||
this.handleChange(valueName, newValue);
|
||
}
|
||
}
|
||
};
|
||
|
||
onTimeInputKeyDown = e => {
|
||
const { showTime } = this.props;
|
||
const { activeDateInput } = this.state;
|
||
const stateName = mapInputStateName(activeDateInput);
|
||
const timeInputStr = this.state[stateName];
|
||
const {
|
||
disabledMinutes,
|
||
disabledSeconds,
|
||
hourStep = 1,
|
||
minuteStep = 1,
|
||
secondStep = 1,
|
||
} = typeof showTime === 'object' ? showTime : {};
|
||
let unit = 'second';
|
||
|
||
if (disabledSeconds) {
|
||
unit = disabledMinutes ? 'hour' : 'minute';
|
||
}
|
||
|
||
const timeStr = onTimeKeydown(
|
||
e,
|
||
{
|
||
format: this.timeFormat,
|
||
timeInputStr,
|
||
value: this.state[
|
||
activeDateInput.indexOf('start') ? 'startValue' : 'endValue'
|
||
],
|
||
steps: {
|
||
hour: hourStep,
|
||
minute: minuteStep,
|
||
second: secondStep,
|
||
},
|
||
},
|
||
unit,
|
||
);
|
||
|
||
if (!timeStr) return;
|
||
|
||
this.onTimeInputChange(timeStr);
|
||
};
|
||
|
||
handleChange = (valueName, newValue) => {
|
||
if (!('value' in this.props)) {
|
||
this.setState({
|
||
[valueName]: newValue,
|
||
});
|
||
}
|
||
|
||
const startValue =
|
||
valueName === 'startValue' ? newValue : this.state.startValue;
|
||
const endValue =
|
||
valueName === 'endValue' ? newValue : this.state.endValue;
|
||
|
||
this.onValueChange([startValue, endValue]);
|
||
};
|
||
|
||
onVisibleChange = (visible, type) => {
|
||
if (!('visible' in this.props)) {
|
||
this.setState({
|
||
visible,
|
||
});
|
||
}
|
||
this.props.onVisibleChange(visible, type);
|
||
};
|
||
|
||
changePanel = panel => {
|
||
const { startValue, endValue } = this.state;
|
||
this.setState({
|
||
panel,
|
||
activeDateInput:
|
||
panel === PANEL.DATE
|
||
? !!startValue && !endValue
|
||
? 'endValue'
|
||
: 'startValue'
|
||
: 'startTime',
|
||
});
|
||
};
|
||
|
||
onOk = () => {
|
||
this.onVisibleChange(false, 'okBtnClick');
|
||
this.onValueChange(
|
||
[this.state.startValue, this.state.endValue],
|
||
'onOk',
|
||
);
|
||
};
|
||
|
||
// 如果用户没有给定时间禁用逻辑,则给默认到禁用逻辑,即如果是同一天,则时间不能是同样的
|
||
getDisabledTime = ({ startValue, endValue }) => {
|
||
const { disabledHours, disabledMinutes, disabledSeconds } =
|
||
this.props.showTime || {};
|
||
|
||
let disabledTime = {};
|
||
|
||
if (startValue && endValue) {
|
||
const isSameDay = startValue.format('L') === endValue.format('L');
|
||
const newDisabledHours = isFunction(disabledHours)
|
||
? disabledHours
|
||
: index => {
|
||
if (isSameDay && index < startValue.hour()) {
|
||
return true;
|
||
}
|
||
};
|
||
|
||
const newDisabledMinutes = isFunction(disabledMinutes)
|
||
? disabledMinutes
|
||
: index => {
|
||
if (
|
||
isSameDay &&
|
||
startValue.hour() === endValue.hour() &&
|
||
index < startValue.minute()
|
||
) {
|
||
return true;
|
||
}
|
||
};
|
||
|
||
const newDisabledSeconds = isFunction(disabledSeconds)
|
||
? disabledSeconds
|
||
: index => {
|
||
if (
|
||
isSameDay &&
|
||
startValue.hour() === endValue.hour() &&
|
||
startValue.minute() === endValue.minute() &&
|
||
index <= startValue.second()
|
||
) {
|
||
return true;
|
||
}
|
||
};
|
||
disabledTime = {
|
||
disabledHours: newDisabledHours,
|
||
disabledMinutes: newDisabledMinutes,
|
||
disabledSeconds: newDisabledSeconds,
|
||
};
|
||
}
|
||
|
||
return disabledTime;
|
||
};
|
||
|
||
renderPreview([startValue, endValue], others) {
|
||
const { prefix, format, className, renderPreview } = this.props;
|
||
|
||
const previewCls = classnames(className, `${prefix}form-preview`);
|
||
const startLabel = startValue ? startValue.format(format) : '';
|
||
const endLabel = endValue ? endValue.format(format) : '';
|
||
|
||
if (typeof renderPreview === 'function') {
|
||
return (
|
||
<div {...others} className={previewCls}>
|
||
{renderPreview([startValue, endValue], this.props)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<p {...others} className={previewCls}>
|
||
{startLabel} - {endLabel}
|
||
</p>
|
||
);
|
||
}
|
||
|
||
render() {
|
||
const {
|
||
prefix,
|
||
rtl,
|
||
type,
|
||
defaultVisibleMonth,
|
||
onVisibleMonthChange,
|
||
showTime,
|
||
disabledDate,
|
||
footerRender,
|
||
label,
|
||
ranges = {}, // 兼容0.x ranges 属性
|
||
state: inputState,
|
||
size,
|
||
disabled,
|
||
hasClear,
|
||
popupTriggerType,
|
||
popupAlign,
|
||
popupContainer,
|
||
popupStyle,
|
||
popupClassName,
|
||
popupProps,
|
||
popupComponent,
|
||
popupContent,
|
||
followTrigger,
|
||
className,
|
||
locale,
|
||
inputProps,
|
||
dateCellRender,
|
||
monthCellRender,
|
||
yearCellRender,
|
||
startDateInputAriaLabel,
|
||
startTimeInputAriaLabel,
|
||
endDateInputAriaLabel,
|
||
endTimeInputAriaLabel,
|
||
isPreview,
|
||
disableChangeMode,
|
||
yearRange,
|
||
placeholder,
|
||
...others
|
||
} = this.props;
|
||
|
||
const { state } = this;
|
||
|
||
const classNames = classnames(
|
||
{
|
||
[`${prefix}range-picker`]: true,
|
||
[`${prefix}${size}`]: size,
|
||
[`${prefix}disabled`]: disabled,
|
||
},
|
||
className,
|
||
);
|
||
|
||
const panelBodyClassName = classnames({
|
||
[`${prefix}range-picker-body`]: true,
|
||
[`${prefix}range-picker-body-show-time`]: showTime,
|
||
});
|
||
|
||
const triggerCls = classnames({
|
||
[`${prefix}range-picker-trigger`]: true,
|
||
[`${prefix}error`]: inputState === 'error',
|
||
});
|
||
|
||
const startDateInputCls = classnames({
|
||
[`${prefix}range-picker-panel-input-start-date`]: true,
|
||
[`${prefix}focus`]: state.activeDateInput === 'startValue',
|
||
});
|
||
|
||
const endDateInputCls = classnames({
|
||
[`${prefix}range-picker-panel-input-end-date`]: true,
|
||
[`${prefix}focus`]: state.activeDateInput === 'endValue',
|
||
});
|
||
|
||
if (rtl) {
|
||
others.dir = 'rtl';
|
||
}
|
||
|
||
if (isPreview) {
|
||
return this.renderPreview(
|
||
[state.startValue, state.endValue],
|
||
obj.pickOthers(others, RangePicker.PropTypes),
|
||
);
|
||
}
|
||
|
||
const startDateInputValue =
|
||
state.inputing === 'startValue'
|
||
? state.startDateInputStr
|
||
: (state.startValue && state.startValue.format(this.format)) ||
|
||
'';
|
||
const endDateInputValue =
|
||
state.inputing === 'endValue'
|
||
? state.endDateInputStr
|
||
: (state.endValue && state.endValue.format(this.format)) || '';
|
||
|
||
let startTriggerValue = startDateInputValue;
|
||
let endTriggerValue = endDateInputValue;
|
||
|
||
const sharedInputProps = {
|
||
...inputProps,
|
||
size,
|
||
disabled,
|
||
onChange: this.onDateInputChange,
|
||
onBlur: this.onDateInputBlur,
|
||
onPressEnter: this.onDateInputBlur,
|
||
onKeyDown: this.onDateInputKeyDown,
|
||
};
|
||
|
||
const startDateInput = (
|
||
<Input
|
||
{...sharedInputProps}
|
||
aria-label={startDateInputAriaLabel}
|
||
placeholder={this.format}
|
||
value={startDateInputValue}
|
||
onFocus={() => this.onFocusDateInput('startValue')}
|
||
className={startDateInputCls}
|
||
/>
|
||
);
|
||
|
||
const endDateInput = (
|
||
<Input
|
||
{...sharedInputProps}
|
||
aria-label={endDateInputAriaLabel}
|
||
placeholder={this.format}
|
||
value={endDateInputValue}
|
||
onFocus={() => this.onFocusDateInput('endValue')}
|
||
className={endDateInputCls}
|
||
/>
|
||
);
|
||
|
||
const shareCalendarProps = {
|
||
showOtherMonth: true,
|
||
dateCellRender,
|
||
monthCellRender,
|
||
yearCellRender,
|
||
format: this.format,
|
||
defaultVisibleMonth,
|
||
onVisibleMonthChange,
|
||
};
|
||
|
||
const datePanel =
|
||
type === 'date' ? (
|
||
<RangeCalendar
|
||
{...shareCalendarProps}
|
||
yearRange={yearRange}
|
||
disableChangeMode={disableChangeMode}
|
||
disabledDate={disabledDate}
|
||
onSelect={this.onSelectCalendarPanel}
|
||
startValue={state.startValue}
|
||
endValue={state.endValue}
|
||
/>
|
||
) : (
|
||
<div className={`${prefix}range-picker-panel-body`}>
|
||
<Calendar
|
||
shape="panel"
|
||
modes={type === 'month' ? ['month', 'year'] : ['year']}
|
||
{...{ ...shareCalendarProps }}
|
||
disabledDate={date => {
|
||
return (
|
||
state.endValue &&
|
||
date.isAfter(state.endValue, type)
|
||
);
|
||
}}
|
||
onSelect={value => {
|
||
const selectedValue = value
|
||
.clone()
|
||
.date(1)
|
||
.hour(0)
|
||
.minute(0)
|
||
.second(0);
|
||
if (type === 'year') {
|
||
selectedValue.month(0);
|
||
}
|
||
this.onSelectCalendarPanel(
|
||
selectedValue,
|
||
'startValue',
|
||
);
|
||
}}
|
||
value={state.startValue}
|
||
/>
|
||
<Calendar
|
||
shape="panel"
|
||
modes={type === 'month' ? ['month', 'year'] : ['year']}
|
||
{...shareCalendarProps}
|
||
disabledDate={date => {
|
||
return (
|
||
state.startValue &&
|
||
date.isBefore(state.startValue, type)
|
||
);
|
||
}}
|
||
onSelect={value => {
|
||
const selectedValue = value
|
||
.clone()
|
||
.hour(23)
|
||
.minute(59)
|
||
.second(59);
|
||
if (type === 'year') {
|
||
selectedValue.month(11).date(31);
|
||
} else {
|
||
selectedValue.date(selectedValue.daysInMonth());
|
||
}
|
||
this.onSelectCalendarPanel(
|
||
selectedValue,
|
||
'endValue',
|
||
);
|
||
}}
|
||
value={state.endValue}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
let startTimeInput = null;
|
||
let endTimeInput = null;
|
||
let timePanel = null;
|
||
let panelFooter = footerRender();
|
||
|
||
if (showTime) {
|
||
const startTimeInputValue =
|
||
state.inputing === 'startTime'
|
||
? state.startTimeInputStr
|
||
: (state.startValue &&
|
||
state.startValue.format(this.timeFormat)) ||
|
||
'';
|
||
const endTimeInputValue =
|
||
state.inputing === 'endTime'
|
||
? state.endTimeInputStr
|
||
: (state.endValue &&
|
||
state.endValue.format(this.timeFormat)) ||
|
||
'';
|
||
|
||
startTriggerValue =
|
||
(state.startValue &&
|
||
state.startValue.format(this.dateTimeFormat)) ||
|
||
'';
|
||
endTriggerValue =
|
||
(state.endValue &&
|
||
state.endValue.format(this.dateTimeFormat)) ||
|
||
'';
|
||
|
||
const sharedTimeInputProps = {
|
||
size,
|
||
placeholder: this.timeFormat,
|
||
onFocus: this.onFocusTimeInput,
|
||
onBlur: this.onTimeInputBlur,
|
||
onPressEnter: this.onTimeInputBlur,
|
||
onChange: this.onTimeInputChange,
|
||
onKeyDown: this.onTimeInputKeyDown,
|
||
};
|
||
|
||
const startTimeInputCls = classnames({
|
||
[`${prefix}range-picker-panel-input-start-time`]: true,
|
||
[`${prefix}focus`]: state.activeDateInput === 'startTime',
|
||
});
|
||
|
||
startTimeInput = (
|
||
<Input
|
||
{...sharedTimeInputProps}
|
||
value={startTimeInputValue}
|
||
aria-label={startTimeInputAriaLabel}
|
||
disabled={disabled || !state.startValue}
|
||
onFocus={() => this.onFocusTimeInput('startTime')}
|
||
className={startTimeInputCls}
|
||
/>
|
||
);
|
||
|
||
const endTimeInputCls = classnames({
|
||
[`${prefix}range-picker-panel-input-end-time`]: true,
|
||
[`${prefix}focus`]: state.activeDateInput === 'endTime',
|
||
});
|
||
|
||
endTimeInput = (
|
||
<Input
|
||
{...sharedTimeInputProps}
|
||
value={endTimeInputValue}
|
||
aria-label={endTimeInputAriaLabel}
|
||
disabled={disabled || !state.endValue}
|
||
onFocus={() => this.onFocusTimeInput('endTime')}
|
||
className={endTimeInputCls}
|
||
/>
|
||
);
|
||
|
||
const showSecond = this.timeFormat.indexOf('s') > -1;
|
||
const showMinute = this.timeFormat.indexOf('m') > -1;
|
||
|
||
const sharedTimePickerProps = {
|
||
...showTime,
|
||
prefix,
|
||
locale,
|
||
disabled,
|
||
showSecond,
|
||
showMinute,
|
||
};
|
||
|
||
const disabledTime = this.getDisabledTime(state);
|
||
|
||
timePanel = (
|
||
<div className={`${prefix}range-picker-panel-time`}>
|
||
<TimePickerPanel
|
||
{...sharedTimePickerProps}
|
||
disabled={disabled || !state.startValue}
|
||
className={`${prefix}range-picker-panel-time-start`}
|
||
value={state.startValue}
|
||
onSelect={this.onSelectStartTime}
|
||
/>
|
||
<TimePickerPanel
|
||
{...sharedTimePickerProps}
|
||
{...disabledTime}
|
||
disabled={disabled || !state.endValue}
|
||
className={`${prefix}range-picker-panel-time-end`}
|
||
value={state.endValue}
|
||
onSelect={this.onSelectEndTime}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
panelFooter = panelFooter || (
|
||
<PanelFooter
|
||
prefix={prefix}
|
||
value={state.startValue || state.endValue}
|
||
ranges={Object.keys(ranges).map(key => ({
|
||
label: key,
|
||
value: ranges[key],
|
||
onChange: values => {
|
||
this.setState({
|
||
startValue: values[0],
|
||
endValue: values[1],
|
||
});
|
||
this.onValueChange(values);
|
||
},
|
||
}))}
|
||
disabledOk={
|
||
!state.startValue ||
|
||
!state.endValue ||
|
||
state.startValue.valueOf() > state.endValue.valueOf()
|
||
}
|
||
locale={locale}
|
||
panel={state.panel}
|
||
onPanelChange={showTime ? this.changePanel : null}
|
||
onOk={this.onOk}
|
||
/>
|
||
);
|
||
|
||
const panelBody = {
|
||
[PANEL.DATE]: datePanel,
|
||
[PANEL.TIME]: timePanel,
|
||
}[state.panel];
|
||
|
||
const allowClear = state.startValue && state.endValue && hasClear;
|
||
let [startPlaceholder, endPlaceholder] = placeholder || [];
|
||
|
||
if (typeof placeholder === 'string') {
|
||
startPlaceholder = placeholder;
|
||
endPlaceholder = placeholder;
|
||
}
|
||
|
||
const trigger = (
|
||
<div className={triggerCls}>
|
||
<Input
|
||
{...sharedInputProps}
|
||
readOnly
|
||
role="combobox"
|
||
aria-expanded={state.visible}
|
||
label={label}
|
||
placeholder={startPlaceholder || locale.startPlaceholder}
|
||
value={startTriggerValue}
|
||
hasBorder={false}
|
||
className={`${prefix}range-picker-trigger-input`}
|
||
onFocus={() => this.onFocusDateInput('startValue')}
|
||
/>
|
||
<span className={`${prefix}range-picker-trigger-separator`}>
|
||
-
|
||
</span>
|
||
<Input
|
||
{...sharedInputProps}
|
||
readOnly
|
||
role="combobox"
|
||
aria-expanded={state.visible}
|
||
placeholder={endPlaceholder || locale.endPlaceholder}
|
||
value={endTriggerValue}
|
||
hasBorder={false}
|
||
className={`${prefix}range-picker-trigger-input`}
|
||
onFocus={() => this.onFocusDateInput('endValue')}
|
||
hasClear={allowClear}
|
||
hint="calendar"
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
const PopupComponent = popupComponent || Popup;
|
||
|
||
return (
|
||
<div
|
||
{...obj.pickOthers(RangePicker.propTypes, others)}
|
||
className={classNames}
|
||
>
|
||
<PopupComponent
|
||
autoFocus
|
||
align={popupAlign}
|
||
{...popupProps}
|
||
followTrigger={followTrigger}
|
||
disabled={disabled}
|
||
visible={state.visible}
|
||
onVisibleChange={this.onVisibleChange}
|
||
triggerType={popupTriggerType}
|
||
container={popupContainer}
|
||
style={popupStyle}
|
||
className={popupClassName}
|
||
trigger={trigger}
|
||
>
|
||
{popupContent || (
|
||
<div dir={others.dir} className={panelBodyClassName}>
|
||
<div
|
||
className={`${prefix}range-picker-panel-header`}
|
||
>
|
||
<div
|
||
className={`${prefix}range-picker-panel-input`}
|
||
>
|
||
{startDateInput}
|
||
{startTimeInput}
|
||
<span
|
||
className={`${prefix}range-picker-panel-input-separator`}
|
||
>
|
||
-
|
||
</span>
|
||
{endDateInput}
|
||
{endTimeInput}
|
||
</div>
|
||
</div>
|
||
{panelBody}
|
||
{panelFooter}
|
||
</div>
|
||
)}
|
||
</PopupComponent>
|
||
</div>
|
||
);
|
||
}
|
||
}
|