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 = {
dev: {
componentName: 'empty', // dev components
componentName: 'form', // dev components
},
};

View File

@ -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<any>;
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({

View File

@ -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 ? <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) {
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 <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) {
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 ? (
<Col {...colProps}>
<label
for={id || this.getId()}
for={htmlFor || id || this.getId()}
class={labelClassName}
title={typeof label === 'string' ? label : ''}
onClick={this.onLabelClick}
@ -431,23 +450,27 @@ export default {
];
},
renderFormItem() {
const { prefixCls: customizePrefixCls, colon } = this.$props;
const { prefixCls: customizePrefixCls } = this.$props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('form', customizePrefixCls);
const children = this.renderChildren(prefixCls);
const itemClassName = {
[`${prefixCls}-item`]: true,
[`${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) {
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}.`,
@ -458,8 +481,8 @@ export default {
}
},
decoratorChildren(vnodes) {
const { FormProps } = this;
const getFieldDecorator = FormProps.form.getFieldDecorator;
const { FormContextProps } = this;
const getFieldDecorator = FormContextProps.form.getFieldDecorator;
for (let i = 0, len = vnodes.length; i < len; i++) {
const vnode = vnodes[i];
if (getSlotOptions(vnode).__ANT_FORM_ITEM) {
@ -487,7 +510,7 @@ export default {
decoratorFormProps,
fieldDecoratorId,
fieldDecoratorOptions = {},
FormProps,
FormContextProps,
} = this;
let child = filterEmpty($slots.default || []);
if (decoratorFormProps.form && fieldDecoratorId && child.length) {
@ -495,10 +518,11 @@ export default {
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 (FormProps.form) {
} else if (FormContextProps.form) {
child = cloneVNodes(child);
this.slotDefault = this.decoratorChildren(child);
} 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-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-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>
@ -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-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-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="">
<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 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>
<!---->
</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-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-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="">
<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 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>
<!---->
</div>
</div>
@ -794,6 +790,30 @@ exports[`renders ./components/form/demo/validate-static.vue correctly 1`] = `
</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>
`;

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 Form from '..';
import mountTest from '../../../tests/shared/mountTest';
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', () => {
const wrapper = mount(Form, {
propsData: {

View File

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

View File

@ -1,17 +1,17 @@
<cn>
#### 自定义表单控件
自定义或第三方的表单控件也可以与 Form 组件一起使用只要该组件遵循以下的约定
> * 提供受控属性 `value` 或其它与 [`valuePropName`](/components/form-cn/#getFieldDecorator(id,-options)-)
> * 提供 `onChange` 事件或 [`trigger`](/components/form-cn/#getFieldDecorator(id,-options)-)
> * 不能是函数式组件
> - 提供受控属性 `value` 或其它与 [`valuePropName`](/components/form-cn/#getFieldDecorator(id,-options)-)
> - 提供 `onChange` 事件或 [`trigger`](/components/form-cn/#getFieldDecorator(id,-options)-)
> - 不能是函数式组件
</cn>
<us>
#### Customized Form Controls
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 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 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 must be a class component.
</us>
<template>
@ -80,15 +80,9 @@ const PriceInput = {
if (isNaN(number)) {
return;
}
if (!hasProp(this, 'value')) {
this.number = number;
}
this.triggerChange({ number });
},
handleCurrencyChange(currency) {
if (!hasProp(this, 'value')) {
this.currency = currency;
}
this.triggerChange({ currency });
},
triggerChange(changedValue) {

View File

@ -114,7 +114,12 @@ export default {
e.preventDefault();
this.form.validateFields((err, values) => {
if (!err) {
const { keys, names } = 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>
<us>
#### Horizontal Login Form
Horizontal login form is often used in navigation bar.
#### Inline Login Form
Inline login form is often used in navigation bar.
</us>
<template>

View File

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

View File

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

View File

@ -15,29 +15,18 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
</us>
<template>
<a-form>
<a-form :label-col="labelCol" :wrapper-col="wrapperCol">
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Fail"
validate-status="error"
help="Should be combination of numbers & alphabets"
>
<a-input id="error" placeholder="unavailable choice" />
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Warning"
validate-status="warning"
>
<a-form-item label="Warning" validate-status="warning">
<a-input id="warning" placeholder="Warning" />
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Validating"
has-feedback
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-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Success"
has-feedback
validate-status="success"
>
<a-form-item label="Success" has-feedback validate-status="success">
<a-input id="success" placeholder="I'm the content" />
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Warning"
has-feedback
validate-status="warning"
>
<a-form-item label="Warning" has-feedback validate-status="warning">
<a-input id="warning2" placeholder="Warning" />
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Fail"
has-feedback
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-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Success"
has-feedback
validate-status="success"
>
<a-form-item label="Success" has-feedback validate-status="success">
<a-date-picker style="width: 100%" />
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Warning"
has-feedback
validate-status="warning"
>
<a-form-item label="Warning" has-feedback validate-status="warning">
<a-time-picker style="width: 100%" />
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Error"
has-feedback
validate-status="error"
>
<a-form-item label="Error" has-feedback validate-status="error">
<a-select default-value="1">
<a-select-option value="1">
Option 1
@ -116,10 +67,7 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Validating"
has-feedback
validate-status="validating"
@ -127,13 +75,7 @@ We provide properties like `validateStatus` `help` `hasFeedback` to customize yo
>
<a-cascader :default-value="['1']" :options="[]" />
</a-form-item>
<a-form-item
label="inline"
:label-col="labelCol"
:wrapper-col="wrapperCol"
style="margin-bottom:0;"
>
<a-form-item label="inline" style="margin-bottom:0;">
<a-form-item
validate-status="error"
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-form-item>
</a-form-item>
<a-form-item
:label-col="labelCol"
:wrapper-col="wrapperCol"
label="Success"
has-feedback
validate-status="success"
>
<a-form-item label="Success" has-feedback validate-status="success">
<a-input-number style="width: 100%" />
</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>
</template>
<script>