feat: update form

pull/1790/head
tangjinzhou 2020-02-15 17:56:01 +08:00
parent e8f5b5a4c6
commit 894f954cb8
13 changed files with 228 additions and 199 deletions

View File

@ -1,5 +1,5 @@
module.exports = { module.exports = {
dev: { dev: {
componentName: 'empty', // dev components componentName: 'form', // dev components
}, },
}; };

View File

@ -62,6 +62,8 @@ export const FormProps = {
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']), layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
labelCol: PropTypes.shape(ColProps).loose, labelCol: PropTypes.shape(ColProps).loose,
wrapperCol: PropTypes.shape(ColProps).loose, wrapperCol: PropTypes.shape(ColProps).loose,
colon: PropTypes.bool,
labelAlign: PropTypes.oneOf(['left', 'right']),
form: PropTypes.object, form: PropTypes.object,
// onSubmit: React.FormEventHandler<any>; // onSubmit: React.FormEventHandler<any>;
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
@ -128,6 +130,7 @@ const Form = {
props: initDefaultProps(FormProps, { props: initDefaultProps(FormProps, {
layout: 'horizontal', layout: 'horizontal',
hideRequiredMark: false, hideRequiredMark: false,
colon: true,
}), }),
Item: FormItem, Item: FormItem,
createFormField: createFormField, createFormField: createFormField,
@ -148,7 +151,7 @@ const Form = {
}, },
provide() { provide() {
return { return {
FormProps: this.$props, FormContextProps: this,
// https://github.com/vueComponent/ant-design-vue/issues/446 // https://github.com/vueComponent/ant-design-vue/issues/446
collectFormItemContext: collectFormItemContext:
this.form && this.form.templateContext this.form && this.form.templateContext
@ -178,6 +181,11 @@ const Form = {
this.$forceUpdate(); this.$forceUpdate();
}, },
}, },
computed: {
vertical() {
return this.layout === 'vertical';
},
},
beforeUpdate() { beforeUpdate() {
this.formItemContexts.forEach((number, c) => { this.formItemContexts.forEach((number, c) => {
if (c.$forceUpdate) { if (c.$forceUpdate) {
@ -220,7 +228,7 @@ const Form = {
[`${prefixCls}-hide-required-mark`]: hideRequiredMark, [`${prefixCls}-hide-required-mark`]: hideRequiredMark,
}); });
if (autoFormCreate) { if (autoFormCreate) {
warning(false, '`autoFormCreate` is deprecated. please use `form` instead.'); warning(false, 'Form', '`autoFormCreate` is deprecated. please use `form` instead.');
const DomForm = const DomForm =
this.DomForm || this.DomForm ||
createDOMForm({ createDOMForm({

View File

@ -26,6 +26,7 @@ function intersperseSpace(list) {
} }
export const FormItemProps = { export const FormItemProps = {
id: PropTypes.string, id: PropTypes.string,
htmlFor: PropTypes.string,
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
label: PropTypes.any, label: PropTypes.any,
labelCol: PropTypes.shape(ColProps).loose, labelCol: PropTypes.shape(ColProps).loose,
@ -39,6 +40,7 @@ export const FormItemProps = {
fieldDecoratorId: PropTypes.string, fieldDecoratorId: PropTypes.string,
fieldDecoratorOptions: PropTypes.object, fieldDecoratorOptions: PropTypes.object,
selfUpdate: PropTypes.bool, selfUpdate: PropTypes.bool,
labelAlign: PropTypes.oneOf(['left', 'right']),
}; };
function comeFromSlot(vnodes = [], itemVnode) { function comeFromSlot(vnodes = [], itemVnode) {
let isSlot = false; let isSlot = false;
@ -65,10 +67,15 @@ export default {
mixins: [BaseMixin], mixins: [BaseMixin],
props: initDefaultProps(FormItemProps, { props: initDefaultProps(FormItemProps, {
hasFeedback: false, hasFeedback: false,
colon: true,
}), }),
provide() {
return {
isFormItemChildren: true,
};
},
inject: { inject: {
FormProps: { default: () => ({}) }, isFormItemChildren: { default: false },
FormContextProps: { default: () => ({}) },
decoratorFormProps: { default: () => ({}) }, decoratorFormProps: { default: () => ({}) },
collectFormItemContext: { default: () => noop }, collectFormItemContext: { default: () => noop },
configProvider: { default: () => ConfigConsumerProps }, configProvider: { default: () => ConfigConsumerProps },
@ -78,7 +85,7 @@ export default {
}, },
computed: { computed: {
itemSelfUpdate() { itemSelfUpdate() {
return !!(this.selfUpdate === undefined ? this.FormProps.selfUpdate : this.selfUpdate); return !!(this.selfUpdate === undefined ? this.FormContextProps.selfUpdate : this.selfUpdate);
}, },
}, },
created() { created() {
@ -90,7 +97,7 @@ export default {
} }
}, },
beforeDestroy() { beforeDestroy() {
this.collectFormItemContext(this.$vnode.context, 'delete'); this.collectFormItemContext(this.$vnode && this.$vnode.context, 'delete');
}, },
mounted() { mounted() {
const { help, validateStatus } = this.$props; const { help, validateStatus } = this.$props;
@ -98,18 +105,20 @@ export default {
this.getControls(this.slotDefault, true).length <= 1 || this.getControls(this.slotDefault, true).length <= 1 ||
help !== undefined || help !== undefined ||
validateStatus !== 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.', 'while there are more than one `getFieldDecorator` in it.',
); );
warning( warning(
!this.fieldDecoratorId, !this.fieldDecoratorId,
'Form.Item',
'`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.', '`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.',
); );
}, },
methods: { methods: {
collectContext() { collectContext() {
if (this.FormProps.form && this.FormProps.form.templateContext) { if (this.FormContextProps.form && this.FormContextProps.form.templateContext) {
const { templateContext } = this.FormProps.form; const { templateContext } = this.FormContextProps.form;
const vnodes = Object.values(templateContext.$slots || {}).reduce((a, b) => { const vnodes = Object.values(templateContext.$slots || {}).reduce((a, b) => {
return [...a, ...b]; return [...a, ...b];
}, []); }, []);
@ -208,6 +217,39 @@ export default {
return this.getChildAttr(FIELD_DATA_PROP); 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) { onHelpAnimEnd(_key, helpShow) {
this.helpShow = helpShow; this.helpShow = helpShow;
if (!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) { renderHelp(prefixCls) {
const help = this.getHelpMessage(); const help = this.getHelpMessage();
const children = help ? ( const children = help ? (
@ -241,25 +301,6 @@ export default {
return extra ? <div class={`${prefixCls}-extra`}>{extra}</div> : null; return extra ? <div class={`${prefixCls}-extra`}>{extra}</div> : 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) { renderValidateWrapper(prefixCls, c1, c2, c3) {
const props = this.$props; const props = this.$props;
const onlyControl = this.getOnlyControl; const onlyControl = this.getOnlyControl;
@ -315,10 +356,13 @@ export default {
}, },
renderWrapper(prefixCls, children) { renderWrapper(prefixCls, children) {
const { FormProps: { wrapperCol: wrapperColForm = {} } = {} } = this; const { wrapperCol: contextWrapperCol } = this.isFormItemChildren
const { wrapperCol = wrapperColForm } = this; ? {}
const { class: cls, style, id, on, ...restProps } = wrapperCol; : this.FormContextProps;
const className = classNames(`${prefixCls}-item-control-wrapper`, cls); const { wrapperCol } = this;
const mergedWrapperCol = wrapperCol || contextWrapperCol || {};
const { style, id, on, ...restProps } = mergedWrapperCol;
const className = classNames(`${prefixCls}-item-control-wrapper`, mergedWrapperCol.class);
const colProps = { const colProps = {
props: restProps, props: restProps,
class: className, class: className,
@ -330,70 +374,45 @@ export default {
return <Col {...colProps}>{children}</Col>; return <Col {...colProps}>{children}</Col>;
}, },
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={<a href="xx">link</a>}``
if (typeof label === 'string') {
e.preventDefault();
}
if (control.focus) {
control.focus();
}
}
},
renderLabel(prefixCls) { renderLabel(prefixCls) {
const { FormProps: { labelCol: labelColForm = {} } = {} } = this; const {
const { labelCol = labelColForm, colon, id } = this; vertical,
labelAlign: contextLabelAlign,
labelCol: contextLabelCol,
colon: contextColon,
} = this.FormContextProps;
const { labelAlign, labelCol, colon, id, htmlFor } = this;
const label = getComponentFromProp(this, 'label'); const label = getComponentFromProp(this, 'label');
const required = this.isRequired(); 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 { const {
class: labelColClass, class: labelColClass,
style: labelColStyle, style: labelColStyle,
id: labelColId, id: labelColId,
on, on,
...restProps ...restProps
} = labelCol; } = mergedLabelCol;
const labelColClassName = classNames(`${prefixCls}-item-label`, labelColClass);
const labelClassName = classNames({
[`${prefixCls}-item-required`]: required,
});
let labelChildren = label; let labelChildren = label;
// Keep label is original where there should have no colon // 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 // Remove duplicated user input colon
if (haveColon && typeof label === 'string' && label.trim() !== '') { 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 = { const colProps = {
props: restProps, props: restProps,
class: labelColClassName, class: labelColClassName,
@ -406,7 +425,7 @@ export default {
return label ? ( return label ? (
<Col {...colProps}> <Col {...colProps}>
<label <label
for={id || this.getId()} for={htmlFor || id || this.getId()}
class={labelClassName} class={labelClassName}
title={typeof label === 'string' ? label : ''} title={typeof label === 'string' ? label : ''}
onClick={this.onLabelClick} onClick={this.onLabelClick}
@ -431,23 +450,27 @@ export default {
]; ];
}, },
renderFormItem() { renderFormItem() {
const { prefixCls: customizePrefixCls, colon } = this.$props; const { prefixCls: customizePrefixCls } = this.$props;
const getPrefixCls = this.configProvider.getPrefixCls; const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('form', customizePrefixCls); const prefixCls = getPrefixCls('form', customizePrefixCls);
const children = this.renderChildren(prefixCls); const children = this.renderChildren(prefixCls);
const itemClassName = { const itemClassName = {
[`${prefixCls}-item`]: true, [`${prefixCls}-item`]: true,
[`${prefixCls}-item-with-help`]: this.helpShow, [`${prefixCls}-item-with-help`]: this.helpShow,
[`${prefixCls}-item-no-colon`]: !colon,
}; };
return <Row class={classNames(itemClassName)}>{children}</Row>; return (
<Row class={classNames(itemClassName)} key="row">
{children}
</Row>
);
}, },
decoratorOption(vnode) { decoratorOption(vnode) {
if (vnode.data && vnode.data.directives) { if (vnode.data && vnode.data.directives) {
const directive = find(vnode.data.directives, ['name', 'decorator']); const directive = find(vnode.data.directives, ['name', 'decorator']);
warning( warning(
!directive || (directive && Array.isArray(directive.value)), !directive || (directive && Array.isArray(directive.value)),
'Form',
`Invalid directive: type check failed for directive "decorator". Expected Array, got ${typeof (directive `Invalid directive: type check failed for directive "decorator". Expected Array, got ${typeof (directive
? directive.value ? directive.value
: directive)}. At ${vnode.tag}.`, : directive)}. At ${vnode.tag}.`,
@ -458,8 +481,8 @@ export default {
} }
}, },
decoratorChildren(vnodes) { decoratorChildren(vnodes) {
const { FormProps } = this; const { FormContextProps } = this;
const getFieldDecorator = FormProps.form.getFieldDecorator; const getFieldDecorator = FormContextProps.form.getFieldDecorator;
for (let i = 0, len = vnodes.length; i < len; i++) { for (let i = 0, len = vnodes.length; i < len; i++) {
const vnode = vnodes[i]; const vnode = vnodes[i];
if (getSlotOptions(vnode).__ANT_FORM_ITEM) { if (getSlotOptions(vnode).__ANT_FORM_ITEM) {
@ -487,7 +510,7 @@ export default {
decoratorFormProps, decoratorFormProps,
fieldDecoratorId, fieldDecoratorId,
fieldDecoratorOptions = {}, fieldDecoratorOptions = {},
FormProps, FormContextProps,
} = this; } = this;
let child = filterEmpty($slots.default || []); let child = filterEmpty($slots.default || []);
if (decoratorFormProps.form && fieldDecoratorId && child.length) { if (decoratorFormProps.form && fieldDecoratorId && child.length) {
@ -495,10 +518,11 @@ export default {
child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions, this)(child[0]); child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions, this)(child[0]);
warning( warning(
!(child.length > 1), !(child.length > 1),
'Form',
'`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children', '`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children',
); );
this.slotDefault = child; this.slotDefault = child;
} else if (FormProps.form) { } else if (FormContextProps.form) {
child = cloneVNodes(child); child = cloneVNodes(child);
this.slotDefault = this.decoratorChildren(child); this.slotDefault = this.decoratorChildren(child);
} else { } else {

View File

@ -470,7 +470,7 @@ exports[`renders ./components/form/demo/time-related-controls.vue correctly 1`]
<div class="ant-row ant-form-item"> <div class="ant-row ant-form-item">
<div class="ant-col-xs-24 ant-col-sm-8 ant-form-item-label"><label for="time_related_controls_date-time-picker" title="DatePicker[showTime]" class="ant-form-item-required">DatePicker[showTime]</label></div> <div class="ant-col-xs-24 ant-col-sm-8 ant-form-item-label"><label for="time_related_controls_date-time-picker" title="DatePicker[showTime]" class="ant-form-item-required">DatePicker[showTime]</label></div>
<div class="ant-col-xs-24 ant-col-sm-16 ant-form-item-control-wrapper"> <div class="ant-col-xs-24 ant-col-sm-16 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><span class="ant-calendar-picker" style="width: 195px;" data-__meta="[object Object]" data-__field="[object Object]" id="time_related_controls_date-time-picker"><div class=""><input readonly="true" placeholder="Select date" class="ant-calendar-picker-input ant-input"><i aria-label="icon: calendar" class="ant-calendar-picker-icon anticon anticon-calendar"><svg viewBox="64 64 896 896" data-icon="calendar" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"></path></svg></i></div></span></span> <div class="ant-form-item-control"><span class="ant-form-item-children"><span class="ant-calendar-picker" style="min-width: 195px;" data-__meta="[object Object]" data-__field="[object Object]" id="time_related_controls_date-time-picker"><div class=""><input readonly="true" placeholder="Select date" class="ant-calendar-picker-input ant-input"><i aria-label="icon: calendar" class="ant-calendar-picker-icon anticon anticon-calendar"><svg viewBox="64 64 896 896" data-icon="calendar" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"></path></svg></i></div></span></span>
<!----> <!---->
</div> </div>
</div> </div>
@ -486,9 +486,7 @@ exports[`renders ./components/form/demo/time-related-controls.vue correctly 1`]
<div class="ant-row ant-form-item"> <div class="ant-row ant-form-item">
<div class="ant-col-xs-24 ant-col-sm-8 ant-form-item-label"><label for="time_related_controls_range-picker" title="RangePicker" class="ant-form-item-required">RangePicker</label></div> <div class="ant-col-xs-24 ant-col-sm-8 ant-form-item-label"><label for="time_related_controls_range-picker" title="RangePicker" class="ant-form-item-required">RangePicker</label></div>
<div class="ant-col-xs-24 ant-col-sm-16 ant-form-item-control-wrapper"> <div class="ant-col-xs-24 ant-col-sm-16 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><span tabindex="0" class="ant-calendar-picker" data-__meta="[object Object]" data-__field="[object Object]" id="time_related_controls_range-picker"><span class="ant-calendar-picker-input ant-input"><input readonly="true" placeholder="Start date" tabindex="-1" class="ant-calendar-range-picker-input"><span class="ant-calendar-range-picker-separator"> ~ </span><input readonly="true" placeholder="End date" tabindex="-1" class="ant-calendar-range-picker-input"><i aria-label="icon: calendar" class="ant-calendar-picker-icon anticon anticon-calendar"><svg viewBox="64 64 896 896" data-icon="calendar" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""> <div class="ant-form-item-control"><span class="ant-form-item-children"><span tabindex="0" class="ant-calendar-picker" data-__meta="[object Object]" data-__field="[object Object]" id="time_related_controls_range-picker"><span class="ant-calendar-picker-input ant-input"><input readonly="true" placeholder="Start date" tabindex="-1" class="ant-calendar-range-picker-input"><span class="ant-calendar-range-picker-separator"> ~ </span><input readonly="true" placeholder="End date" tabindex="-1" class="ant-calendar-range-picker-input"><span class="ant-calendar-picker-icon"></span></span></span></span>
<path d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"></path>
</svg></i></span></span></span>
<!----> <!---->
</div> </div>
</div> </div>
@ -496,9 +494,7 @@ exports[`renders ./components/form/demo/time-related-controls.vue correctly 1`]
<div class="ant-row ant-form-item"> <div class="ant-row ant-form-item">
<div class="ant-col-xs-24 ant-col-sm-8 ant-form-item-label"><label for="time_related_controls_range-time-picker" title="RangePicker[showTime]" class="ant-form-item-required">RangePicker[showTime]</label></div> <div class="ant-col-xs-24 ant-col-sm-8 ant-form-item-label"><label for="time_related_controls_range-time-picker" title="RangePicker[showTime]" class="ant-form-item-required">RangePicker[showTime]</label></div>
<div class="ant-col-xs-24 ant-col-sm-16 ant-form-item-control-wrapper"> <div class="ant-col-xs-24 ant-col-sm-16 ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children"><span tabindex="0" class="ant-calendar-picker" style="width: 350px;" data-__meta="[object Object]" data-__field="[object Object]" id="time_related_controls_range-time-picker"><span class="ant-calendar-picker-input ant-input"><input readonly="true" placeholder="Start date" tabindex="-1" class="ant-calendar-range-picker-input"><span class="ant-calendar-range-picker-separator"> ~ </span><input readonly="true" placeholder="End date" tabindex="-1" class="ant-calendar-range-picker-input"><i aria-label="icon: calendar" class="ant-calendar-picker-icon anticon anticon-calendar"><svg viewBox="64 64 896 896" data-icon="calendar" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""> <div class="ant-form-item-control"><span class="ant-form-item-children"><span tabindex="0" class="ant-calendar-picker" style="width: 350px;" data-__meta="[object Object]" data-__field="[object Object]" id="time_related_controls_range-time-picker"><span class="ant-calendar-picker-input ant-input"><input readonly="true" placeholder="Start date" tabindex="-1" class="ant-calendar-range-picker-input"><span class="ant-calendar-range-picker-separator"> ~ </span><input readonly="true" placeholder="End date" tabindex="-1" class="ant-calendar-range-picker-input"><span class="ant-calendar-picker-icon"></span></span></span></span>
<path d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"></path>
</svg></i></span></span></span>
<!----> <!---->
</div> </div>
</div> </div>
@ -794,6 +790,30 @@ exports[`renders ./components/form/demo/validate-static.vue correctly 1`] = `
</div> </div>
</div> </div>
</div> </div>
<div class="ant-row ant-form-item">
<div class="ant-col-xs-24 ant-col-sm-5 ant-form-item-label"><label title="Success" class="">Success</label></div>
<div class="ant-col-xs-24 ant-col-sm-12 ant-form-item-control-wrapper">
<div class="ant-form-item-control has-feedback has-success"><span class="ant-form-item-children"><span class="ant-input-affix-wrapper"><input placeholder="with allowClear" type="text" class="ant-input"><span class="ant-input-suffix"></span></span><span class="ant-form-item-children-icon"><i aria-label="icon: check-circle" class="anticon anticon-check-circle"><svg viewBox="64 64 896 896" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 0 1-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg></i></span></span>
<!---->
</div>
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col-xs-24 ant-col-sm-5 ant-form-item-label"><label title="Warning" class="">Warning</label></div>
<div class="ant-col-xs-24 ant-col-sm-12 ant-form-item-control-wrapper">
<div class="ant-form-item-control has-feedback has-warning"><span class="ant-form-item-children"><span class="ant-input-affix-wrapper ant-input-password"><input placeholder="with input password" type="password" class="ant-input"><span class="ant-input-suffix"><i aria-label="icon: eye-invisible" tabindex="-1" class="ant-input-password-icon anticon anticon-eye-invisible"><svg viewBox="64 64 896 896" data-icon="eye-invisible" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 0 0 0-51.5zm-63.57-320.64L836 122.88a8 8 0 0 0-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 0 0 0 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 0 0 0 11.31L155.17 889a8 8 0 0 0 11.31 0l712.15-712.12a8 8 0 0 0 0-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 0 0-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 0 1 146.2-106.69L401.31 546.2A112 112 0 0 1 396 512z"></path><path d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 0 0 227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 0 1-112 112z"></path></svg></i></span></span><span class="ant-form-item-children-icon"><i aria-label="icon: exclamation-circle" class="anticon anticon-exclamation-circle"><svg viewBox="64 64 896 896" data-icon="exclamation-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"></path></svg></i></span></span>
<!---->
</div>
</div>
</div>
<div class="ant-row ant-form-item">
<div class="ant-col-xs-24 ant-col-sm-5 ant-form-item-label"><label title="Error" class="">Error</label></div>
<div class="ant-col-xs-24 ant-col-sm-12 ant-form-item-control-wrapper">
<div class="ant-form-item-control has-feedback has-error"><span class="ant-form-item-children"><span class="ant-input-affix-wrapper ant-input-password"><input placeholder="with input password and allowClear" type="password" class="ant-input"><span class="ant-input-suffix"><i aria-label="icon: eye-invisible" tabindex="-1" class="ant-input-password-icon anticon anticon-eye-invisible"><svg viewBox="64 64 896 896" data-icon="eye-invisible" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 0 0 0-51.5zm-63.57-320.64L836 122.88a8 8 0 0 0-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 0 0 0 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 0 0 0 11.31L155.17 889a8 8 0 0 0 11.31 0l712.15-712.12a8 8 0 0 0 0-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 0 0-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 0 1 146.2-106.69L401.31 546.2A112 112 0 0 1 396 512z"></path><path d="M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 0 0 227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 0 1-112 112z"></path></svg></i></span></span><span class="ant-form-item-children-icon"><i aria-label="icon: close-circle" class="anticon anticon-close-circle"><svg viewBox="64 64 896 896" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 0 1-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg></i></span></span>
<!---->
</div>
</div>
</div>
</form> </form>
`; `;

View File

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Form Form.Item should support data-*、aria-* and custom attribute 1`] = `
<form class="ant-form ant-form-horizontal">
<div class="ant-row ant-form-item" data-text="123" aria-hidden="true" cccc="bbbb">
<div class="ant-form-item-control-wrapper">
<div class="ant-form-item-control"><span class="ant-form-item-children">text</span>
<!---->
</div>
</div>
</div>
</form>
`;

View File

@ -1,7 +1,26 @@
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Form from '..'; import Form from '..';
import mountTest from '../../../tests/shared/mountTest';
describe('Form', () => { describe('Form', () => {
mountTest(Form);
mountTest(Form.Item);
it('Form.Item should support data-*、aria-* and custom attribute', () => {
const wrapper = mount({
render() {
return (
<Form>
<Form.Item data-text="123" aria-hidden="true" cccc="bbbb">
text
</Form.Item>
</Form>
);
},
});
expect(wrapper.html()).toMatchSnapshot();
});
it('hideRequiredMark', () => { it('hideRequiredMark', () => {
const wrapper = mount(Form, { const wrapper = mount(Form, {
propsData: { propsData: {

View File

@ -9,13 +9,13 @@ Use `setFieldsValue` to set other control's value programmaticly.
</us> </us>
<template> <template>
<a-form :form="form" @submit="handleSubmit"> <a-form :form="form" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }" @submit="handleSubmit">
<a-form-item label="Note" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }"> <a-form-item label="Note">
<a-input <a-input
v-decorator="['note', { rules: [{ required: true, message: 'Please input your note!' }] }]" v-decorator="['note', { rules: [{ required: true, message: 'Please input your note!' }] }]"
/> />
</a-form-item> </a-form-item>
<a-form-item label="Gender" :label-col="{ span: 5 }" :wrapper-col="{ span: 12 }"> <a-form-item label="Gender">
<a-select <a-select
v-decorator="[ v-decorator="[
'gender', 'gender',

View File

@ -1,17 +1,17 @@
<cn> <cn>
#### 自定义表单控件 #### 自定义表单控件
自定义或第三方的表单控件也可以与 Form 组件一起使用只要该组件遵循以下的约定 自定义或第三方的表单控件也可以与 Form 组件一起使用只要该组件遵循以下的约定
> * 提供受控属性 `value` 或其它与 [`valuePropName`](/components/form-cn/#getFieldDecorator(id,-options)-) > - 提供受控属性 `value` 或其它与 [`valuePropName`](/components/form-cn/#getFieldDecorator(id,-options)-)
> * 提供 `onChange` 事件或 [`trigger`](/components/form-cn/#getFieldDecorator(id,-options)-) > - 提供 `onChange` 事件或 [`trigger`](/components/form-cn/#getFieldDecorator(id,-options)-)
> * 不能是函数式组件 > - 不能是函数式组件
</cn> </cn>
<us> <us>
#### Customized Form Controls #### Customized Form Controls
Customized or third-party form controls can be used in Form, too. Controls must follow these conventions: Customized or third-party form controls can be used in Form, too. Controls must follow these conventions:
> * It has a controlled property `value` or other name which is equal to the value of [`valuePropName`](/components/form/#getFieldDecorator(id,-options)-parameters). > - It has a controlled property `value` or other name which is equal to the value of [`valuePropName`](/components/form/#getFieldDecorator(id,-options)-parameters).
> * It has event `onChange` or an event which name is equal to the value of [`trigger`](/components/form/#getFieldDecorator(id,-options)-parameters). > - It has event `onChange` or an event which name is equal to the value of [`trigger`](/components/form/#getFieldDecorator(id,-options)-parameters).
> * It must be a class component. > - It must be a class component.
</us> </us>
<template> <template>
@ -80,15 +80,9 @@ const PriceInput = {
if (isNaN(number)) { if (isNaN(number)) {
return; return;
} }
if (!hasProp(this, 'value')) {
this.number = number;
}
this.triggerChange({ number }); this.triggerChange({ number });
}, },
handleCurrencyChange(currency) { handleCurrencyChange(currency) {
if (!hasProp(this, 'value')) {
this.currency = currency;
}
this.triggerChange({ currency }); this.triggerChange({ currency });
}, },
triggerChange(changedValue) { triggerChange(changedValue) {

View File

@ -114,7 +114,12 @@ export default {
e.preventDefault(); e.preventDefault();
this.form.validateFields((err, values) => { this.form.validateFields((err, values) => {
if (!err) { if (!err) {
const { keys, names } = values;
console.log('Received values of form: ', values); console.log('Received values of form: ', values);
console.log(
'Merged values:',
keys.map(key => names[key]),
);
} }
}); });
}, },

View File

@ -1,11 +1,11 @@
<cn> <cn>
#### 水平登录栏 #### 内联登录栏
水平登录栏常用在顶部导航栏中 水平登录栏常用在顶部导航栏中
</cn> </cn>
<us> <us>
#### Horizontal Login Form #### Inline Login Form
Horizontal login form is often used in navigation bar. Inline login form is often used in navigation bar.
</us> </us>
<template> <template>

View File

@ -29,7 +29,7 @@ Fill in this form to create a new account for you.
]" ]"
/> />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="Password"> <a-form-item v-bind="formItemLayout" label="Password" has-feedback>
<a-input <a-input
v-decorator="[ v-decorator="[
'password', 'password',
@ -48,7 +48,7 @@ Fill in this form to create a new account for you.
type="password" type="password"
/> />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="Confirm Password"> <a-form-item v-bind="formItemLayout" label="Confirm Password" has-feedback>
<a-input <a-input
v-decorator="[ v-decorator="[
'confirm', 'confirm',

View File

@ -9,31 +9,31 @@ The `value` of time-related components is a `moment` object, which we need to pr
</us> </us>
<template> <template>
<a-form :form="form" @submit="handleSubmit"> <a-form v-bind="formItemLayout" :form="form" @submit="handleSubmit">
<a-form-item v-bind="formItemLayout" label="DatePicker"> <a-form-item label="DatePicker">
<a-date-picker v-decorator="['date-picker', config]" /> <a-date-picker v-decorator="['date-picker', config]" />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="DatePicker[showTime]"> <a-form-item label="DatePicker[showTime]">
<a-date-picker <a-date-picker
v-decorator="['date-time-picker', config]" v-decorator="['date-time-picker', config]"
show-time show-time
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
/> />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="MonthPicker"> <a-form-item label="MonthPicker">
<a-monthPicker v-decorator="['month-picker', config]" /> <a-monthPicker v-decorator="['month-picker', config]" />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="RangePicker"> <a-form-item label="RangePicker">
<a-range-picker v-decorator="['range-picker', rangeConfig]" /> <a-range-picker v-decorator="['range-picker', rangeConfig]" />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="RangePicker[showTime]"> <a-form-item label="RangePicker[showTime]">
<a-range-picker <a-range-picker
v-decorator="['range-time-picker', rangeConfig]" v-decorator="['range-time-picker', rangeConfig]"
show-time show-time
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
/> />
</a-form-item> </a-form-item>
<a-form-item v-bind="formItemLayout" label="TimePicker"> <a-form-item label="TimePicker">
<a-time-picker v-decorator="['time-picker', config]" /> <a-time-picker v-decorator="['time-picker', config]" />
</a-form-item> </a-form-item>
<a-form-item <a-form-item

View File

@ -15,29 +15,18 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
</us> </us>
<template> <template>
<a-form> <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item <a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Fail" label="Fail"
validate-status="error" validate-status="error"
help="Should be combination of numbers & alphabets" help="Should be combination of numbers & alphabets"
> >
<a-input id="error" placeholder="unavailable choice" /> <a-input id="error" placeholder="unavailable choice" />
</a-form-item> </a-form-item>
<a-form-item label="Warning" validate-status="warning">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Warning"
validate-status="warning"
>
<a-input id="warning" placeholder="Warning" /> <a-input id="warning" placeholder="Warning" />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Validating" label="Validating"
has-feedback has-feedback
validate-status="validating" validate-status="validating"
@ -45,30 +34,13 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
> >
<a-input id="validating" placeholder="I'm the content is being validated" /> <a-input id="validating" placeholder="I'm the content is being validated" />
</a-form-item> </a-form-item>
<a-form-item label="Success" has-feedback validate-status="success">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Success"
has-feedback
validate-status="success"
>
<a-input id="success" placeholder="I'm the content" /> <a-input id="success" placeholder="I'm the content" />
</a-form-item> </a-form-item>
<a-form-item label="Warning" has-feedback validate-status="warning">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Warning"
has-feedback
validate-status="warning"
>
<a-input id="warning2" placeholder="Warning" /> <a-input id="warning2" placeholder="Warning" />
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Fail" label="Fail"
has-feedback has-feedback
validate-status="error" validate-status="error"
@ -76,34 +48,13 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
> >
<a-input id="error2" placeholder="unavailable choice" /> <a-input id="error2" placeholder="unavailable choice" />
</a-form-item> </a-form-item>
<a-form-item label="Success" has-feedback validate-status="success">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Success"
has-feedback
validate-status="success"
>
<a-date-picker style="width: 100%" /> <a-date-picker style="width: 100%" />
</a-form-item> </a-form-item>
<a-form-item label="Warning" has-feedback validate-status="warning">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Warning"
has-feedback
validate-status="warning"
>
<a-time-picker style="width: 100%" /> <a-time-picker style="width: 100%" />
</a-form-item> </a-form-item>
<a-form-item label="Error" has-feedback validate-status="error">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Error"
has-feedback
validate-status="error"
>
<a-select default-value="1"> <a-select default-value="1">
<a-select-option value="1"> <a-select-option value="1">
Option 1 Option 1
@ -116,10 +67,7 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Validating" label="Validating"
has-feedback has-feedback
validate-status="validating" validate-status="validating"
@ -127,13 +75,7 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
> >
<a-cascader :default-value="['1']" :options="[]" /> <a-cascader :default-value="['1']" :options="[]" />
</a-form-item> </a-form-item>
<a-form-item label="inline" style="margin-bottom:0;">
<a-form-item
label="inline"
:label-col="labelCol"
:wrapper-col="wrapperCol"
style="margin-bottom:0;"
>
<a-form-item <a-form-item
validate-status="error" validate-status="error"
help="Please select the correct date" help="Please select the correct date"
@ -148,16 +90,20 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
<a-date-picker style="width: 100%" /> <a-date-picker style="width: 100%" />
</a-form-item> </a-form-item>
</a-form-item> </a-form-item>
<a-form-item label="Success" has-feedback validate-status="success">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Success"
has-feedback
validate-status="success"
>
<a-input-number style="width: 100%" /> <a-input-number style="width: 100%" />
</a-form-item> </a-form-item>
<a-form-item label="Success" has-feedback validate-status="success">
<a-input allow-clear placeholder="with allowClear" />
</a-form-item>
<a-form-item label="Warning" has-feedback validate-status="warning">
<a-input-password placeholder="with input password" />
</a-form-item>
<a-form-item label="Error" has-feedback validate-status="error">
<a-input-password allow-clear placeholder="with input password and allowClear" />
</a-form-item>
</a-form> </a-form>
</template> </template>
<script> <script>