diff --git a/components/form-model/Form.jsx b/components/form-model/Form.jsx
deleted file mode 100755
index 111b19db6..000000000
--- a/components/form-model/Form.jsx
+++ /dev/null
@@ -1,282 +0,0 @@
-import { inject, provide } from 'vue';
-import PropTypes from '../_util/vue-types';
-import classNames from 'classnames';
-import { ColProps } from '../grid/Col';
-import isRegExp from 'lodash/isRegExp';
-import warning from '../_util/warning';
-import FormItem from './FormItem';
-import { initDefaultProps, getSlot } from '../_util/props-util';
-import { ConfigConsumerProps } from '../config-provider';
-import { getNamePath, containsNamePath } from './utils/valueUtil';
-import { defaultValidateMessages } from './utils/messages';
-import { allPromiseFinish } from './utils/asyncUtil';
-import { toArray } from './utils/typeUtil';
-import isEqual from 'lodash/isEqual';
-import scrollIntoView from 'scroll-into-view-if-needed';
-
-export const FormProps = {
- layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
- labelCol: PropTypes.shape(ColProps).loose,
- wrapperCol: PropTypes.shape(ColProps).loose,
- colon: PropTypes.bool,
- labelAlign: PropTypes.oneOf(['left', 'right']),
- prefixCls: PropTypes.string,
- hideRequiredMark: PropTypes.bool,
- model: PropTypes.object,
- rules: PropTypes.object,
- validateMessages: PropTypes.any,
- validateOnRuleChange: PropTypes.bool,
- // 提交失败自动滚动到第一个错误字段
- scrollToFirstError: PropTypes.bool,
- onFinish: PropTypes.func,
- onFinishFailed: PropTypes.func,
- name: PropTypes.name,
-};
-
-export const ValidationRule = {
- /** validation error message */
- message: PropTypes.string,
- /** built-in validation type, available options: https://github.com/yiminghe/async-validator#type */
- type: PropTypes.string,
- /** indicates whether field is required */
- required: PropTypes.boolean,
- /** treat required fields that only contain whitespace as errors */
- whitespace: PropTypes.boolean,
- /** validate the exact length of a field */
- len: PropTypes.number,
- /** validate the min length of a field */
- min: PropTypes.number,
- /** validate the max length of a field */
- max: PropTypes.number,
- /** validate the value from a list of possible values */
- enum: PropTypes.oneOfType([String, PropTypes.arrayOf(String)]),
- /** validate from a regular expression */
- pattern: PropTypes.custom(isRegExp),
- /** transform a value before validation */
- transform: PropTypes.func,
- /** custom validate function (Note: callback must be called) */
- validator: PropTypes.func,
-};
-
-function isEqualName(name1, name2) {
- return isEqual(toArray(name1), toArray(name2));
-}
-
-const Form = {
- name: 'AFormModel',
- inheritAttrs: false,
- props: initDefaultProps(FormProps, {
- layout: 'horizontal',
- hideRequiredMark: false,
- colon: true,
- }),
- Item: FormItem,
- created() {
- this.fields = [];
- this.form = undefined;
- this.lastValidatePromise = null;
- provide('FormContext', this);
- },
- setup() {
- return {
- configProvider: inject('configProvider', ConfigConsumerProps),
- };
- },
- watch: {
- rules() {
- if (this.validateOnRuleChange) {
- this.validateFields();
- }
- },
- },
- computed: {
- vertical() {
- return this.layout === 'vertical';
- },
- },
- methods: {
- addField(field) {
- if (field) {
- this.fields.push(field);
- }
- },
- removeField(field) {
- if (field.fieldName) {
- this.fields.splice(this.fields.indexOf(field), 1);
- }
- },
- handleSubmit(e) {
- e.preventDefault();
- e.stopPropagation();
- this.$emit('submit', e);
- const res = this.validateFields();
- res
- .then(values => {
- this.$emit('finish', values);
- })
- .catch(errors => {
- this.handleFinishFailed(errors);
- });
- },
- getFieldsByNameList(nameList) {
- const provideNameList = !!nameList;
- const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
- if (!provideNameList) {
- return this.fields;
- } else {
- return this.fields.filter(
- field => namePathList.findIndex(namePath => isEqualName(namePath, field.fieldName)) > -1,
- );
- }
- },
- resetFields(name) {
- if (!this.model) {
- warning(false, 'Form', 'model is required for resetFields to work.');
- return;
- }
- this.getFieldsByNameList(name).forEach(field => {
- field.resetField();
- });
- },
- clearValidate(name) {
- this.getFieldsByNameList(name).forEach(field => {
- field.clearValidate();
- });
- },
- handleFinishFailed(errorInfo) {
- const { scrollToFirstError } = this;
- this.$emit('finishFailed', errorInfo);
- if (scrollToFirstError && errorInfo.errorFields.length) {
- this.scrollToField(errorInfo.errorFields[0].name);
- }
- },
- validate() {
- return this.validateField(...arguments);
- },
- scrollToField(name, options = {}) {
- const fields = this.getFieldsByNameList([name]);
- if (fields.length) {
- const fieldId = fields[0].fieldId;
- const node = fieldId ? document.getElementById(fieldId) : null;
-
- if (node) {
- scrollIntoView(node, {
- scrollMode: 'if-needed',
- block: 'nearest',
- ...options,
- });
- }
- }
- },
- // eslint-disable-next-line no-unused-vars
- getFieldsValue(nameList = true) {
- const values = {};
- this.fields.forEach(({ fieldName, fieldValue }) => {
- values[fieldName] = fieldValue;
- });
- if (nameList === true) {
- return values;
- } else {
- const res = {};
- toArray(nameList).forEach(namePath => (res[namePath] = values[namePath]));
- return res;
- }
- },
- validateFields(nameList, options) {
- if (!this.model) {
- warning(false, 'Form', 'model is required for validateFields to work.');
- return;
- }
- const provideNameList = !!nameList;
- const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
-
- // Collect result in promise list
- const promiseList = [];
-
- this.fields.forEach(field => {
- // Add field if not provide `nameList`
- if (!provideNameList) {
- namePathList.push(field.getNamePath());
- }
-
- // Skip if without rule
- if (!field.getRules().length) {
- return;
- }
-
- const fieldNamePath = field.getNamePath();
-
- // Add field validate rule in to promise list
- if (!provideNameList || containsNamePath(namePathList, fieldNamePath)) {
- const promise = field.validateRules({
- validateMessages: {
- ...defaultValidateMessages,
- ...this.validateMessages,
- },
- ...options,
- });
-
- // Wrap promise with field
- promiseList.push(
- promise
- .then(() => ({ name: fieldNamePath, errors: [] }))
- .catch(errors =>
- Promise.reject({
- name: fieldNamePath,
- errors,
- }),
- ),
- );
- }
- });
-
- const summaryPromise = allPromiseFinish(promiseList);
- this.lastValidatePromise = summaryPromise;
-
- const returnPromise = summaryPromise
- .then(() => {
- if (this.lastValidatePromise === summaryPromise) {
- return Promise.resolve(this.getFieldsValue(namePathList));
- }
- return Promise.reject([]);
- })
- .catch(results => {
- const errorList = results.filter(result => result && result.errors.length);
- return Promise.reject({
- values: this.getFieldsValue(namePathList),
- errorFields: errorList,
- outOfDate: this.lastValidatePromise !== summaryPromise,
- });
- });
-
- // Do not throw in console
- returnPromise.catch(e => e);
-
- return returnPromise;
- },
- validateField() {
- return this.validateFields(...arguments);
- },
- },
-
- render() {
- const { prefixCls: customizePrefixCls, hideRequiredMark, layout, handleSubmit } = this;
- const getPrefixCls = this.configProvider.getPrefixCls;
- const prefixCls = getPrefixCls('form', customizePrefixCls);
- const { class: className, onSubmit: originSubmit, ...restProps } = this.$attrs;
-
- const formClassName = classNames(prefixCls, className, {
- [`${prefixCls}-horizontal`]: layout === 'horizontal',
- [`${prefixCls}-vertical`]: layout === 'vertical',
- [`${prefixCls}-inline`]: layout === 'inline',
- [`${prefixCls}-hide-required-mark`]: hideRequiredMark,
- });
- return (
-
- );
- },
-};
-
-export default Form;
diff --git a/components/form-model/FormItem.jsx b/components/form-model/FormItem.jsx
deleted file mode 100644
index d3e21f8b2..000000000
--- a/components/form-model/FormItem.jsx
+++ /dev/null
@@ -1,498 +0,0 @@
-import { inject, provide, Transition } from 'vue';
-import cloneDeep from 'lodash/cloneDeep';
-import PropTypes from '../_util/vue-types';
-import classNames from 'classnames';
-import getTransitionProps from '../_util/getTransitionProps';
-import Row from '../grid/Row';
-import Col, { ColProps } from '../grid/Col';
-import hasProp, {
- initDefaultProps,
- findDOMNode,
- getComponent,
- getOptionProps,
- getEvents,
- isValidElement,
- getSlot,
-} from '../_util/props-util';
-import BaseMixin from '../_util/BaseMixin';
-import { ConfigConsumerProps } from '../config-provider';
-import { cloneElement } from '../_util/vnode';
-import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
-import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
-import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
-import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
-import { validateRules } from './utils/validateUtil';
-import { getNamePath } from './utils/valueUtil';
-import { toArray } from './utils/typeUtil';
-import { warning } from '../vc-util/warning';
-
-const iconMap = {
- success: CheckCircleFilled,
- warning: ExclamationCircleFilled,
- error: CloseCircleFilled,
- validating: LoadingOutlined,
-};
-
-function getPropByPath(obj, namePathList, strict) {
- let tempObj = obj;
-
- const keyArr = namePathList;
- let i = 0;
- try {
- for (let len = keyArr.length; i < len - 1; ++i) {
- if (!tempObj && !strict) break;
- let key = keyArr[i];
- if (key in tempObj) {
- tempObj = tempObj[key];
- } else {
- if (strict) {
- throw Error('please transfer a valid name path to form item!');
- }
- break;
- }
- }
- if (strict && !tempObj) {
- throw Error('please transfer a valid name path to form item!');
- }
- } catch (error) {
- console.error('please transfer a valid name path to form item!');
- }
-
- return {
- o: tempObj,
- k: keyArr[i],
- v: tempObj ? tempObj[keyArr[i]] : undefined,
- };
-}
-export const FormItemProps = {
- id: PropTypes.string,
- htmlFor: PropTypes.string,
- prefixCls: PropTypes.string,
- label: PropTypes.any,
- help: PropTypes.any,
- extra: PropTypes.any,
- labelCol: PropTypes.shape(ColProps).loose,
- wrapperCol: PropTypes.shape(ColProps).loose,
- hasFeedback: PropTypes.bool,
- colon: PropTypes.bool,
- labelAlign: PropTypes.oneOf(['left', 'right']),
- prop: PropTypes.oneOfType([Array, String, Number]),
- name: PropTypes.oneOfType([Array, String, Number]),
- rules: PropTypes.oneOfType([Array, Object]),
- autoLink: PropTypes.bool,
- required: PropTypes.bool,
- validateFirst: PropTypes.bool,
- validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
-};
-
-export default {
- name: 'AFormModelItem',
- mixins: [BaseMixin],
- inheritAttrs: false,
- __ANT_NEW_FORM_ITEM: true,
- props: initDefaultProps(FormItemProps, {
- hasFeedback: false,
- autoLink: true,
- }),
- setup() {
- return {
- isFormItemChildren: inject('isFormItemChildren', false),
- configProvider: inject('configProvider', ConfigConsumerProps),
- FormContext: inject('FormContext', {}),
- };
- },
- data() {
- warning(hasProp(this, 'prop'), `\`prop\` is deprecated. Please use \`name\` instead.`);
- return {
- validateState: this.validateStatus,
- validateMessage: '',
- validateDisabled: false,
- validator: {},
- helpShow: false,
- errors: [],
- };
- },
-
- computed: {
- fieldName() {
- return this.name || this.prop;
- },
- namePath() {
- return getNamePath(this.fieldName);
- },
- fieldId() {
- if (this.id) {
- return this.id;
- } else if (!this.namePath.length) {
- return undefined;
- } else {
- const formName = this.FormContext.name;
- const mergedId = this.namePath.join('_');
- return formName ? `${formName}_${mergedId}` : mergedId;
- }
- },
- fieldValue() {
- const model = this.FormContext.model;
- if (!model || !this.fieldName) {
- return;
- }
- return getPropByPath(model, this.namePath, true).v;
- },
- isRequired() {
- let rules = this.getRules();
- let isRequired = false;
- if (rules && rules.length) {
- rules.every(rule => {
- if (rule.required) {
- isRequired = true;
- return false;
- }
- return true;
- });
- }
- return isRequired || this.required;
- },
- },
- watch: {
- validateStatus(val) {
- this.validateState = val;
- },
- },
- created() {
- provide('isFormItemChildren', true);
- },
- mounted() {
- if (this.fieldName) {
- const { addField } = this.FormContext;
- addField && addField(this);
- this.initialValue = cloneDeep(this.fieldValue);
- }
- },
- beforeUnmount() {
- const { removeField } = this.FormContext;
- removeField && removeField(this);
- },
- methods: {
- getNamePath() {
- const { fieldName } = this;
- const { prefixName = [] } = this.FormContext;
-
- return fieldName !== undefined ? [...prefixName, ...this.namePath] : [];
- },
- validateRules(options) {
- const { validateFirst = false, messageVariables } = this.$props;
- const { triggerName } = options || {};
- const namePath = this.getNamePath();
-
- let filteredRules = this.getRules();
- if (triggerName) {
- filteredRules = filteredRules.filter(rule => {
- const { trigger } = rule;
- if (!trigger) {
- return true;
- }
- const triggerList = toArray(trigger);
- return triggerList.includes(triggerName);
- });
- }
- if (!filteredRules.length) {
- return Promise.resolve();
- }
- const promise = validateRules(
- namePath,
- this.fieldValue,
- filteredRules,
- options,
- validateFirst,
- messageVariables,
- );
- this.validateState = 'validating';
- this.errors = [];
-
- promise
- .catch(e => e)
- .then((errors = []) => {
- if (this.validateState === 'validating') {
- this.validateState = errors.length ? 'error' : 'success';
- this.validateMessage = errors[0];
- this.errors = errors;
- }
- });
-
- return promise;
- },
- getRules() {
- let formRules = this.FormContext.rules;
- const selfRules = this.rules;
- const requiredRule =
- this.required !== undefined ? { required: !!this.required, trigger: 'change' } : [];
- const prop = getPropByPath(formRules, this.namePath);
- formRules = formRules ? prop.o[prop.k] || prop.v : [];
- return [].concat(selfRules || formRules || []).concat(requiredRule);
- },
- getFilteredRule(trigger) {
- const rules = this.getRules();
- return rules
- .filter(rule => {
- if (!rule.trigger || trigger === '') return true;
- if (Array.isArray(rule.trigger)) {
- return rule.trigger.indexOf(trigger) > -1;
- } else {
- return rule.trigger === trigger;
- }
- })
- .map(rule => ({ ...rule }));
- },
- onFieldBlur() {
- this.validateRules({ triggerName: 'blur' });
- },
- onFieldChange() {
- if (this.validateDisabled) {
- this.validateDisabled = false;
- return;
- }
- this.validateRules({ triggerName: 'change' });
- },
- clearValidate() {
- this.validateState = '';
- this.validateMessage = '';
- this.validateDisabled = false;
- },
- resetField() {
- this.validateState = '';
- this.validateMessage = '';
- const model = this.FormContext.model || {};
- const value = this.fieldValue;
- const prop = getPropByPath(model, this.namePath, true);
- this.validateDisabled = true;
- if (Array.isArray(value)) {
- prop.o[prop.k] = [].concat(this.initialValue);
- } else {
- prop.o[prop.k] = this.initialValue;
- }
- // reset validateDisabled after onFieldChange triggered
- this.$nextTick(() => {
- this.validateDisabled = false;
- });
- },
- getHelpMessage() {
- const help = getComponent(this, 'help');
-
- return this.validateMessage || help;
- },
-
- onLabelClick() {
- const id = this.fieldId;
- if (!id) {
- return;
- }
- const formItemNode = findDOMNode(this);
- const control = formItemNode.querySelector(`[id="${id}"]`);
- if (control && control.focus) {
- control.focus();
- }
- },
-
- onHelpAnimEnd(_key, helpShow) {
- this.helpShow = helpShow;
- if (!helpShow) {
- this.$forceUpdate();
- }
- },
-
- renderHelp(prefixCls) {
- const help = this.getHelpMessage();
- const children = help ? (
-
- {help}
-
- ) : null;
- if (children) {
- this.helpShow = !!children;
- }
- const transitionProps = getTransitionProps('show-help', {
- onAfterEnter: () => this.onHelpAnimEnd('help', true),
- onAfterLeave: () => this.onHelpAnimEnd('help', false),
- });
- return (
-
- {children}
-
- );
- },
-
- renderExtra(prefixCls) {
- const extra = getComponent(this, 'extra');
- return extra ? : null;
- },
-
- renderValidateWrapper(prefixCls, c1, c2, c3) {
- const validateStatus = this.validateState;
-
- let classes = `${prefixCls}-item-control`;
- if (validateStatus) {
- classes = classNames(`${prefixCls}-item-control`, {
- 'has-feedback': this.hasFeedback || validateStatus === 'validating',
- 'has-success': validateStatus === 'success',
- 'has-warning': validateStatus === 'warning',
- 'has-error': validateStatus === 'error',
- 'is-validating': validateStatus === 'validating',
- });
- }
- const IconNode = validateStatus && iconMap[validateStatus];
-
- const icon =
- this.hasFeedback && IconNode ? (
-
-
-
- ) : null;
- return (
-
-
- {c1}
- {icon}
-
- {c2}
- {c3}
-
- );
- },
-
- renderWrapper(prefixCls, children) {
- const { wrapperCol: contextWrapperCol } = this.isFormItemChildren ? {} : this.FormContext;
- const { wrapperCol } = this;
- const mergedWrapperCol = wrapperCol || contextWrapperCol || {};
- const { style, id, ...restProps } = mergedWrapperCol;
- const className = classNames(`${prefixCls}-item-control-wrapper`, mergedWrapperCol.class);
- const colProps = {
- ...restProps,
- class: className,
- key: 'wrapper',
- style,
- id,
- };
- return {children};
- },
-
- renderLabel(prefixCls) {
- const {
- vertical,
- labelAlign: contextLabelAlign,
- labelCol: contextLabelCol,
- colon: contextColon,
- } = this.FormContext;
- const { labelAlign, labelCol, colon, fieldId, htmlFor } = this;
- const label = getComponent(this, 'label');
- const required = this.isRequired;
- const mergedLabelCol = labelCol || contextLabelCol || {};
-
- const mergedLabelAlign = labelAlign || contextLabelAlign;
- const labelClsBasic = `${prefixCls}-item-label`;
- const labelColClassName = classNames(
- labelClsBasic,
- mergedLabelAlign === 'left' && `${labelClsBasic}-left`,
- mergedLabelCol.class,
- );
- const {
- class: labelColClass,
- style: labelColStyle,
- id: labelColId,
- ...restProps
- } = mergedLabelCol;
- let labelChildren = label;
- // Keep label is original where there should have no colon
- const computedColon = colon === true || (contextColon !== false && colon !== false);
- const haveColon = computedColon && !vertical;
- // Remove duplicated user input colon
- if (haveColon && typeof label === 'string' && label.trim() !== '') {
- labelChildren = label.replace(/[::]\s*$/, '');
- }
-
- const labelClassName = classNames({
- [`${prefixCls}-item-required`]: required,
- [`${prefixCls}-item-no-colon`]: !computedColon,
- });
- const colProps = {
- ...restProps,
- class: labelColClassName,
- key: 'label',
- style: labelColStyle,
- id: labelColId,
- };
-
- return label ? (
-
-
-
- ) : null;
- },
- renderChildren(prefixCls, child) {
- return [
- this.renderLabel(prefixCls),
- this.renderWrapper(
- prefixCls,
- this.renderValidateWrapper(
- prefixCls,
- child,
- this.renderHelp(prefixCls),
- this.renderExtra(prefixCls),
- ),
- ),
- ];
- },
- renderFormItem(child) {
- const { prefixCls: customizePrefixCls } = this.$props;
- const { class: className, ...restProps } = this.$attrs;
- const getPrefixCls = this.configProvider.getPrefixCls;
- const prefixCls = getPrefixCls('form', customizePrefixCls);
- const children = this.renderChildren(prefixCls, child);
- const itemClassName = {
- [className]: className,
- [`${prefixCls}-item`]: true,
- [`${prefixCls}-item-with-help`]: this.helpShow,
- };
-
- return (
-
- {children}
-
- );
- },
- },
- render() {
- const { autoLink } = getOptionProps(this);
- const children = getSlot(this);
- let firstChildren = children[0];
- if (this.fieldName && autoLink && isValidElement(firstChildren)) {
- const originalEvents = getEvents(firstChildren);
- const originalBlur = originalEvents.onBlur;
- const originalChange = originalEvents.onChange;
- firstChildren = cloneElement(firstChildren, {
- ...(this.fieldId ? { id: this.fieldId } : undefined),
- onBlur: (...args) => {
- originalBlur && originalBlur(...args);
- this.onFieldBlur();
- },
- onChange: (...args) => {
- if (Array.isArray(originalChange)) {
- for (let i = 0, l = originalChange.length; i < l; i++) {
- originalChange[i](...args);
- }
- } else if (originalChange) {
- originalChange(...args);
- }
- this.onFieldChange();
- },
- });
- }
- return this.renderFormItem([firstChildren, children.slice(1)]);
- },
-};
diff --git a/components/form-model/__tests__/__snapshots__/demo.test.js.snap b/components/form-model/__tests__/__snapshots__/demo.test.js.snap
deleted file mode 100644
index 5a248ee90..000000000
--- a/components/form-model/__tests__/__snapshots__/demo.test.js.snap
+++ /dev/null
@@ -1,282 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders ./antdv-demo/docs/form-model/demo/basic.md correctly 1`] = `
-
-`;
-
-exports[`renders ./antdv-demo/docs/form-model/demo/custom-validation.md correctly 1`] = `
-
-`;
-
-exports[`renders ./antdv-demo/docs/form-model/demo/dynamic-form-item.md correctly 1`] = `
-
-`;
-
-exports[`renders ./antdv-demo/docs/form-model/demo/horizontal-login.md correctly 1`] = `
-
-`;
-
-exports[`renders ./antdv-demo/docs/form-model/demo/layout.md correctly 1`] = `
-
-`;
-
-exports[`renders ./antdv-demo/docs/form-model/demo/validation.md correctly 1`] = `
-
-`;
diff --git a/components/form-model/__tests__/demo.test.js b/components/form-model/__tests__/demo.test.js
deleted file mode 100644
index 22a74f8a8..000000000
--- a/components/form-model/__tests__/demo.test.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import demoTest from '../../../tests/shared/demoTest';
-
-demoTest('form-model');
diff --git a/components/form-model/index.jsx b/components/form-model/index.jsx
deleted file mode 100644
index b63f33cf5..000000000
--- a/components/form-model/index.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Form from './Form';
-
-export { FormProps, ValidationRule } from './Form';
-export { FormItemProps } from './FormItem';
-
-/* istanbul ignore next */
-Form.install = function(app) {
- app.component(Form.name, Form);
- app.component(Form.Item.name, Form.Item);
-};
-
-export default Form;
diff --git a/components/form-old/Form.jsx b/components/form-old/Form.jsx
new file mode 100755
index 000000000..22dd38637
--- /dev/null
+++ b/components/form-old/Form.jsx
@@ -0,0 +1,287 @@
+import PropTypes from '../_util/vue-types';
+import classNames from 'classnames';
+import { ColProps } from '../grid/Col';
+import Vue from 'vue';
+import isRegExp from 'lodash/isRegExp';
+import warning from '../_util/warning';
+import createDOMForm from '../vc-form/src/createDOMForm';
+import createFormField from '../vc-form/src/createFormField';
+import FormItem from './FormItem';
+import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
+import { initDefaultProps, getListeners } from '../_util/props-util';
+import { ConfigConsumerProps } from '../config-provider';
+
+export const FormCreateOption = {
+ onFieldsChange: PropTypes.func,
+ onValuesChange: PropTypes.func,
+ mapPropsToFields: PropTypes.func,
+ validateMessages: PropTypes.any,
+ withRef: PropTypes.bool,
+ name: PropTypes.string,
+};
+
+// function create
+export const WrappedFormUtils = {
+ /** 获取一组输入控件的值,如不传入参数,则获取全部组件的值 */
+ getFieldsValue: PropTypes.func,
+ /** 获取一个输入控件的值*/
+ getFieldValue: PropTypes.func,
+ /** 设置一组输入控件的值*/
+ setFieldsValue: PropTypes.func,
+ /** 设置一组输入控件的值*/
+ setFields: PropTypes.func,
+ /** 校验并获取一组输入域的值与 Error */
+ validateFields: PropTypes.func,
+ // validateFields(fieldNames: Array, options: Object, callback: ValidateCallback): void;
+ // validateFields(fieldNames: Array, callback: ValidateCallback): void;
+ // validateFields(options: Object, callback: ValidateCallback): void;
+ // validateFields(callback: ValidateCallback): void;
+ // validateFields(): void;
+ /** 与 `validateFields` 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 */
+ validateFieldsAndScroll: PropTypes.func,
+ // validateFieldsAndScroll(fieldNames?: Array, options?: Object, callback?: ValidateCallback): void;
+ // validateFieldsAndScroll(fieldNames?: Array, callback?: ValidateCallback): void;
+ // validateFieldsAndScroll(options?: Object, callback?: ValidateCallback): void;
+ // validateFieldsAndScroll(callback?: ValidateCallback): void;
+ // validateFieldsAndScroll(): void;
+ /** 获取某个输入控件的 Error */
+ getFieldError: PropTypes.func,
+ getFieldsError: PropTypes.func,
+ /** 判断一个输入控件是否在校验状态*/
+ isFieldValidating: PropTypes.func,
+ isFieldTouched: PropTypes.func,
+ isFieldsTouched: PropTypes.func,
+ /** 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 */
+ resetFields: PropTypes.func,
+
+ getFieldDecorator: PropTypes.func,
+};
+
+export const FormProps = {
+ layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
+ labelCol: PropTypes.shape(ColProps).loose,
+ wrapperCol: PropTypes.shape(ColProps).loose,
+ colon: PropTypes.bool,
+ labelAlign: PropTypes.oneOf(['left', 'right']),
+ form: PropTypes.object,
+ // onSubmit: React.FormEventHandler;
+ prefixCls: PropTypes.string,
+ hideRequiredMark: PropTypes.bool,
+ autoFormCreate: PropTypes.func,
+ options: PropTypes.object,
+ selfUpdate: PropTypes.bool,
+};
+
+export const ValidationRule = {
+ /** validation error message */
+ message: PropTypes.string,
+ /** built-in validation type, available options: https://github.com/yiminghe/async-validator#type */
+ type: PropTypes.string,
+ /** indicates whether field is required */
+ required: PropTypes.boolean,
+ /** treat required fields that only contain whitespace as errors */
+ whitespace: PropTypes.boolean,
+ /** validate the exact length of a field */
+ len: PropTypes.number,
+ /** validate the min length of a field */
+ min: PropTypes.number,
+ /** validate the max length of a field */
+ max: PropTypes.number,
+ /** validate the value from a list of possible values */
+ enum: PropTypes.oneOfType([String, PropTypes.arrayOf(String)]),
+ /** validate from a regular expression */
+ pattern: PropTypes.custom(isRegExp),
+ /** transform a value before validation */
+ transform: PropTypes.func,
+ /** custom validate function (Note: callback must be called) */
+ validator: PropTypes.func,
+};
+
+// export type ValidateCallback = (errors: any, values: any) => void;
+
+// export type GetFieldDecoratorOptions = {
+// /** 子节点的值的属性,如 Checkbox 的是 'checked' */
+// valuePropName?: string;
+// /** 子节点的初始值,类型、可选值均由子节点决定 */
+// initialValue?: any;
+// /** 收集子节点的值的时机 */
+// trigger?: string;
+// /** 可以把 onChange 的参数转化为控件的值,例如 DatePicker 可设为:(date, dateString) => dateString */
+// getValueFromEvent?: (...args: any[]) => any;
+// /** Get the component props according to field value. */
+// getValueProps?: (value: any) => any;
+// /** 校验子节点值的时机 */
+// validateTrigger?: string | string[];
+// /** 校验规则,参见 [async-validator](https://github.com/yiminghe/async-validator) */
+// rules?: ValidationRule[];
+// /** 是否和其他控件互斥,特别用于 Radio 单选控件 */
+// exclusive?: boolean;
+// /** Normalize value to form component */
+// normalize?: (value: any, prevValue: any, allValues: any) => any;
+// /** Whether stop validate on first rule of error for this field. */
+// validateFirst?: boolean;
+// /** 是否一直保留子节点的信息 */
+// preserve?: boolean;
+// };
+
+const Form = {
+ name: 'AForm',
+ props: initDefaultProps(FormProps, {
+ layout: 'horizontal',
+ hideRequiredMark: false,
+ colon: true,
+ }),
+ Item: FormItem,
+ createFormField,
+ create: (options = {}) => {
+ return createDOMForm({
+ fieldNameProp: 'id',
+ ...options,
+ fieldMetaProp: FIELD_META_PROP,
+ fieldDataProp: FIELD_DATA_PROP,
+ });
+ },
+ createForm(context, options = {}) {
+ const V = Vue;
+ return new V(Form.create({ ...options, templateContext: context })());
+ },
+ created() {
+ this.formItemContexts = new Map();
+ },
+ provide() {
+ return {
+ FormContext: this,
+ // https://github.com/vueComponent/ant-design-vue/issues/446
+ collectFormItemContext:
+ this.form && this.form.templateContext
+ ? (c, type = 'add') => {
+ const formItemContexts = this.formItemContexts;
+ const number = formItemContexts.get(c) || 0;
+ if (type === 'delete') {
+ if (number <= 1) {
+ formItemContexts.delete(c);
+ } else {
+ formItemContexts.set(c, number - 1);
+ }
+ } else {
+ if (c !== this.form.templateContext) {
+ formItemContexts.set(c, number + 1);
+ }
+ }
+ }
+ : () => {},
+ };
+ },
+ inject: {
+ configProvider: { default: () => ConfigConsumerProps },
+ },
+ watch: {
+ form() {
+ this.$forceUpdate();
+ },
+ },
+ computed: {
+ vertical() {
+ return this.layout === 'vertical';
+ },
+ },
+ beforeUpdate() {
+ this.formItemContexts.forEach((number, c) => {
+ if (c.$forceUpdate) {
+ c.$forceUpdate();
+ }
+ });
+ },
+ updated() {
+ if (this.form && this.form.cleanUpUselessFields) {
+ this.form.cleanUpUselessFields();
+ }
+ },
+ methods: {
+ onSubmit(e) {
+ if (!getListeners(this).submit) {
+ e.preventDefault();
+ } else {
+ this.$emit('submit', e);
+ }
+ },
+ },
+
+ render() {
+ const {
+ prefixCls: customizePrefixCls,
+ hideRequiredMark,
+ layout,
+ onSubmit,
+ $slots,
+ autoFormCreate,
+ options = {},
+ } = this;
+ const getPrefixCls = this.configProvider.getPrefixCls;
+ const prefixCls = getPrefixCls('form', customizePrefixCls);
+
+ const formClassName = classNames(prefixCls, {
+ [`${prefixCls}-horizontal`]: layout === 'horizontal',
+ [`${prefixCls}-vertical`]: layout === 'vertical',
+ [`${prefixCls}-inline`]: layout === 'inline',
+ [`${prefixCls}-hide-required-mark`]: hideRequiredMark,
+ });
+ if (autoFormCreate) {
+ warning(false, 'Form', '`autoFormCreate` is deprecated. please use `form` instead.');
+ const DomForm =
+ this.DomForm ||
+ createDOMForm({
+ fieldNameProp: 'id',
+ ...options,
+ fieldMetaProp: FIELD_META_PROP,
+ fieldDataProp: FIELD_DATA_PROP,
+ templateContext: this.$vnode.context,
+ })({
+ provide() {
+ return {
+ decoratorFormProps: this.$props,
+ };
+ },
+ data() {
+ return {
+ children: $slots.default,
+ formClassName,
+ submit: onSubmit,
+ };
+ },
+ created() {
+ autoFormCreate(this.form);
+ },
+ render() {
+ const { children, formClassName, submit } = this;
+ return (
+
+ );
+ },
+ });
+ if (this.domForm) {
+ this.domForm.children = $slots.default;
+ this.domForm.submit = onSubmit;
+ this.domForm.formClassName = formClassName;
+ }
+ this.DomForm = DomForm;
+
+ return (
+ {
+ this.domForm = inst;
+ }}
+ />
+ );
+ }
+ return (
+
+ );
+ },
+};
+
+export default Form;
diff --git a/components/form-old/FormItem.jsx b/components/form-old/FormItem.jsx
new file mode 100644
index 000000000..64744fb08
--- /dev/null
+++ b/components/form-old/FormItem.jsx
@@ -0,0 +1,520 @@
+import { provide, inject, Transition } from 'vue';
+import PropTypes from '../_util/vue-types';
+import classNames from 'classnames';
+import find from 'lodash/find';
+import Row from '../grid/Row';
+import Col, { ColProps } from '../grid/Col';
+import warning from '../_util/warning';
+import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
+import {
+ initDefaultProps,
+ getComponent,
+ getSlotOptions,
+ isValidElement,
+ getAllChildren,
+ findDOMNode,
+ getSlot,
+} from '../_util/props-util';
+import getTransitionProps from '../_util/getTransitionProps';
+import BaseMixin from '../_util/BaseMixin';
+import { cloneElement, cloneVNodes } from '../_util/vnode';
+import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
+import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
+import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
+import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
+import { ConfigConsumerProps } from '../config-provider';
+
+const iconMap = {
+ success: CheckCircleFilled,
+ warning: ExclamationCircleFilled,
+ error: CloseCircleFilled,
+ validating: LoadingOutlined,
+};
+
+function noop() {}
+
+function intersperseSpace(list) {
+ return list.reduce((current, item) => [...current, ' ', item], []).slice(1);
+}
+export const FormItemProps = {
+ id: PropTypes.string,
+ htmlFor: PropTypes.string,
+ prefixCls: PropTypes.string,
+ label: PropTypes.any,
+ labelCol: PropTypes.shape(ColProps).loose,
+ wrapperCol: PropTypes.shape(ColProps).loose,
+ help: PropTypes.any,
+ extra: PropTypes.any,
+ validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
+ hasFeedback: PropTypes.bool,
+ required: PropTypes.bool,
+ colon: PropTypes.bool,
+ fieldDecoratorId: PropTypes.string,
+ fieldDecoratorOptions: PropTypes.object,
+ selfUpdate: PropTypes.bool,
+ labelAlign: PropTypes.oneOf(['left', 'right']),
+};
+function comeFromSlot(vnodes = [], itemVnode) {
+ let isSlot = false;
+ for (let i = 0, len = vnodes.length; i < len; i++) {
+ const vnode = vnodes[i];
+ if (vnode && (vnode === itemVnode || vnode.$vnode === itemVnode)) {
+ isSlot = true;
+ } else {
+ const componentOptions =
+ vnode.componentOptions || (vnode.$vnode && vnode.$vnode.componentOptions);
+ const children = componentOptions ? componentOptions.children : vnode.$children;
+ isSlot = comeFromSlot(children, itemVnode);
+ }
+ if (isSlot) {
+ break;
+ }
+ }
+ return isSlot;
+}
+
+export default {
+ name: 'AFormItem',
+ mixins: [BaseMixin],
+ inheritAttrs: false,
+ __ANT_FORM_ITEM: true,
+ props: initDefaultProps(FormItemProps, {
+ hasFeedback: false,
+ }),
+ setup() {
+ return {
+ isFormItemChildren: inject('isFormItemChildren', false),
+ FormContext: inject('FormContext', {}),
+ decoratorFormProps: inject('decoratorFormProps', {}),
+ collectFormItemContext: inject('collectFormItemContext', noop),
+ configProvider: inject('configProvider', ConfigConsumerProps),
+ };
+ },
+ data() {
+ return { helpShow: false };
+ },
+ computed: {
+ itemSelfUpdate() {
+ return !!(this.selfUpdate === undefined ? this.FormContext.selfUpdate : this.selfUpdate);
+ },
+ },
+ created() {
+ provide('isFormItemChildren', true);
+ this.collectContext();
+ },
+ beforeUpdate() {
+ if (process.env.NODE_ENV !== 'production') {
+ this.collectContext();
+ }
+ },
+ beforeUnmount() {
+ this.collectFormItemContext(this.$vnode && this.$vnode.context, 'delete');
+ },
+ mounted() {
+ const { help, validateStatus } = this.$props;
+ warning(
+ this.getControls(this.slotDefault, true).length <= 1 ||
+ help !== undefined ||
+ validateStatus !== undefined,
+ 'Form.Item',
+ 'Cannot generate `validateStatus` and `help` automatically, ' +
+ 'while there are more than one `getFieldDecorator` in it.',
+ );
+ warning(
+ !this.fieldDecoratorId,
+ 'Form.Item',
+ '`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.',
+ );
+ },
+ methods: {
+ collectContext() {
+ if (this.FormContext.form && this.FormContext.form.templateContext) {
+ const { templateContext } = this.FormContext.form;
+ const vnodes = Object.values(templateContext.$slots || {}).reduce((a, b) => {
+ return [...a, ...b];
+ }, []);
+ const isSlot = comeFromSlot(vnodes, this.$vnode);
+ warning(!isSlot, 'You can not set FormItem from slot, please use slot-scope instead slot');
+ let isSlotScope = false;
+ // 进一步判断是否是通过slot-scope传递
+ if (!isSlot && this.$vnode.context !== templateContext) {
+ isSlotScope = comeFromSlot(this.$vnode.context.$children, templateContext.$vnode);
+ }
+ if (!isSlotScope && !isSlot) {
+ this.collectFormItemContext(this.$vnode.context);
+ }
+ }
+ },
+ getHelpMessage() {
+ const help = getComponent(this, 'help');
+ const onlyControl = this.getOnlyControl();
+ if (help === undefined && onlyControl) {
+ const errors = this.getField().errors;
+ if (errors) {
+ return intersperseSpace(
+ errors.map((e, index) => {
+ let node = null;
+ if (isValidElement(e)) {
+ node = e;
+ } else if (isValidElement(e.message)) {
+ node = e.message;
+ }
+ return node ? cloneElement(node, { key: index }) : e.message;
+ }),
+ );
+ } else {
+ return '';
+ }
+ }
+
+ return help;
+ },
+
+ getControls(childrenArray = [], recursively) {
+ let controls = [];
+ for (let i = 0; i < childrenArray.length; i++) {
+ if (!recursively && controls.length > 0) {
+ break;
+ }
+
+ const child = childrenArray[i];
+ // if (!child.tag && child.text.trim() === '') {
+ // continue;
+ // }
+
+ if (typeof child.type === 'object' && child.type.__ANT_FORM_ITEM) {
+ continue;
+ }
+ const children = getAllChildren(child);
+ const attrs = child.props || {};
+ if (FIELD_META_PROP in attrs) {
+ // And means FIELD_DATA_PROP in child.props, too.
+ controls.push(child);
+ } else if (children) {
+ controls = controls.concat(this.getControls(children, recursively));
+ }
+ }
+ return controls;
+ },
+
+ getOnlyControl() {
+ const child = this.getControls(this.slotDefault, false)[0];
+ return child !== undefined ? child : null;
+ },
+
+ getChildAttr(prop) {
+ const child = this.getOnlyControl();
+ let data = {};
+ if (!child) {
+ return undefined;
+ }
+ debugger;
+ if (child.data) {
+ data = child.data;
+ } else if (child.$vnode && child.$vnode.data) {
+ data = child.$vnode.data;
+ }
+ return data[prop] || data.attrs[prop];
+ },
+
+ getId() {
+ return this.getChildAttr('id');
+ },
+
+ getMeta() {
+ return this.getChildAttr(FIELD_META_PROP);
+ },
+
+ getField() {
+ return this.getChildAttr(FIELD_DATA_PROP);
+ },
+
+ getValidateStatus() {
+ const onlyControl = this.getOnlyControl();
+ if (!onlyControl) {
+ return '';
+ }
+ const field = this.getField();
+ if (field.validating) {
+ return 'validating';
+ }
+ if (field.errors) {
+ return 'error';
+ }
+ const fieldValue = 'value' in field ? field.value : this.getMeta().initialValue;
+ if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
+ return 'success';
+ }
+ return '';
+ },
+
+ // Resolve duplicated ids bug between different forms
+ // https://github.com/ant-design/ant-design/issues/7351
+ onLabelClick() {
+ const id = this.id || this.getId();
+ if (!id) {
+ return;
+ }
+ const formItemNode = findDOMNode(this);
+ const control = formItemNode.querySelector(`[id="${id}"]`);
+ if (control && control.focus) {
+ control.focus();
+ }
+ },
+
+ onHelpAnimEnd(_key, helpShow) {
+ this.helpShow = helpShow;
+ if (!helpShow) {
+ this.$forceUpdate();
+ }
+ },
+
+ isRequired() {
+ const { required } = this;
+ if (required !== undefined) {
+ return required;
+ }
+ if (this.getOnlyControl()) {
+ const meta = this.getMeta() || {};
+ const validate = meta.validate || [];
+
+ return validate
+ .filter(item => !!item.rules)
+ .some(item => {
+ return item.rules.some(rule => rule.required);
+ });
+ }
+ return false;
+ },
+
+ renderHelp(prefixCls) {
+ const help = this.getHelpMessage();
+ const children = help ? (
+
+ {help}
+
+ ) : null;
+ if (children) {
+ this.helpShow = !!children;
+ }
+ const transitionProps = getTransitionProps('show-help', {
+ onAfterEnter: () => this.onHelpAnimEnd('help', true),
+ onAfterLeave: () => this.onHelpAnimEnd('help', false),
+ });
+ return (
+
+ {children}
+
+ );
+ },
+
+ renderExtra(prefixCls) {
+ const extra = getComponent(this, 'extra');
+ return extra ? : null;
+ },
+
+ renderValidateWrapper(prefixCls, c1, c2, c3) {
+ const props = this.$props;
+ const onlyControl = this.getOnlyControl;
+ const validateStatus =
+ props.validateStatus === undefined && onlyControl
+ ? this.getValidateStatus()
+ : props.validateStatus;
+
+ let classes = `${prefixCls}-item-control`;
+ if (validateStatus) {
+ classes = classNames(`${prefixCls}-item-control`, {
+ 'has-feedback': props.hasFeedback || validateStatus === 'validating',
+ 'has-success': validateStatus === 'success',
+ 'has-warning': validateStatus === 'warning',
+ 'has-error': validateStatus === 'error',
+ 'is-validating': validateStatus === 'validating',
+ });
+ }
+ const IconNode = validateStatus && iconMap[validateStatus];
+
+ const icon =
+ props.hasFeedback && IconNode ? (
+
+
+
+ ) : null;
+ return (
+
+
+ {c1}
+ {icon}
+
+ {c2}
+ {c3}
+
+ );
+ },
+
+ renderWrapper(prefixCls, children) {
+ const { wrapperCol: contextWrapperCol } = this.isFormItemChildren ? {} : this.FormContext;
+ const { wrapperCol } = this;
+ const mergedWrapperCol = wrapperCol || contextWrapperCol || {};
+ const { style, id, ...restProps } = mergedWrapperCol;
+ const className = classNames(`${prefixCls}-item-control-wrapper`, mergedWrapperCol.class);
+ const colProps = {
+ ...restProps,
+ class: className,
+ key: 'wrapper',
+ style,
+ id,
+ };
+ return {children};
+ },
+
+ renderLabel(prefixCls) {
+ const {
+ vertical,
+ labelAlign: contextLabelAlign,
+ labelCol: contextLabelCol,
+ colon: contextColon,
+ } = this.FormContext;
+ const { labelAlign, labelCol, colon, id, htmlFor } = this;
+ const label = getComponent(this, 'label');
+ const required = this.isRequired();
+ const mergedLabelCol = labelCol || contextLabelCol || {};
+
+ const mergedLabelAlign = labelAlign || contextLabelAlign;
+ const labelClsBasic = `${prefixCls}-item-label`;
+ const labelColClassName = classNames(
+ labelClsBasic,
+ mergedLabelAlign === 'left' && `${labelClsBasic}-left`,
+ mergedLabelCol.class,
+ );
+ const {
+ class: labelColClass,
+ style: labelColStyle,
+ id: labelColId,
+ ...restProps
+ } = mergedLabelCol;
+ let labelChildren = label;
+ // Keep label is original where there should have no colon
+ const computedColon = colon === true || (contextColon !== false && colon !== false);
+ const haveColon = computedColon && !vertical;
+ // Remove duplicated user input colon
+ if (haveColon && typeof label === 'string' && label.trim() !== '') {
+ labelChildren = label.replace(/[::]\s*$/, '');
+ }
+
+ const labelClassName = classNames({
+ [`${prefixCls}-item-required`]: required,
+ [`${prefixCls}-item-no-colon`]: !computedColon,
+ });
+ const colProps = {
+ ...restProps,
+ class: labelColClassName,
+ key: 'label',
+ style: labelColStyle,
+ id: labelColId,
+ };
+
+ return label ? (
+
+
+
+ ) : null;
+ },
+ renderChildren(prefixCls) {
+ return [
+ this.renderLabel(prefixCls),
+ this.renderWrapper(
+ prefixCls,
+ this.renderValidateWrapper(
+ prefixCls,
+ this.slotDefault,
+ this.renderHelp(prefixCls),
+ this.renderExtra(prefixCls),
+ ),
+ ),
+ ];
+ },
+ renderFormItem() {
+ const { prefixCls: customizePrefixCls } = this.$props;
+ const { class: className, ...restProps } = this.$attrs;
+ const getPrefixCls = this.configProvider.getPrefixCls;
+ const prefixCls = getPrefixCls('form', customizePrefixCls);
+ const children = this.renderChildren(prefixCls);
+ const itemClassName = {
+ [className]: true,
+ [`${prefixCls}-item`]: true,
+ [`${prefixCls}-item-with-help`]: this.helpShow,
+ };
+
+ return (
+
+ {children}
+
+ );
+ },
+ decoratorOption(vnode) {
+ if (vnode.data && vnode.data.directives) {
+ const directive = find(vnode.data.directives, ['name', 'decorator']);
+ warning(
+ !directive || (directive && Array.isArray(directive.value)),
+ 'Form',
+ `Invalid directive: type check failed for directive "decorator". Expected Array, got ${typeof (directive
+ ? directive.value
+ : directive)}. At ${vnode.tag}.`,
+ );
+ return directive ? directive.value : null;
+ } else {
+ return null;
+ }
+ },
+ decoratorChildren(vnodes) {
+ const { FormContext } = this;
+ const getFieldDecorator = FormContext.form.getFieldDecorator;
+ for (let i = 0, len = vnodes.length; i < len; i++) {
+ const vnode = vnodes[i];
+ if (getSlotOptions(vnode).__ANT_FORM_ITEM) {
+ break;
+ }
+ if (vnode.children) {
+ vnode.children = this.decoratorChildren(cloneVNodes(vnode.children));
+ } else if (vnode.componentOptions && vnode.componentOptions.children) {
+ vnode.componentOptions.children = this.decoratorChildren(
+ cloneVNodes(vnode.componentOptions.children),
+ );
+ }
+ const option = this.decoratorOption(vnode);
+ if (option && option[0]) {
+ vnodes[i] = getFieldDecorator(option[0], option[1], this)(vnode);
+ }
+ }
+ return vnodes;
+ },
+ },
+
+ render() {
+ const { decoratorFormProps, fieldDecoratorId, fieldDecoratorOptions = {}, FormContext } = this;
+ let child = getSlot(this);
+ if (decoratorFormProps.form && fieldDecoratorId && child.length) {
+ const getFieldDecorator = decoratorFormProps.form.getFieldDecorator;
+ child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions, this)(child[0]);
+ warning(
+ !(child.length > 1),
+ 'Form',
+ '`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children',
+ );
+ this.slotDefault = child;
+ } else if (FormContext.form) {
+ child = cloneVNodes(child);
+ this.slotDefault = this.decoratorChildren(child);
+ } else {
+ this.slotDefault = child;
+ }
+ return this.renderFormItem();
+ },
+};
diff --git a/components/form-old/__tests__/__snapshots__/demo.test.js.snap b/components/form-old/__tests__/__snapshots__/demo.test.js.snap
new file mode 100644
index 000000000..97fd53963
--- /dev/null
+++ b/components/form-old/__tests__/__snapshots__/demo.test.js.snap
@@ -0,0 +1,849 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders ./antdv-demo/docs/form/demo/advanced-search.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/coordinated.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/customized-form-controls.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/dynamic-form-item.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/dynamic-rule.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/form-in-modal.vue correctly 1`] = `
+
+
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/global-state.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/horizontal-login.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/layout.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/normal-login.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/register.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/time-related-controls.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/validate-other.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/validate-static.vue correctly 1`] = `
+
+`;
+
+exports[`renders ./antdv-demo/docs/form/demo/without-form-create.vue correctly 1`] = `
+
+`;
diff --git a/components/form/__tests__/__snapshots__/index.test.js.snap b/components/form-old/__tests__/__snapshots__/index.test.js.snap
similarity index 100%
rename from components/form/__tests__/__snapshots__/index.test.js.snap
rename to components/form-old/__tests__/__snapshots__/index.test.js.snap
diff --git a/components/form/__tests__/__snapshots__/message.test.js.snap b/components/form-old/__tests__/__snapshots__/message.test.js.snap
similarity index 100%
rename from components/form/__tests__/__snapshots__/message.test.js.snap
rename to components/form-old/__tests__/__snapshots__/message.test.js.snap
diff --git a/components/form-old/__tests__/demo.test.js b/components/form-old/__tests__/demo.test.js
new file mode 100644
index 000000000..e5338b89c
--- /dev/null
+++ b/components/form-old/__tests__/demo.test.js
@@ -0,0 +1,3 @@
+import demoTest from '../../../tests/shared/demoTest';
+
+demoTest('form', { suffix: 'vue', skip: ['index.vue', 'vuex.vue'] });
diff --git a/components/form/__tests__/index.test.js b/components/form-old/__tests__/index.test.js
similarity index 100%
rename from components/form/__tests__/index.test.js
rename to components/form-old/__tests__/index.test.js
diff --git a/components/form/__tests__/label.test.js b/components/form-old/__tests__/label.test.js
similarity index 100%
rename from components/form/__tests__/label.test.js
rename to components/form-old/__tests__/label.test.js
diff --git a/components/form/__tests__/message.test.js b/components/form-old/__tests__/message.test.js
similarity index 100%
rename from components/form/__tests__/message.test.js
rename to components/form-old/__tests__/message.test.js
diff --git a/components/form/constants.jsx b/components/form-old/constants.jsx
similarity index 100%
rename from components/form/constants.jsx
rename to components/form-old/constants.jsx
diff --git a/components/form-old/index.jsx b/components/form-old/index.jsx
new file mode 100644
index 000000000..b5c0b1602
--- /dev/null
+++ b/components/form-old/index.jsx
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import Form from './Form';
+import ref from 'vue-ref';
+import FormDecoratorDirective from '../_util/FormDecoratorDirective';
+
+Vue.use(ref, { name: 'ant-ref' });
+Vue.use(FormDecoratorDirective);
+Vue.prototype.$form = Form;
+
+export { FormProps, FormCreateOption, ValidationRule } from './Form';
+export { FormItemProps } from './FormItem';
+
+/* istanbul ignore next */
+Form.install = function(Vue) {
+ Vue.component(Form.name, Form);
+ Vue.component(Form.Item.name, Form.Item);
+ Vue.prototype.$form = Form;
+};
+
+export default Form;
diff --git a/components/form-model/style/index.js b/components/form-old/style/index.js
similarity index 100%
rename from components/form-model/style/index.js
rename to components/form-old/style/index.js
diff --git a/components/form-model/style/index.less b/components/form-old/style/index.less
similarity index 100%
rename from components/form-model/style/index.less
rename to components/form-old/style/index.less
diff --git a/components/form-model/style/mixin.less b/components/form-old/style/mixin.less
similarity index 100%
rename from components/form-model/style/mixin.less
rename to components/form-old/style/mixin.less
diff --git a/components/form/Form.jsx b/components/form/Form.jsx
index 22dd38637..d98a441bd 100755
--- a/components/form/Form.jsx
+++ b/components/form/Form.jsx
@@ -1,61 +1,18 @@
+import { inject, provide } from 'vue';
import PropTypes from '../_util/vue-types';
import classNames from 'classnames';
import { ColProps } from '../grid/Col';
-import Vue from 'vue';
import isRegExp from 'lodash/isRegExp';
import warning from '../_util/warning';
-import createDOMForm from '../vc-form/src/createDOMForm';
-import createFormField from '../vc-form/src/createFormField';
import FormItem from './FormItem';
-import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
-import { initDefaultProps, getListeners } from '../_util/props-util';
+import { initDefaultProps, getSlot } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
-
-export const FormCreateOption = {
- onFieldsChange: PropTypes.func,
- onValuesChange: PropTypes.func,
- mapPropsToFields: PropTypes.func,
- validateMessages: PropTypes.any,
- withRef: PropTypes.bool,
- name: PropTypes.string,
-};
-
-// function create
-export const WrappedFormUtils = {
- /** 获取一组输入控件的值,如不传入参数,则获取全部组件的值 */
- getFieldsValue: PropTypes.func,
- /** 获取一个输入控件的值*/
- getFieldValue: PropTypes.func,
- /** 设置一组输入控件的值*/
- setFieldsValue: PropTypes.func,
- /** 设置一组输入控件的值*/
- setFields: PropTypes.func,
- /** 校验并获取一组输入域的值与 Error */
- validateFields: PropTypes.func,
- // validateFields(fieldNames: Array, options: Object, callback: ValidateCallback): void;
- // validateFields(fieldNames: Array, callback: ValidateCallback): void;
- // validateFields(options: Object, callback: ValidateCallback): void;
- // validateFields(callback: ValidateCallback): void;
- // validateFields(): void;
- /** 与 `validateFields` 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 */
- validateFieldsAndScroll: PropTypes.func,
- // validateFieldsAndScroll(fieldNames?: Array, options?: Object, callback?: ValidateCallback): void;
- // validateFieldsAndScroll(fieldNames?: Array, callback?: ValidateCallback): void;
- // validateFieldsAndScroll(options?: Object, callback?: ValidateCallback): void;
- // validateFieldsAndScroll(callback?: ValidateCallback): void;
- // validateFieldsAndScroll(): void;
- /** 获取某个输入控件的 Error */
- getFieldError: PropTypes.func,
- getFieldsError: PropTypes.func,
- /** 判断一个输入控件是否在校验状态*/
- isFieldValidating: PropTypes.func,
- isFieldTouched: PropTypes.func,
- isFieldsTouched: PropTypes.func,
- /** 重置一组输入控件的值与状态,如不传入参数,则重置所有组件 */
- resetFields: PropTypes.func,
-
- getFieldDecorator: PropTypes.func,
-};
+import { getNamePath, containsNamePath } from './utils/valueUtil';
+import { defaultValidateMessages } from './utils/messages';
+import { allPromiseFinish } from './utils/asyncUtil';
+import { toArray } from './utils/typeUtil';
+import isEqual from 'lodash/isEqual';
+import scrollIntoView from 'scroll-into-view-if-needed';
export const FormProps = {
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
@@ -63,13 +20,17 @@ export const FormProps = {
wrapperCol: PropTypes.shape(ColProps).loose,
colon: PropTypes.bool,
labelAlign: PropTypes.oneOf(['left', 'right']),
- form: PropTypes.object,
- // onSubmit: React.FormEventHandler;
prefixCls: PropTypes.string,
hideRequiredMark: PropTypes.bool,
- autoFormCreate: PropTypes.func,
- options: PropTypes.object,
- selfUpdate: PropTypes.bool,
+ model: PropTypes.object,
+ rules: PropTypes.object,
+ validateMessages: PropTypes.any,
+ validateOnRuleChange: PropTypes.bool,
+ // 提交失败自动滚动到第一个错误字段
+ scrollToFirstError: PropTypes.bool,
+ onFinish: PropTypes.func,
+ onFinishFailed: PropTypes.func,
+ name: PropTypes.name,
};
export const ValidationRule = {
@@ -97,87 +58,35 @@ export const ValidationRule = {
validator: PropTypes.func,
};
-// export type ValidateCallback = (errors: any, values: any) => void;
-
-// export type GetFieldDecoratorOptions = {
-// /** 子节点的值的属性,如 Checkbox 的是 'checked' */
-// valuePropName?: string;
-// /** 子节点的初始值,类型、可选值均由子节点决定 */
-// initialValue?: any;
-// /** 收集子节点的值的时机 */
-// trigger?: string;
-// /** 可以把 onChange 的参数转化为控件的值,例如 DatePicker 可设为:(date, dateString) => dateString */
-// getValueFromEvent?: (...args: any[]) => any;
-// /** Get the component props according to field value. */
-// getValueProps?: (value: any) => any;
-// /** 校验子节点值的时机 */
-// validateTrigger?: string | string[];
-// /** 校验规则,参见 [async-validator](https://github.com/yiminghe/async-validator) */
-// rules?: ValidationRule[];
-// /** 是否和其他控件互斥,特别用于 Radio 单选控件 */
-// exclusive?: boolean;
-// /** Normalize value to form component */
-// normalize?: (value: any, prevValue: any, allValues: any) => any;
-// /** Whether stop validate on first rule of error for this field. */
-// validateFirst?: boolean;
-// /** 是否一直保留子节点的信息 */
-// preserve?: boolean;
-// };
+function isEqualName(name1, name2) {
+ return isEqual(toArray(name1), toArray(name2));
+}
const Form = {
name: 'AForm',
+ inheritAttrs: false,
props: initDefaultProps(FormProps, {
layout: 'horizontal',
hideRequiredMark: false,
colon: true,
}),
Item: FormItem,
- createFormField,
- create: (options = {}) => {
- return createDOMForm({
- fieldNameProp: 'id',
- ...options,
- fieldMetaProp: FIELD_META_PROP,
- fieldDataProp: FIELD_DATA_PROP,
- });
- },
- createForm(context, options = {}) {
- const V = Vue;
- return new V(Form.create({ ...options, templateContext: context })());
- },
created() {
- this.formItemContexts = new Map();
+ this.fields = [];
+ this.form = undefined;
+ this.lastValidatePromise = null;
+ provide('FormContext', this);
},
- provide() {
+ setup() {
return {
- FormContext: this,
- // https://github.com/vueComponent/ant-design-vue/issues/446
- collectFormItemContext:
- this.form && this.form.templateContext
- ? (c, type = 'add') => {
- const formItemContexts = this.formItemContexts;
- const number = formItemContexts.get(c) || 0;
- if (type === 'delete') {
- if (number <= 1) {
- formItemContexts.delete(c);
- } else {
- formItemContexts.set(c, number - 1);
- }
- } else {
- if (c !== this.form.templateContext) {
- formItemContexts.set(c, number + 1);
- }
- }
- }
- : () => {},
+ configProvider: inject('configProvider', ConfigConsumerProps),
};
},
- inject: {
- configProvider: { default: () => ConfigConsumerProps },
- },
watch: {
- form() {
- this.$forceUpdate();
+ rules() {
+ if (this.validateOnRuleChange) {
+ this.validateFields();
+ }
},
},
computed: {
@@ -185,100 +94,186 @@ const Form = {
return this.layout === 'vertical';
},
},
- beforeUpdate() {
- this.formItemContexts.forEach((number, c) => {
- if (c.$forceUpdate) {
- c.$forceUpdate();
- }
- });
- },
- updated() {
- if (this.form && this.form.cleanUpUselessFields) {
- this.form.cleanUpUselessFields();
- }
- },
methods: {
- onSubmit(e) {
- if (!getListeners(this).submit) {
- e.preventDefault();
- } else {
- this.$emit('submit', e);
+ addField(field) {
+ if (field) {
+ this.fields.push(field);
}
},
+ removeField(field) {
+ if (field.fieldName) {
+ this.fields.splice(this.fields.indexOf(field), 1);
+ }
+ },
+ handleSubmit(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.$emit('submit', e);
+ const res = this.validateFields();
+ res
+ .then(values => {
+ this.$emit('finish', values);
+ })
+ .catch(errors => {
+ this.handleFinishFailed(errors);
+ });
+ },
+ getFieldsByNameList(nameList) {
+ const provideNameList = !!nameList;
+ const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
+ if (!provideNameList) {
+ return this.fields;
+ } else {
+ return this.fields.filter(
+ field => namePathList.findIndex(namePath => isEqualName(namePath, field.fieldName)) > -1,
+ );
+ }
+ },
+ resetFields(name) {
+ if (!this.model) {
+ warning(false, 'Form', 'model is required for resetFields to work.');
+ return;
+ }
+ this.getFieldsByNameList(name).forEach(field => {
+ field.resetField();
+ });
+ },
+ clearValidate(name) {
+ this.getFieldsByNameList(name).forEach(field => {
+ field.clearValidate();
+ });
+ },
+ handleFinishFailed(errorInfo) {
+ const { scrollToFirstError } = this;
+ this.$emit('finishFailed', errorInfo);
+ if (scrollToFirstError && errorInfo.errorFields.length) {
+ this.scrollToField(errorInfo.errorFields[0].name);
+ }
+ },
+ validate() {
+ return this.validateField(...arguments);
+ },
+ scrollToField(name, options = {}) {
+ const fields = this.getFieldsByNameList([name]);
+ if (fields.length) {
+ const fieldId = fields[0].fieldId;
+ const node = fieldId ? document.getElementById(fieldId) : null;
+
+ if (node) {
+ scrollIntoView(node, {
+ scrollMode: 'if-needed',
+ block: 'nearest',
+ ...options,
+ });
+ }
+ }
+ },
+ // eslint-disable-next-line no-unused-vars
+ getFieldsValue(nameList = true) {
+ const values = {};
+ this.fields.forEach(({ fieldName, fieldValue }) => {
+ values[fieldName] = fieldValue;
+ });
+ if (nameList === true) {
+ return values;
+ } else {
+ const res = {};
+ toArray(nameList).forEach(namePath => (res[namePath] = values[namePath]));
+ return res;
+ }
+ },
+ validateFields(nameList, options) {
+ if (!this.model) {
+ warning(false, 'Form', 'model is required for validateFields to work.');
+ return;
+ }
+ const provideNameList = !!nameList;
+ const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
+
+ // Collect result in promise list
+ const promiseList = [];
+
+ this.fields.forEach(field => {
+ // Add field if not provide `nameList`
+ if (!provideNameList) {
+ namePathList.push(field.getNamePath());
+ }
+
+ // Skip if without rule
+ if (!field.getRules().length) {
+ return;
+ }
+
+ const fieldNamePath = field.getNamePath();
+
+ // Add field validate rule in to promise list
+ if (!provideNameList || containsNamePath(namePathList, fieldNamePath)) {
+ const promise = field.validateRules({
+ validateMessages: {
+ ...defaultValidateMessages,
+ ...this.validateMessages,
+ },
+ ...options,
+ });
+
+ // Wrap promise with field
+ promiseList.push(
+ promise
+ .then(() => ({ name: fieldNamePath, errors: [] }))
+ .catch(errors =>
+ Promise.reject({
+ name: fieldNamePath,
+ errors,
+ }),
+ ),
+ );
+ }
+ });
+
+ const summaryPromise = allPromiseFinish(promiseList);
+ this.lastValidatePromise = summaryPromise;
+
+ const returnPromise = summaryPromise
+ .then(() => {
+ if (this.lastValidatePromise === summaryPromise) {
+ return Promise.resolve(this.getFieldsValue(namePathList));
+ }
+ return Promise.reject([]);
+ })
+ .catch(results => {
+ const errorList = results.filter(result => result && result.errors.length);
+ return Promise.reject({
+ values: this.getFieldsValue(namePathList),
+ errorFields: errorList,
+ outOfDate: this.lastValidatePromise !== summaryPromise,
+ });
+ });
+
+ // Do not throw in console
+ returnPromise.catch(e => e);
+
+ return returnPromise;
+ },
+ validateField() {
+ return this.validateFields(...arguments);
+ },
},
render() {
- const {
- prefixCls: customizePrefixCls,
- hideRequiredMark,
- layout,
- onSubmit,
- $slots,
- autoFormCreate,
- options = {},
- } = this;
+ const { prefixCls: customizePrefixCls, hideRequiredMark, layout, handleSubmit } = this;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('form', customizePrefixCls);
+ const { class: className, onSubmit: originSubmit, ...restProps } = this.$attrs;
- const formClassName = classNames(prefixCls, {
+ const formClassName = classNames(prefixCls, className, {
[`${prefixCls}-horizontal`]: layout === 'horizontal',
[`${prefixCls}-vertical`]: layout === 'vertical',
[`${prefixCls}-inline`]: layout === 'inline',
[`${prefixCls}-hide-required-mark`]: hideRequiredMark,
});
- if (autoFormCreate) {
- warning(false, 'Form', '`autoFormCreate` is deprecated. please use `form` instead.');
- const DomForm =
- this.DomForm ||
- createDOMForm({
- fieldNameProp: 'id',
- ...options,
- fieldMetaProp: FIELD_META_PROP,
- fieldDataProp: FIELD_DATA_PROP,
- templateContext: this.$vnode.context,
- })({
- provide() {
- return {
- decoratorFormProps: this.$props,
- };
- },
- data() {
- return {
- children: $slots.default,
- formClassName,
- submit: onSubmit,
- };
- },
- created() {
- autoFormCreate(this.form);
- },
- render() {
- const { children, formClassName, submit } = this;
- return (
-
- );
- },
- });
- if (this.domForm) {
- this.domForm.children = $slots.default;
- this.domForm.submit = onSubmit;
- this.domForm.formClassName = formClassName;
- }
- this.DomForm = DomForm;
-
- return (
- {
- this.domForm = inst;
- }}
- />
- );
- }
return (
-
);
},
diff --git a/components/form/FormItem.jsx b/components/form/FormItem.jsx
index 64744fb08..c938d64c0 100644
--- a/components/form/FormItem.jsx
+++ b/components/form/FormItem.jsx
@@ -1,28 +1,30 @@
-import { provide, inject, Transition } from 'vue';
+import { inject, provide, Transition } from 'vue';
+import cloneDeep from 'lodash/cloneDeep';
import PropTypes from '../_util/vue-types';
import classNames from 'classnames';
-import find from 'lodash/find';
+import getTransitionProps from '../_util/getTransitionProps';
import Row from '../grid/Row';
import Col, { ColProps } from '../grid/Col';
-import warning from '../_util/warning';
-import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
-import {
+import hasProp, {
initDefaultProps,
- getComponent,
- getSlotOptions,
- isValidElement,
- getAllChildren,
findDOMNode,
+ getComponent,
+ getOptionProps,
+ getEvents,
+ isValidElement,
getSlot,
} from '../_util/props-util';
-import getTransitionProps from '../_util/getTransitionProps';
import BaseMixin from '../_util/BaseMixin';
-import { cloneElement, cloneVNodes } from '../_util/vnode';
+import { ConfigConsumerProps } from '../config-provider';
+import { cloneElement } from '../_util/vnode';
import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
-import { ConfigConsumerProps } from '../config-provider';
+import { validateRules } from './utils/validateUtil';
+import { getNamePath } from './utils/valueUtil';
+import { toArray } from './utils/typeUtil';
+import { warning } from '../vc-util/warning';
const iconMap = {
success: CheckCircleFilled,
@@ -31,227 +33,256 @@ const iconMap = {
validating: LoadingOutlined,
};
-function noop() {}
+function getPropByPath(obj, namePathList, strict) {
+ let tempObj = obj;
-function intersperseSpace(list) {
- return list.reduce((current, item) => [...current, ' ', item], []).slice(1);
+ const keyArr = namePathList;
+ let i = 0;
+ try {
+ for (let len = keyArr.length; i < len - 1; ++i) {
+ if (!tempObj && !strict) break;
+ let key = keyArr[i];
+ if (key in tempObj) {
+ tempObj = tempObj[key];
+ } else {
+ if (strict) {
+ throw Error('please transfer a valid name path to form item!');
+ }
+ break;
+ }
+ }
+ if (strict && !tempObj) {
+ throw Error('please transfer a valid name path to form item!');
+ }
+ } catch (error) {
+ console.error('please transfer a valid name path to form item!');
+ }
+
+ return {
+ o: tempObj,
+ k: keyArr[i],
+ v: tempObj ? tempObj[keyArr[i]] : undefined,
+ };
}
export const FormItemProps = {
id: PropTypes.string,
htmlFor: PropTypes.string,
prefixCls: PropTypes.string,
label: PropTypes.any,
- labelCol: PropTypes.shape(ColProps).loose,
- wrapperCol: PropTypes.shape(ColProps).loose,
help: PropTypes.any,
extra: PropTypes.any,
- validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
+ labelCol: PropTypes.shape(ColProps).loose,
+ wrapperCol: PropTypes.shape(ColProps).loose,
hasFeedback: PropTypes.bool,
- required: PropTypes.bool,
colon: PropTypes.bool,
- fieldDecoratorId: PropTypes.string,
- fieldDecoratorOptions: PropTypes.object,
- selfUpdate: PropTypes.bool,
labelAlign: PropTypes.oneOf(['left', 'right']),
+ prop: PropTypes.oneOfType([Array, String, Number]),
+ name: PropTypes.oneOfType([Array, String, Number]),
+ rules: PropTypes.oneOfType([Array, Object]),
+ autoLink: PropTypes.bool,
+ required: PropTypes.bool,
+ validateFirst: PropTypes.bool,
+ validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
};
-function comeFromSlot(vnodes = [], itemVnode) {
- let isSlot = false;
- for (let i = 0, len = vnodes.length; i < len; i++) {
- const vnode = vnodes[i];
- if (vnode && (vnode === itemVnode || vnode.$vnode === itemVnode)) {
- isSlot = true;
- } else {
- const componentOptions =
- vnode.componentOptions || (vnode.$vnode && vnode.$vnode.componentOptions);
- const children = componentOptions ? componentOptions.children : vnode.$children;
- isSlot = comeFromSlot(children, itemVnode);
- }
- if (isSlot) {
- break;
- }
- }
- return isSlot;
-}
export default {
name: 'AFormItem',
mixins: [BaseMixin],
inheritAttrs: false,
- __ANT_FORM_ITEM: true,
+ __ANT_NEW_FORM_ITEM: true,
props: initDefaultProps(FormItemProps, {
hasFeedback: false,
+ autoLink: true,
}),
setup() {
return {
isFormItemChildren: inject('isFormItemChildren', false),
- FormContext: inject('FormContext', {}),
- decoratorFormProps: inject('decoratorFormProps', {}),
- collectFormItemContext: inject('collectFormItemContext', noop),
configProvider: inject('configProvider', ConfigConsumerProps),
+ FormContext: inject('FormContext', {}),
};
},
data() {
- return { helpShow: false };
+ warning(!hasProp(this, 'prop'), `\`prop\` is deprecated. Please use \`name\` instead.`);
+ return {
+ validateState: this.validateStatus,
+ validateMessage: '',
+ validateDisabled: false,
+ validator: {},
+ helpShow: false,
+ errors: [],
+ };
},
+
computed: {
- itemSelfUpdate() {
- return !!(this.selfUpdate === undefined ? this.FormContext.selfUpdate : this.selfUpdate);
+ fieldName() {
+ return this.name || this.prop;
+ },
+ namePath() {
+ return getNamePath(this.fieldName);
+ },
+ fieldId() {
+ if (this.id) {
+ return this.id;
+ } else if (!this.namePath.length) {
+ return undefined;
+ } else {
+ const formName = this.FormContext.name;
+ const mergedId = this.namePath.join('_');
+ return formName ? `${formName}_${mergedId}` : mergedId;
+ }
+ },
+ fieldValue() {
+ const model = this.FormContext.model;
+ if (!model || !this.fieldName) {
+ return;
+ }
+ return getPropByPath(model, this.namePath, true).v;
+ },
+ isRequired() {
+ let rules = this.getRules();
+ let isRequired = false;
+ if (rules && rules.length) {
+ rules.every(rule => {
+ if (rule.required) {
+ isRequired = true;
+ return false;
+ }
+ return true;
+ });
+ }
+ return isRequired || this.required;
+ },
+ },
+ watch: {
+ validateStatus(val) {
+ this.validateState = val;
},
},
created() {
provide('isFormItemChildren', true);
- this.collectContext();
},
- beforeUpdate() {
- if (process.env.NODE_ENV !== 'production') {
- this.collectContext();
+ mounted() {
+ if (this.fieldName) {
+ const { addField } = this.FormContext;
+ addField && addField(this);
+ this.initialValue = cloneDeep(this.fieldValue);
}
},
beforeUnmount() {
- this.collectFormItemContext(this.$vnode && this.$vnode.context, 'delete');
- },
- mounted() {
- const { help, validateStatus } = this.$props;
- warning(
- this.getControls(this.slotDefault, true).length <= 1 ||
- help !== undefined ||
- validateStatus !== undefined,
- 'Form.Item',
- 'Cannot generate `validateStatus` and `help` automatically, ' +
- 'while there are more than one `getFieldDecorator` in it.',
- );
- warning(
- !this.fieldDecoratorId,
- 'Form.Item',
- '`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.',
- );
+ const { removeField } = this.FormContext;
+ removeField && removeField(this);
},
methods: {
- collectContext() {
- if (this.FormContext.form && this.FormContext.form.templateContext) {
- const { templateContext } = this.FormContext.form;
- const vnodes = Object.values(templateContext.$slots || {}).reduce((a, b) => {
- return [...a, ...b];
- }, []);
- const isSlot = comeFromSlot(vnodes, this.$vnode);
- warning(!isSlot, 'You can not set FormItem from slot, please use slot-scope instead slot');
- let isSlotScope = false;
- // 进一步判断是否是通过slot-scope传递
- if (!isSlot && this.$vnode.context !== templateContext) {
- isSlotScope = comeFromSlot(this.$vnode.context.$children, templateContext.$vnode);
- }
- if (!isSlotScope && !isSlot) {
- this.collectFormItemContext(this.$vnode.context);
- }
+ getNamePath() {
+ const { fieldName } = this;
+ const { prefixName = [] } = this.FormContext;
+
+ return fieldName !== undefined ? [...prefixName, ...this.namePath] : [];
+ },
+ validateRules(options) {
+ const { validateFirst = false, messageVariables } = this.$props;
+ const { triggerName } = options || {};
+ const namePath = this.getNamePath();
+
+ let filteredRules = this.getRules();
+ if (triggerName) {
+ filteredRules = filteredRules.filter(rule => {
+ const { trigger } = rule;
+ if (!trigger) {
+ return true;
+ }
+ const triggerList = toArray(trigger);
+ return triggerList.includes(triggerName);
+ });
}
+ if (!filteredRules.length) {
+ return Promise.resolve();
+ }
+ const promise = validateRules(
+ namePath,
+ this.fieldValue,
+ filteredRules,
+ options,
+ validateFirst,
+ messageVariables,
+ );
+ this.validateState = 'validating';
+ this.errors = [];
+
+ promise
+ .catch(e => e)
+ .then((errors = []) => {
+ if (this.validateState === 'validating') {
+ this.validateState = errors.length ? 'error' : 'success';
+ this.validateMessage = errors[0];
+ this.errors = errors;
+ }
+ });
+
+ return promise;
+ },
+ getRules() {
+ let formRules = this.FormContext.rules;
+ const selfRules = this.rules;
+ const requiredRule =
+ this.required !== undefined ? { required: !!this.required, trigger: 'change' } : [];
+ const prop = getPropByPath(formRules, this.namePath);
+ formRules = formRules ? prop.o[prop.k] || prop.v : [];
+ return [].concat(selfRules || formRules || []).concat(requiredRule);
+ },
+ getFilteredRule(trigger) {
+ const rules = this.getRules();
+ return rules
+ .filter(rule => {
+ if (!rule.trigger || trigger === '') return true;
+ if (Array.isArray(rule.trigger)) {
+ return rule.trigger.indexOf(trigger) > -1;
+ } else {
+ return rule.trigger === trigger;
+ }
+ })
+ .map(rule => ({ ...rule }));
+ },
+ onFieldBlur() {
+ this.validateRules({ triggerName: 'blur' });
+ },
+ onFieldChange() {
+ if (this.validateDisabled) {
+ this.validateDisabled = false;
+ return;
+ }
+ this.validateRules({ triggerName: 'change' });
+ },
+ clearValidate() {
+ this.validateState = '';
+ this.validateMessage = '';
+ this.validateDisabled = false;
+ },
+ resetField() {
+ this.validateState = '';
+ this.validateMessage = '';
+ const model = this.FormContext.model || {};
+ const value = this.fieldValue;
+ const prop = getPropByPath(model, this.namePath, true);
+ this.validateDisabled = true;
+ if (Array.isArray(value)) {
+ prop.o[prop.k] = [].concat(this.initialValue);
+ } else {
+ prop.o[prop.k] = this.initialValue;
+ }
+ // reset validateDisabled after onFieldChange triggered
+ this.$nextTick(() => {
+ this.validateDisabled = false;
+ });
},
getHelpMessage() {
const help = getComponent(this, 'help');
- const onlyControl = this.getOnlyControl();
- if (help === undefined && onlyControl) {
- const errors = this.getField().errors;
- if (errors) {
- return intersperseSpace(
- errors.map((e, index) => {
- let node = null;
- if (isValidElement(e)) {
- node = e;
- } else if (isValidElement(e.message)) {
- node = e.message;
- }
- return node ? cloneElement(node, { key: index }) : e.message;
- }),
- );
- } else {
- return '';
- }
- }
- return help;
+ return this.validateMessage || help;
},
- getControls(childrenArray = [], recursively) {
- let controls = [];
- for (let i = 0; i < childrenArray.length; i++) {
- if (!recursively && controls.length > 0) {
- break;
- }
-
- const child = childrenArray[i];
- // if (!child.tag && child.text.trim() === '') {
- // continue;
- // }
-
- if (typeof child.type === 'object' && child.type.__ANT_FORM_ITEM) {
- continue;
- }
- const children = getAllChildren(child);
- const attrs = child.props || {};
- if (FIELD_META_PROP in attrs) {
- // And means FIELD_DATA_PROP in child.props, too.
- controls.push(child);
- } else if (children) {
- controls = controls.concat(this.getControls(children, recursively));
- }
- }
- return controls;
- },
-
- getOnlyControl() {
- const child = this.getControls(this.slotDefault, false)[0];
- return child !== undefined ? child : null;
- },
-
- getChildAttr(prop) {
- const child = this.getOnlyControl();
- let data = {};
- if (!child) {
- return undefined;
- }
- debugger;
- if (child.data) {
- data = child.data;
- } else if (child.$vnode && child.$vnode.data) {
- data = child.$vnode.data;
- }
- return data[prop] || data.attrs[prop];
- },
-
- getId() {
- return this.getChildAttr('id');
- },
-
- getMeta() {
- return this.getChildAttr(FIELD_META_PROP);
- },
-
- getField() {
- return this.getChildAttr(FIELD_DATA_PROP);
- },
-
- getValidateStatus() {
- const onlyControl = this.getOnlyControl();
- if (!onlyControl) {
- return '';
- }
- const field = this.getField();
- if (field.validating) {
- return 'validating';
- }
- if (field.errors) {
- return 'error';
- }
- const fieldValue = 'value' in field ? field.value : this.getMeta().initialValue;
- if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
- return 'success';
- }
- return '';
- },
-
- // Resolve duplicated ids bug between different forms
- // https://github.com/ant-design/ant-design/issues/7351
onLabelClick() {
- const id = this.id || this.getId();
+ const id = this.fieldId;
if (!id) {
return;
}
@@ -269,24 +300,6 @@ export default {
}
},
- isRequired() {
- const { required } = this;
- if (required !== undefined) {
- return required;
- }
- if (this.getOnlyControl()) {
- const meta = this.getMeta() || {};
- const validate = meta.validate || [];
-
- return validate
- .filter(item => !!item.rules)
- .some(item => {
- return item.rules.some(rule => rule.required);
- });
- }
- return false;
- },
-
renderHelp(prefixCls) {
const help = this.getHelpMessage();
const children = help ? (
@@ -314,17 +327,12 @@ export default {
},
renderValidateWrapper(prefixCls, c1, c2, c3) {
- const props = this.$props;
- const onlyControl = this.getOnlyControl;
- const validateStatus =
- props.validateStatus === undefined && onlyControl
- ? this.getValidateStatus()
- : props.validateStatus;
+ const validateStatus = this.validateState;
let classes = `${prefixCls}-item-control`;
if (validateStatus) {
classes = classNames(`${prefixCls}-item-control`, {
- 'has-feedback': props.hasFeedback || validateStatus === 'validating',
+ 'has-feedback': this.hasFeedback || validateStatus === 'validating',
'has-success': validateStatus === 'success',
'has-warning': validateStatus === 'warning',
'has-error': validateStatus === 'error',
@@ -334,7 +342,7 @@ export default {
const IconNode = validateStatus && iconMap[validateStatus];
const icon =
- props.hasFeedback && IconNode ? (
+ this.hasFeedback && IconNode ? (
@@ -374,9 +382,9 @@ export default {
labelCol: contextLabelCol,
colon: contextColon,
} = this.FormContext;
- const { labelAlign, labelCol, colon, id, htmlFor } = this;
+ const { labelAlign, labelCol, colon, fieldId, htmlFor } = this;
const label = getComponent(this, 'label');
- const required = this.isRequired();
+ const required = this.isRequired;
const mergedLabelCol = labelCol || contextLabelCol || {};
const mergedLabelAlign = labelAlign || contextLabelAlign;
@@ -416,7 +424,7 @@ export default {
return label ? (