diff --git a/build/config.js b/build/config.js index b65e077ac..e54a2e102 100644 --- a/build/config.js +++ b/build/config.js @@ -1,5 +1,5 @@ module.exports = { dev: { - componentName: 'empty', // dev components + componentName: 'form', // dev components }, }; diff --git a/components/form/Form.jsx b/components/form/Form.jsx index 88fe5934a..8441dc03e 100755 --- a/components/form/Form.jsx +++ b/components/form/Form.jsx @@ -62,6 +62,8 @@ 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, @@ -128,6 +130,7 @@ const Form = { props: initDefaultProps(FormProps, { layout: 'horizontal', hideRequiredMark: false, + colon: true, }), Item: FormItem, createFormField: createFormField, @@ -148,7 +151,7 @@ const Form = { }, provide() { return { - FormProps: this.$props, + FormContextProps: this, // https://github.com/vueComponent/ant-design-vue/issues/446 collectFormItemContext: this.form && this.form.templateContext @@ -178,6 +181,11 @@ const Form = { this.$forceUpdate(); }, }, + computed: { + vertical() { + return this.layout === 'vertical'; + }, + }, beforeUpdate() { this.formItemContexts.forEach((number, c) => { if (c.$forceUpdate) { @@ -220,7 +228,7 @@ const Form = { [`${prefixCls}-hide-required-mark`]: hideRequiredMark, }); if (autoFormCreate) { - warning(false, '`autoFormCreate` is deprecated. please use `form` instead.'); + warning(false, 'Form', '`autoFormCreate` is deprecated. please use `form` instead.'); const DomForm = this.DomForm || createDOMForm({ diff --git a/components/form/FormItem.jsx b/components/form/FormItem.jsx index 95bdec62f..8731de351 100644 --- a/components/form/FormItem.jsx +++ b/components/form/FormItem.jsx @@ -26,6 +26,7 @@ function intersperseSpace(list) { } export const FormItemProps = { id: PropTypes.string, + htmlFor: PropTypes.string, prefixCls: PropTypes.string, label: PropTypes.any, labelCol: PropTypes.shape(ColProps).loose, @@ -39,6 +40,7 @@ export const FormItemProps = { fieldDecoratorId: PropTypes.string, fieldDecoratorOptions: PropTypes.object, selfUpdate: PropTypes.bool, + labelAlign: PropTypes.oneOf(['left', 'right']), }; function comeFromSlot(vnodes = [], itemVnode) { let isSlot = false; @@ -65,10 +67,15 @@ export default { mixins: [BaseMixin], props: initDefaultProps(FormItemProps, { hasFeedback: false, - colon: true, }), + provide() { + return { + isFormItemChildren: true, + }; + }, inject: { - FormProps: { default: () => ({}) }, + isFormItemChildren: { default: false }, + FormContextProps: { default: () => ({}) }, decoratorFormProps: { default: () => ({}) }, collectFormItemContext: { default: () => noop }, configProvider: { default: () => ConfigConsumerProps }, @@ -78,7 +85,7 @@ export default { }, computed: { itemSelfUpdate() { - return !!(this.selfUpdate === undefined ? this.FormProps.selfUpdate : this.selfUpdate); + return !!(this.selfUpdate === undefined ? this.FormContextProps.selfUpdate : this.selfUpdate); }, }, created() { @@ -90,7 +97,7 @@ export default { } }, beforeDestroy() { - this.collectFormItemContext(this.$vnode.context, 'delete'); + this.collectFormItemContext(this.$vnode && this.$vnode.context, 'delete'); }, mounted() { const { help, validateStatus } = this.$props; @@ -98,18 +105,20 @@ export default { this.getControls(this.slotDefault, true).length <= 1 || help !== undefined || validateStatus !== undefined, - '`Form.Item` cannot generate `validateStatus` and `help` automatically, ' + + '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.FormProps.form && this.FormProps.form.templateContext) { - const { templateContext } = this.FormProps.form; + if (this.FormContextProps.form && this.FormContextProps.form.templateContext) { + const { templateContext } = this.FormContextProps.form; const vnodes = Object.values(templateContext.$slots || {}).reduce((a, b) => { return [...a, ...b]; }, []); @@ -208,6 +217,39 @@ export default { 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(e) { + const id = this.id || this.getId(); + if (!id) { + return; + } + const formItemNode = this.$el; + const control = formItemNode.querySelector(`[id="${id}"]`); + if (control && control.focus) { + control.focus(); + } + }, + onHelpAnimEnd(_key, helpShow) { this.helpShow = helpShow; if (!helpShow) { @@ -215,6 +257,24 @@ 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 ? ( @@ -241,25 +301,6 @@ export default { return extra ?
{extra}
: null; }, - 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 ''; - }, - renderValidateWrapper(prefixCls, c1, c2, c3) { const props = this.$props; const onlyControl = this.getOnlyControl; @@ -315,10 +356,13 @@ export default { }, renderWrapper(prefixCls, children) { - const { FormProps: { wrapperCol: wrapperColForm = {} } = {} } = this; - const { wrapperCol = wrapperColForm } = this; - const { class: cls, style, id, on, ...restProps } = wrapperCol; - const className = classNames(`${prefixCls}-item-control-wrapper`, cls); + const { wrapperCol: contextWrapperCol } = this.isFormItemChildren + ? {} + : this.FormContextProps; + const { wrapperCol } = this; + const mergedWrapperCol = wrapperCol || contextWrapperCol || {}; + const { style, id, on, ...restProps } = mergedWrapperCol; + const className = classNames(`${prefixCls}-item-control-wrapper`, mergedWrapperCol.class); const colProps = { props: restProps, class: className, @@ -330,70 +374,45 @@ export default { return {children}; }, - 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; - }, - - // Resolve duplicated ids bug between different forms - // https://github.com/ant-design/ant-design/issues/7351 - onLabelClick(e) { - const label = getComponentFromProp(this, 'label'); - const id = this.id || this.getId(); - if (!id) { - return; - } - const formItemNode = this.$el; - const control = formItemNode.querySelector(`[id="${id}"]`); - if (control) { - // Only prevent in default situation - // Avoid preventing event in `label={link}`` - if (typeof label === 'string') { - e.preventDefault(); - } - if (control.focus) { - control.focus(); - } - } - }, - renderLabel(prefixCls) { - const { FormProps: { labelCol: labelColForm = {} } = {} } = this; - const { labelCol = labelColForm, colon, id } = this; + const { + vertical, + labelAlign: contextLabelAlign, + labelCol: contextLabelCol, + colon: contextColon, + } = this.FormContextProps; + const { labelAlign, labelCol, colon, id, htmlFor } = this; const label = getComponentFromProp(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, on, ...restProps - } = labelCol; - const labelColClassName = classNames(`${prefixCls}-item-label`, labelColClass); - const labelClassName = classNames({ - [`${prefixCls}-item-required`]: required, - }); - + } = mergedLabelCol; let labelChildren = label; // Keep label is original where there should have no colon - const haveColon = colon && this.FormProps.layout !== 'vertical'; + 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*$/, ''); + labelChildren = label.replace(/[::]\s*$/, ''); } + + const labelClassName = classNames({ + [`${prefixCls}-item-required`]: required, + [`${prefixCls}-item-no-colon`]: !computedColon, + }); const colProps = { props: restProps, class: labelColClassName, @@ -406,7 +425,7 @@ export default { return label ? (