@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
298 lines • 10.6 kB
JavaScript
import _noop from "lodash/noop";
var __rest = this && this.__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import FormFoundation from '@douyinfe/semi-foundation/lib/es/form/foundation';
import { strings, cssClasses } from '@douyinfe/semi-foundation/lib/es/form/constants';
import { getUuidv4 } from '@douyinfe/semi-foundation/lib/es/utils/uuid';
import warning from '@douyinfe/semi-foundation/lib/es/utils/warning';
import BaseComponent from '../_base/baseComponent';
import { FormStateContext, FormApiContext, FormUpdaterContext } from './context';
import { isEmptyChildren } from '../_base/reactUtils';
import Row from '../grid/row';
import { cloneDeep } from '../_utils/index';
import Slot from './slot';
import Section from './section';
import Label from './label';
import ErrorMessage from './errorMessage';
import FormInputGroup from './group';
import '@douyinfe/semi-foundation/lib/es/form/form.css';
import { FormInput, FormInputNumber, FormTextArea, FormSelect, FormCheckboxGroup, FormCheckbox, FormRadioGroup, FormRadio, FormDatePicker, FormSwitch, FormSlider, FormTimePicker, FormTreeSelect, FormCascader, FormRating, FormAutoComplete, FormUpload, FormTagInput } from './field';
const prefix = cssClasses.PREFIX;
class Form extends BaseComponent {
constructor(props) {
super(props);
this.state = {
formId: ''
};
warning(Boolean(props.component && props.render), '[Semi Form] You should not use <Form component> and <Form render> in ths same time; <Form render> will be ignored');
warning(props.component && props.children && !isEmptyChildren(props.children), '[Semi Form] You should not use <Form component> and <Form>{children}</Form> in ths same time; <Form>{children}</Form> will be ignored');
warning(props.render && props.children && !isEmptyChildren(props.children), '[Semi Form] You should not use <Form render> and <Form>{children}</Form> in ths same time; <Form>{children}</Form> will be ignored');
this.submit = this.submit.bind(this);
this.reset = this.reset.bind(this);
this.foundation = new FormFoundation(this.adapter);
this.formApi = this.foundation.getFormApi();
if (this.props.getFormApi) {
this.props.getFormApi(this.formApi);
}
}
componentDidMount() {
this.foundation.init();
}
componentWillUnmount() {
this.foundation.destroy();
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
cloneDeep,
notifySubmit: (values, e) => {
this.props.onSubmit(values, e);
},
notifySubmitFail: (errors, values, e) => {
this.props.onSubmitFail(errors, values, e);
},
forceUpdate: callback => {
this.forceUpdate(callback);
},
notifyChange: formState => {
this.props.onChange(formState);
},
notifyValueChange: (values, changedValues) => {
this.props.onValueChange(values, changedValues);
},
notifyErrorChange: (errors, changedError) => {
this.props.onErrorChange(errors, changedError);
},
notifyReset: () => {
this.props.onReset();
},
initFormId: () => {
this.setState({
formId: getUuidv4()
});
},
getInitValues: () => this.props.initValues,
getFormProps: keys => {
if (typeof keys === 'undefined') {
return this.props;
} else if (typeof keys === 'string') {
return this.props[keys];
} else {
const props = {};
keys.forEach(key => {
props[key] = this.props[key];
});
return props;
}
},
getAllErrorDOM: () => {
const {
formId
} = this.state;
const {
id
} = this.props;
const xId = id ? id : formId;
return document.querySelectorAll(`form[x-form-id="${xId}"] .${cssClasses.PREFIX}-field-error-message`);
},
getFieldDOM: field => {
const {
formId
} = this.state;
const {
id
} = this.props;
const xId = id ? id : formId;
return document.querySelector(`form[x-form-id="${xId}"] .${cssClasses.PREFIX}-field[x-field-id="${field}"]`);
},
getFieldErrorDOM: field => {
const {
formId
} = this.state;
const {
id
} = this.props;
const xId = id ? id : formId;
let selector = `form[x-form-id="${xId}"] .${cssClasses.PREFIX}-field[x-field-id="${field}"] .${cssClasses.PREFIX}-field-error-message`;
return document.querySelector(selector);
}
});
}
get content() {
const {
children,
component,
render
} = this.props;
const formState = this.foundation.getFormState();
const props = {
formState,
formApi: this.foundation.getFormApi(),
values: formState.values
};
if (component) {
return /*#__PURE__*/React.createElement(component, props);
}
if (render) {
return render(props);
}
if (typeof children === 'function') {
return children(props);
}
return children;
}
submit(e) {
e.preventDefault();
if (this.props.stopPropagation && this.props.stopPropagation.submit) {
e.stopPropagation();
}
this.foundation.submit(e);
}
reset(e) {
e.preventDefault();
if (this.props.stopPropagation && this.props.stopPropagation.reset) {
e.stopPropagation();
}
this.foundation.reset();
}
render() {
const needClone = false;
const formState = this.foundation.getFormState(needClone);
const updaterApi = this.foundation.getModifyFormStateApi();
const {
formId
} = this.state;
const _a = this.props,
{
children,
getFormApi,
onChange,
onSubmit,
onSubmitFail,
onErrorChange,
onValueChange,
component,
render,
validateFields,
initValues,
layout,
style,
className,
labelPosition,
labelWidth,
labelAlign,
labelCol,
wrapperCol,
allowEmpty,
autoScrollToError,
showValidateIcon,
stopValidateWithError,
extraTextPosition,
id,
trigger
} = _a,
rest = __rest(_a, ["children", "getFormApi", "onChange", "onSubmit", "onSubmitFail", "onErrorChange", "onValueChange", "component", "render", "validateFields", "initValues", "layout", "style", "className", "labelPosition", "labelWidth", "labelAlign", "labelCol", "wrapperCol", "allowEmpty", "autoScrollToError", "showValidateIcon", "stopValidateWithError", "extraTextPosition", "id", "trigger"]);
const formCls = classNames(prefix, className, {
[prefix + '-vertical']: layout === 'vertical',
[prefix + '-horizontal']: layout === 'horizontal'
});
const shouldAppendRow = wrapperCol && labelCol;
const formContent = /*#__PURE__*/React.createElement("form", Object.assign({
style: style
}, rest, {
onReset: this.reset,
onSubmit: this.submit,
className: formCls,
id: id ? id : formId,
"x-form-id": id ? id : formId
}), this.content);
const withRowForm = /*#__PURE__*/React.createElement(Row, null, formContent);
return /*#__PURE__*/React.createElement(FormUpdaterContext.Provider, {
value: updaterApi
}, /*#__PURE__*/React.createElement(FormApiContext.Provider, {
value: this.formApi
}, /*#__PURE__*/React.createElement(FormStateContext.Provider, {
value: formState
}, shouldAppendRow ? withRowForm : formContent)));
}
}
Form.propTypes = {
'aria-label': PropTypes.string,
onSubmit: PropTypes.func,
onSubmitFail: PropTypes.func,
/* Triggered from update, including field mount/unmount/value change/blur/verification status change/error prompt change, input parameter is formState, currentField */
onChange: PropTypes.func,
onReset: PropTypes.func,
// Triggered when the value of the form is updated, only when the value of the subfield changes. The entry parameter is formState.values
onValueChange: PropTypes.func,
autoScrollToError: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
allowEmpty: PropTypes.bool,
className: PropTypes.string,
component: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
disabled: PropTypes.bool,
extraTextPosition: PropTypes.oneOf(strings.EXTRA_POS),
getFormApi: PropTypes.func,
initValues: PropTypes.object,
validateFields: PropTypes.func,
layout: PropTypes.oneOf(strings.LAYOUT),
labelPosition: PropTypes.oneOf(strings.LABEL_POS),
labelWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
labelAlign: PropTypes.oneOf(strings.LABEL_ALIGN),
labelCol: PropTypes.object,
render: PropTypes.func,
style: PropTypes.object,
showValidateIcon: PropTypes.bool,
stopValidateWithError: PropTypes.bool,
stopPropagation: PropTypes.shape({
submit: PropTypes.bool,
reset: PropTypes.bool
}),
id: PropTypes.string,
wrapperCol: PropTypes.object,
trigger: PropTypes.oneOfType([PropTypes.oneOf(['blur', 'change', 'custom', 'mount']), PropTypes.arrayOf(PropTypes.oneOf(['blur', 'change', 'custom', 'mount']))])
};
Form.defaultProps = {
onChange: _noop,
onSubmitFail: _noop,
onSubmit: _noop,
onReset: _noop,
onValueChange: _noop,
onErrorChange: _noop,
layout: 'vertical',
labelPosition: 'top',
allowEmpty: false,
autoScrollToError: false,
showValidateIcon: true
};
Form.Input = FormInput;
Form.TextArea = FormTextArea;
Form.InputNumber = FormInputNumber;
Form.Select = FormSelect;
Form.Checkbox = FormCheckbox;
Form.CheckboxGroup = FormCheckboxGroup;
Form.Radio = FormRadio;
Form.RadioGroup = FormRadioGroup;
Form.DatePicker = FormDatePicker;
Form.TimePicker = FormTimePicker;
Form.Switch = FormSwitch;
Form.Slider = FormSlider;
Form.TreeSelect = FormTreeSelect;
Form.Cascader = FormCascader;
Form.Rating = FormRating;
Form.AutoComplete = FormAutoComplete;
Form.Upload = FormUpload;
Form.TagInput = FormTagInput;
Form.Slot = Slot;
Form.ErrorMessage = ErrorMessage;
Form.InputGroup = FormInputGroup;
Form.Label = Label;
Form.Section = Section;
export default Form;