@kne/react-form-antd
Version:
把 @kne/react-form 表单校验逻辑应用到antd
854 lines (809 loc) • 23.6 kB
JavaScript
import React, { forwardRef, useCallback, useState, useRef, useMemo } from 'react';
import classnames from 'classnames';
import ReactForm__default, { preset as preset$2, useReset, useSubmit } from '@kne/react-form';
export * from '@kne/react-form';
import { preset as preset$1, widget, hooks, hoc } from '@kne/react-form-helper';
export { hoc, hooks, utils, widget } from '@kne/react-form-helper';
import merge from 'lodash/merge';
import { TreeSelect, TimePicker, Input, Switch as Switch$1, Select, Radio, InputNumber, Button, DatePicker, Checkbox as Checkbox$1, Cascader as Cascader$1, Rate as Rate$1, Slider as Slider$1 } from 'antd';
import { IntlProvider, useIntl } from '@kne/react-intl';
import { CloseCircleFilled, CalendarOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import useControlValue from '@kne/use-control-value';
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
const globalParams = {
type: 'default',
size: 'middle',
rules: {},
formModal: {},
resetButton: {},
submitButton: {},
field: {}
};
var preset = props => {
const defaultProps = {};
merge(globalParams, props);
Object.keys(globalParams.field).forEach(name => {
defaultProps[name] = (globalParams.field[name] || {}).defaultProps;
});
preset$1({
field: defaultProps,
globalProps: globalParams.globalProps
});
preset$2(globalParams.rules);
};
const _excluded$5 = ["className", "cache", "enterSubmit", "scrollToError", "scrollProps", "type", "size", "children"];
const {
ScrollToError,
EnterSubmit,
FormStore,
MaxLabelProvider,
SizeProvider
} = widget;
const Form = forwardRef((originProps, ref) => {
const {
className,
cache,
enterSubmit = false,
scrollToError = true,
scrollProps = {
block: 'center'
},
children
} = originProps,
props = _objectWithoutPropertiesLoose(originProps, _excluded$5);
const baseClass = 'react-form';
let computedClass = baseClass;
const {
type,
size
} = Object.assign({
type: 'default',
size: 'middle'
}, globalParams, originProps);
if (type !== 'default') {
computedClass += `--${type}`;
}
if (size !== 'middle') {
computedClass += `--${size}`;
}
const maxLabel = /*#__PURE__*/React.createElement(MaxLabelProvider, null, children);
return /*#__PURE__*/React.createElement("form", {
className: classnames(baseClass, computedClass, className),
onSubmit: e => {
e.preventDefault();
e.stopPropagation();
}
}, /*#__PURE__*/React.createElement(SizeProvider, {
value: {
size
}
}, /*#__PURE__*/React.createElement(ReactForm__default, _extends({}, props, {
ref: ref
}), cache ? /*#__PURE__*/React.createElement(FormStore, {
cache: cache
}) : null, scrollToError ? /*#__PURE__*/React.createElement(ScrollToError, {
scrollProps: scrollProps
}) : null, enterSubmit ? /*#__PURE__*/React.createElement(EnterSubmit, null, maxLabel) : maxLabel)));
});
const locale$1 = {
PleaseSelect: '请选择{label}',
PleaseInput: '请输入{label}',
StartTime: '开始时间',
EndTime: '结束时间',
StartDate: '开始日期',
EndDate: '结束日期',
SoFar: '至今'
};
const locale = {
PleaseSelect: 'Please select {label}',
PleaseInput: 'Please enter {label}',
StartTime: 'Start Time',
EndTime: 'End Time',
StartDate: 'Start Date',
EndDate: 'End Date',
SoFar: 'Present'
};
const _excluded$4 = ["locale"];
const localeMap = {
'zh-CN': locale$1,
'en-US': locale
};
const withLocale = WrappedComponent => {
const WithLocaleComponent = _ref => {
let {
locale = 'zh-CN'
} = _ref,
props = _objectWithoutPropertiesLoose(_ref, _excluded$4);
const messages = localeMap[locale] || locale$1;
return /*#__PURE__*/React.createElement(IntlProvider, {
locale: locale,
messages: messages
}, /*#__PURE__*/React.createElement(WrappedComponent, props));
};
WithLocaleComponent.displayName = `withLocale(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return WithLocaleComponent;
};
const {
useOnChange: useOnChange$b
} = hooks;
const TreeSelectInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'treeSelect'
}, props);
const render = useOnChange$b(Object.assign({
placeholder: formatMessage({
id: 'PleaseSelect'
}, {
label: mergedProps.label || ''
})
}, mergedProps));
return render(TreeSelect);
};
TreeSelectInner.Field = TreeSelect;
TreeSelectInner.TreeNode = TreeSelect.TreeNode;
const _TreeSelect = withLocale(TreeSelectInner);
const {
useOnChange: useOnChange$a
} = hooks;
const {
RangePicker: RangePicker$1
} = TimePicker;
const TimePickerInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'timePicker'
}, props);
const render = useOnChange$a(Object.assign({
placeholder: formatMessage({
id: 'PleaseSelect'
}, {
label: mergedProps.label || ''
})
}, mergedProps));
return render(TimePicker);
};
TimePickerInner.Field = TimePicker;
const RangePickerInner$1 = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'rangeTimePicker'
}, props);
const render = useOnChange$a(Object.assign({
placeholder: formatMessage({
id: 'PleaseSelect'
}, {
label: mergedProps.label || ''
})
}, mergedProps));
return render(RangePicker$1);
};
RangePickerInner$1.Field = RangePicker$1;
const _TimePicker = withLocale(TimePickerInner);
_TimePicker.RangePicker = withLocale(RangePickerInner$1);
const {
useDecorator: useDecorator$2
} = hooks;
const TextAreaInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'textArea'
}, props);
const render = useDecorator$2(Object.assign({
placeholder: formatMessage({
id: 'PleaseInput'
}, {
label: mergedProps.label
})
}, mergedProps));
return render(Input.TextArea);
};
TextAreaInner.Field = Input.TextArea;
const TextArea = withLocale(TextAreaInner);
const {
useOnChange: useOnChange$9,
useCheckedToValue: useCheckedToValue$1
} = hooks;
const {
withChecked: withChecked$1
} = hoc;
const WithSwitch = withChecked$1(Switch$1);
const Switch = props => {
props = Object.assign({}, {
fieldName: 'switch'
}, props);
const checkedProps = useCheckedToValue$1(props);
const render = useOnChange$9(checkedProps);
return render(WithSwitch);
};
Switch.Field = Switch$1;
const {
useOnChange: useOnChange$8
} = hooks;
const SelectInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'select'
}, props);
const render = useOnChange$8(Object.assign({
placeholder: formatMessage({
id: 'PleaseSelect'
}, {
label: mergedProps.label || ''
})
}, mergedProps));
return render(Select);
};
SelectInner.Field = Select;
SelectInner.Option = Select.Option;
SelectInner.OptGroup = Select.OptGroup;
const _Select = withLocale(SelectInner);
const _excluded$3 = ["onChange"];
const {
useOnChange: useOnChange$7
} = hooks;
const RadioGroup = _ref => {
let {
onChange
} = _ref,
props = _objectWithoutPropertiesLoose(_ref, _excluded$3);
props = Object.assign({}, {
fieldName: 'radioGroup'
}, props);
const handler = useCallback(e => {
onChange && onChange(e.target.value);
}, [onChange]);
return /*#__PURE__*/React.createElement(Radio.Group, _extends({}, props, {
onChange: handler
}));
};
const _RadioGroup = props => {
const render = useOnChange$7(props);
return render(RadioGroup);
};
_RadioGroup.Field = RadioGroup;
_RadioGroup.Radio = Radio;
const {
useDecorator: useDecorator$1
} = hooks;
const InputNumberFieldInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'inputNumber',
autoComplete: 'off'
}, props);
const render = useDecorator$1(Object.assign({
placeholder: formatMessage({
id: 'PleaseInput'
}, {
label: mergedProps.label
})
}, mergedProps));
return render(InputNumber);
};
InputNumberFieldInner.Field = InputNumber;
const InputNumberField = withLocale(InputNumberFieldInner);
const {
useDecorator
} = hooks;
const InputFieldInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'input',
autoComplete: 'off'
}, props);
const render = useDecorator(Object.assign({
placeholder: formatMessage({
id: 'PleaseInput'
}, {
label: mergedProps.label
})
}, mergedProps));
return render(Input);
};
InputFieldInner.Field = Input;
const PasswordInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'password',
autoComplete: 'off'
}, props);
const render = useDecorator(Object.assign({
placeholder: formatMessage({
id: 'PleaseInput'
}, {
label: mergedProps.label
})
}, mergedProps));
return render(Input.Password);
};
PasswordInner.Field = Input.Password;
const InputField = withLocale(InputFieldInner);
InputField.Password = withLocale(PasswordInner);
const _excluded$2 = ["soFarText", "soFarValue", "picker"];
const {
useOnChange: useOnChange$6
} = hooks;
const PickerTodayInner = _ref => {
let {
soFarText,
soFarValue = 'soFar',
picker = 'date'
} = _ref,
props = _objectWithoutPropertiesLoose(_ref, _excluded$2);
const {
formatMessage
} = useIntl();
const [data, onChange] = useControlValue(props);
const [openStart, setOpenStart] = useState(false);
const [openEnd, setOpenEnd] = useState(false);
const [tempStart, setTempStart] = useState(null); // 临时存储开始时间
const [tempEnd, setTempEnd] = useState(null); // 临时存储结束时间
const [hovering, setHovering] = useState(false); // hover 状态
const containerRef = useRef(null);
const isSwitchingRef = useRef(false); // 标记是否正在切换到结束时间选择
const soFarLabel = soFarText || formatMessage({
id: 'SoFar'
});
// 根据 picker 类型获取格式化字符串
const formatPattern = useMemo(() => {
const patterns = {
date: 'YYYY-MM-DD',
week: 'YYYY-wo',
month: 'YYYY-MM',
year: 'YYYY'
};
return patterns[picker] || 'YYYY-MM-DD';
}, [picker]);
// 判断是否有值
const hasValue = useMemo(() => {
const [start, end] = data || [];
return !!(start || end);
}, [data]);
// 判断是否为"至今"
const isSoFar = useMemo(() => {
const [, end] = data || [];
return end === soFarValue;
}, [data, soFarValue]);
// 解析数据
const parsedValue = useMemo(() => {
const [start, end] = data || [];
return {
start: start ? dayjs(start) : null,
end: isSoFar ? null : end ? dayjs(end) : null
};
}, [data, isSoFar]);
// 显示文本 - 弹窗打开时显示临时值,关闭后显示实际值
const displayText = useMemo(() => {
// 开始时间:有临时值时显示临时值
const startText = tempStart ? tempStart.format(formatPattern) : parsedValue.start ? parsedValue.start.format(formatPattern) : '';
// 结束时间:有临时值时显示临时值
const endText = tempEnd ? tempEnd.format(formatPattern) : isSoFar ? soFarLabel : parsedValue.end ? parsedValue.end.format(formatPattern) : '';
return {
start: startText,
end: endText
};
}, [parsedValue, isSoFar, soFarLabel, tempStart, tempEnd, formatPattern]);
// 点击整个Input框时,先弹出开始时间选择
const handleInputClick = useCallback(() => {
const [start] = data || [];
setTempStart(start ? dayjs(start) : null);
setOpenStart(true);
setOpenEnd(false);
}, [data]);
// 清除值
const handleClear = useCallback(e => {
e.stopPropagation();
onChange([]);
setTempStart(null);
setTempEnd(null);
setOpenStart(false);
setOpenEnd(false);
}, [onChange]);
// 处理开始时间变化
const handleStartChange = useCallback(date => {
setTempStart(date);
isSwitchingRef.current = true; // 标记正在切换
setOpenStart(false);
setTimeout(() => {
setOpenEnd(true);
isSwitchingRef.current = false;
}, 100);
}, []);
// 处理结束时间变化(选择日期时实时更新显示,选择后确认值)
const handleEndChange = useCallback(date => {
if (date) {
// 选择了有效日期,确认值
if (!tempStart) return;
onChange([tempStart.toISOString(), date.toISOString()]);
setOpenEnd(false);
setTempStart(null);
setTempEnd(null);
} else {
// 清空选择
setTempEnd(null);
}
}, [tempStart, onChange]);
// 点击"至今"按钮
const handleSoFarClick = useCallback(() => {
if (!tempStart) return;
onChange([tempStart.toISOString(), soFarValue]);
setOpenEnd(false);
setTempStart(null);
setTempEnd(null);
}, [tempStart, onChange, soFarValue]);
// 处理开始时间弹窗关闭(未完成选择)
const handleStartOpenChange = useCallback(open => {
if (!open) {
setOpenStart(false);
// 如果不是正在切换到结束时间,则清空临时状态
if (!isSwitchingRef.current) {
setTempStart(null);
}
}
}, []);
// 处理结束时间弹窗关闭(未完成选择)
const handleEndOpenChange = useCallback(open => {
if (!open) {
setOpenEnd(false);
// 关闭结束时间弹窗时,不修改field值
setTempStart(null);
setTempEnd(null);
}
}, []);
// 结束时间面板变化时更新临时值(用于实时显示)
const handleEndPanelChange = useCallback(date => {
setTempEnd(date);
}, []);
// 空的 footer(用于开始时间,保持高度一致)
const renderEmptyFooter = useCallback(() => /*#__PURE__*/React.createElement("div", {
style: {
height: 40
}
}), []);
// 带至今按钮的 footer
const renderExtraFooter = useCallback(() => {
const isCurrentSoFar = !tempStart && isSoFar;
return /*#__PURE__*/React.createElement("div", {
style: {
textAlign: 'right'
}
}, /*#__PURE__*/React.createElement(Button, {
type: isCurrentSoFar ? 'primary' : 'default',
onClick: handleSoFarClick
}, soFarLabel));
}, [isSoFar, tempStart, handleSoFarClick, soFarLabel]);
// 结束时间的可选日期(不能早于开始时间)
const endDisabledDate = useCallback(current => {
if (!tempStart) return false;
const startOfMap = {
date: 'day',
week: 'week',
month: 'month',
year: 'year'
};
const unit = startOfMap[picker] || 'day';
return current && current < tempStart.startOf(unit);
}, [tempStart, picker]);
return /*#__PURE__*/React.createElement("div", {
className: "date-picker-today-container",
ref: containerRef
}, /*#__PURE__*/React.createElement("div", {
className: "date-picker-today-inputs",
onClick: handleInputClick,
onMouseEnter: () => setHovering(true),
onMouseLeave: () => setHovering(false)
}, /*#__PURE__*/React.createElement("div", {
className: "date-picker-today-start"
}, /*#__PURE__*/React.createElement("input", {
readOnly: true,
placeholder: formatMessage({
id: 'StartDate'
}),
value: displayText.start,
className: "ant-input"
})), /*#__PURE__*/React.createElement("span", {
className: "date-picker-today-separator"
}, "~"), /*#__PURE__*/React.createElement("div", {
className: "date-picker-today-end"
}, /*#__PURE__*/React.createElement("input", {
readOnly: true,
placeholder: formatMessage({
id: 'EndDate'
}),
value: displayText.end,
className: `ant-input ${isSoFar ? 'so-far-active' : ''}`
})), /*#__PURE__*/React.createElement("span", {
className: "date-picker-today-suffix"
}, hasValue && hovering ? /*#__PURE__*/React.createElement(CloseCircleFilled, {
className: "date-picker-today-clear",
onClick: handleClear
}) : /*#__PURE__*/React.createElement(CalendarOutlined, null))), /*#__PURE__*/React.createElement(DatePicker, {
picker: picker,
open: openStart,
onOpenChange: handleStartOpenChange,
value: tempStart || parsedValue.start,
onChange: handleStartChange,
renderExtraFooter: renderEmptyFooter,
showNow: false,
getPopupContainer: () => containerRef.current || document.body,
style: {
width: 0,
height: 0,
visibility: 'hidden',
position: 'absolute'
},
placement: "bottomLeft"
}), /*#__PURE__*/React.createElement(DatePicker, {
picker: picker,
open: openEnd,
onOpenChange: handleEndOpenChange,
value: tempEnd || parsedValue.end,
onChange: handleEndChange,
onPanelChange: handleEndPanelChange,
disabledDate: endDisabledDate,
renderExtraFooter: renderExtraFooter,
showNow: false,
getPopupContainer: () => containerRef.current || document.body,
style: {
width: 0,
height: 0,
visibility: 'hidden',
position: 'absolute'
},
placement: "bottomLeft"
}));
};
const PickerToday = withLocale(PickerTodayInner);
const RangePickerToday = props => {
props = Object.assign({}, {
fieldName: 'rangePickerToday'
}, props);
const render = useOnChange$6(props);
return render(PickerToday);
};
RangePickerToday.Field = PickerToday;
const {
useOnChange: useOnChange$5
} = hooks;
const {
MonthPicker,
RangePicker,
WeekPicker
} = DatePicker;
const DatePickerInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'datePicker'
}, props);
const render = useOnChange$5(Object.assign({
placeholder: formatMessage({
id: 'PleaseSelect'
}, {
label: mergedProps.label || ''
})
}, mergedProps));
return render(DatePicker);
};
DatePickerInner.Field = DatePicker;
const MonthPickerInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'monthDatePicker'
}, props);
const render = useOnChange$5(Object.assign({
placeholder: [formatMessage({
id: 'StartTime'
}), formatMessage({
id: 'EndTime'
})]
}, mergedProps));
return render(MonthPicker);
};
MonthPickerInner.Field = MonthPicker;
const RangePickerInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'rangeDatePicker'
}, props);
const render = useOnChange$5(Object.assign({
placeholder: [formatMessage({
id: 'StartTime'
}), formatMessage({
id: 'EndTime'
})]
}, mergedProps));
return render(RangePicker);
};
RangePickerInner.Field = RangePicker;
const WeekPickerInner = props => {
const {
formatMessage
} = useIntl();
const mergedProps = Object.assign({}, {
fieldName: 'weekDatePicker'
}, props);
const render = useOnChange$5(Object.assign({
placeholder: [formatMessage({
id: 'StartTime'
}), formatMessage({
id: 'EndTime'
})]
}, mergedProps));
return render(WeekPicker);
};
WeekPickerInner.Field = WeekPicker;
const _DatePicker = withLocale(DatePickerInner);
_DatePicker.MonthPicker = withLocale(MonthPickerInner);
_DatePicker.RangePicker = withLocale(RangePickerInner);
_DatePicker.WeekPicker = withLocale(WeekPickerInner);
const {
useOnChange: useOnChange$4
} = hooks;
const _CheckboxGroup = Checkbox$1.Group;
const CheckboxGroup = props => {
props = Object.assign({}, {
fieldName: 'checkboxGroup'
}, props);
const render = useOnChange$4(props);
return render(_CheckboxGroup);
};
CheckboxGroup.Checkbox = Checkbox$1;
CheckboxGroup.Field = _CheckboxGroup;
const {
useOnChange: useOnChange$3,
useCheckedToValue
} = hooks;
const {
withChecked
} = hoc;
const WithCheckbox = withChecked(Checkbox$1);
const Checkbox = props => {
props = Object.assign({}, {
fieldName: 'checkbox'
}, props);
const checkedProps = useCheckedToValue(Object.assign({}, props, {
onChange: e => {
e.target.type = 'checkbox';
props.onChange && props.onChange(e);
}
}));
const render = useOnChange$3(checkedProps);
return render(WithCheckbox);
};
Checkbox.Field = Checkbox$1;
const {
useOnChange: useOnChange$2
} = hooks;
const Cascader = props => {
props = Object.assign({}, {
fieldName: 'cascader'
}, props);
const render = useOnChange$2(props);
return render(Cascader$1);
};
Cascader.Field = Cascader$1;
const {
useOnChange: useOnChange$1
} = hooks;
const Rate = props => {
props = Object.assign({}, {
fieldName: 'rate'
}, props);
const render = useOnChange$1(Object.assign({}, props));
return render(Rate$1);
};
Rate.Field = Rate$1;
const {
useOnChange
} = hooks;
const Slider = props => {
props = Object.assign({}, {
fieldName: 'slider'
}, props);
const render = useOnChange(Object.assign({}, props));
return render(Slider$1);
};
Slider.Field = Slider$1;
const ResetButton = props => {
const resetProps = useReset();
return /*#__PURE__*/React.createElement(Button, _extends({}, resetProps, props));
};
const _excluded$1 = ["type", "realtime", "disabled"],
_excluded2 = ["isPass", "isLoading"];
const SubmitButton = _ref => {
let {
type = 'primary',
realtime,
disabled = false
} = _ref,
props = _objectWithoutPropertiesLoose(_ref, _excluded$1);
const _useSubmit = useSubmit(props),
{
isPass,
isLoading
} = _useSubmit,
submitProps = _objectWithoutPropertiesLoose(_useSubmit, _excluded2);
return /*#__PURE__*/React.createElement(Button, _extends({
type: type,
loading: isLoading,
disabled: disabled || (realtime ? !isPass : false)
}, props, submitProps));
};
const _excluded = ["onClick"];
const {
useCacheRemove
} = hooks;
const CancelButton = _ref => {
let {
onClick
} = _ref,
props = _objectWithoutPropertiesLoose(_ref, _excluded);
const remove = useCacheRemove();
return /*#__PURE__*/React.createElement(Button, _extends({}, props, {
onClick: (...args) => {
remove();
onClick && onClick(...args);
}
}));
};
const FormAntd = Form;
const fields = {
TreeSelect: _TreeSelect,
TimePicker: _TimePicker,
TextArea,
Switch,
Select: _Select,
RadioGroup: _RadioGroup,
InputNumber: InputNumberField,
Input: InputField,
DatePickerToday: RangePickerToday,
DatePicker: _DatePicker,
CheckboxGroup,
Checkbox,
Cascader,
Rate,
Slider
};
export { CancelButton, Cascader, Checkbox, CheckboxGroup, _DatePicker as DatePicker, RangePickerToday as DatePickerToday, FormAntd, InputField as Input, InputNumberField as InputNumber, _RadioGroup as RadioGroup, Rate, ResetButton, _Select as Select, Slider, SubmitButton, Switch, TextArea, _TimePicker as TimePicker, _TreeSelect as TreeSelect, Form as default, fields, preset };
//# sourceMappingURL=index.modern.js.map