refactor: button date-picker drawer dropdown form by ts
parent
34b3e6366f
commit
2fdfa2b662
|
@ -10,7 +10,7 @@ const initDefaultProps = <T>(
|
|||
: any;
|
||||
},
|
||||
): T => {
|
||||
const propTypes = { ...types };
|
||||
const propTypes: T = { ...types } as T;
|
||||
Object.keys(defaultProps).forEach(k => {
|
||||
const prop = propTypes[k] as VueTypeValidableDef;
|
||||
if (prop) {
|
||||
|
|
|
@ -140,13 +140,14 @@ export default defineComponent({
|
|||
},
|
||||
render() {
|
||||
this.iconCom = getComponent(this, 'icon');
|
||||
const { type, htmlType, iconCom, disabled, handleClick, sLoading, $attrs } = this;
|
||||
const { type, htmlType, iconCom, disabled, handleClick, sLoading,href,title, $attrs } = this;
|
||||
const children = getSlot(this);
|
||||
this.children = children;
|
||||
const classes = this.getClasses();
|
||||
|
||||
const buttonProps = {
|
||||
...$attrs,
|
||||
title,
|
||||
disabled,
|
||||
class: classes,
|
||||
onClick: handleClick,
|
||||
|
@ -158,9 +159,9 @@ export default defineComponent({
|
|||
this.insertSpace(child, this.isNeedInserted() && autoInsertSpace),
|
||||
);
|
||||
|
||||
if ($attrs.href !== undefined) {
|
||||
if (href !== undefined) {
|
||||
return (
|
||||
<a {...buttonProps} ref="buttonNode">
|
||||
<a {...buttonProps} href={href} ref="buttonNode">
|
||||
{iconNode}
|
||||
{kids}
|
||||
</a>
|
||||
|
|
|
@ -22,5 +22,7 @@ export default () => ({
|
|||
ghost: PropTypes.looseBool,
|
||||
block: PropTypes.looseBool,
|
||||
icon: PropTypes.VNodeChild,
|
||||
href: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import { CSSProperties, DefineComponent, HTMLAttributes } from 'vue';
|
||||
import { CSSProperties, DefineComponent } from 'vue';
|
||||
import { tuple, VueNode } from '../_util/type';
|
||||
|
||||
export type RangePickerValue =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { inject, provide, nextTick } from 'vue';
|
||||
import { inject, provide, nextTick, defineComponent, App, CSSProperties } from 'vue';
|
||||
import classnames from '../_util/classNames';
|
||||
import omit from 'omit.js';
|
||||
import VcDrawer from '../vc-drawer/src';
|
||||
|
@ -7,8 +7,11 @@ import BaseMixin from '../_util/BaseMixin';
|
|||
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
|
||||
import { getComponent, getOptionProps } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { tuple } from '../_util/type';
|
||||
|
||||
const Drawer = {
|
||||
const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
|
||||
type placementType = typeof PlacementTypes[number];
|
||||
const Drawer = defineComponent({
|
||||
name: 'ADrawer',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
|
@ -22,16 +25,16 @@ const Drawer = {
|
|||
bodyStyle: PropTypes.object,
|
||||
headerStyle: PropTypes.object,
|
||||
drawerStyle: PropTypes.object,
|
||||
title: PropTypes.any,
|
||||
title: PropTypes.VNodeChild,
|
||||
visible: PropTypes.looseBool,
|
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256),
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256),
|
||||
zIndex: PropTypes.number,
|
||||
prefixCls: PropTypes.string,
|
||||
placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']).def('right'),
|
||||
placement: PropTypes.oneOf(PlacementTypes).def('right'),
|
||||
level: PropTypes.any.def(null),
|
||||
wrapClassName: PropTypes.string, // not use class like react, vue will add class to root dom
|
||||
handle: PropTypes.any,
|
||||
handle: PropTypes.VNodeChild,
|
||||
afterVisibleChange: PropTypes.func,
|
||||
keyboard: PropTypes.looseBool.def(true),
|
||||
onClose: PropTypes.func,
|
||||
|
@ -39,22 +42,21 @@ const Drawer = {
|
|||
},
|
||||
mixins: [BaseMixin],
|
||||
data() {
|
||||
this.destroyClose = false;
|
||||
this.preVisible = this.$props.visible;
|
||||
return {
|
||||
_push: false,
|
||||
sPush: false,
|
||||
};
|
||||
},
|
||||
setup() {
|
||||
setup(props) {
|
||||
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||
return {
|
||||
configProvider,
|
||||
destroyClose: false,
|
||||
preVisible: props.visible,
|
||||
parentDrawer: inject('parentDrawer', null)
|
||||
};
|
||||
},
|
||||
beforeCreate() {
|
||||
const parentDrawer = inject('parentDrawer', null);
|
||||
provide('parentDrawer', this);
|
||||
this.parentDrawer = parentDrawer;
|
||||
},
|
||||
mounted() {
|
||||
// fix: delete drawer in child and re-render, no push started.
|
||||
|
@ -83,7 +85,7 @@ const Drawer = {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
close(e) {
|
||||
close(e: Event) {
|
||||
this.$emit('update:visible', false);
|
||||
this.$emit('close', e);
|
||||
},
|
||||
|
@ -95,12 +97,12 @@ const Drawer = {
|
|||
// },
|
||||
push() {
|
||||
this.setState({
|
||||
_push: true,
|
||||
sPush: true,
|
||||
});
|
||||
},
|
||||
pull() {
|
||||
this.setState({
|
||||
_push: false,
|
||||
sPush: false,
|
||||
});
|
||||
},
|
||||
onDestroyTransitionEnd() {
|
||||
|
@ -118,7 +120,7 @@ const Drawer = {
|
|||
return this.destroyOnClose && !this.visible;
|
||||
},
|
||||
// get drawar push width or height
|
||||
getPushTransform(placement) {
|
||||
getPushTransform(placement?: placementType) {
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
return `translateX(${placement === 'left' ? 180 : -180}px)`;
|
||||
}
|
||||
|
@ -128,14 +130,14 @@ const Drawer = {
|
|||
},
|
||||
getRcDrawerStyle() {
|
||||
const { zIndex, placement, wrapStyle } = this.$props;
|
||||
const { _push: push } = this.$data;
|
||||
const { sPush: push } = this.$data;
|
||||
return {
|
||||
zIndex,
|
||||
transform: push ? this.getPushTransform(placement) : undefined,
|
||||
...wrapStyle,
|
||||
};
|
||||
},
|
||||
renderHeader(prefixCls) {
|
||||
renderHeader(prefixCls: string) {
|
||||
const { closable, headerStyle } = this.$props;
|
||||
const title = getComponent(this, 'title');
|
||||
if (!title && !closable) {
|
||||
|
@ -150,7 +152,7 @@ const Drawer = {
|
|||
</div>
|
||||
);
|
||||
},
|
||||
renderCloseIcon(prefixCls) {
|
||||
renderCloseIcon(prefixCls: string) {
|
||||
const { closable } = this;
|
||||
return (
|
||||
closable && (
|
||||
|
@ -161,14 +163,14 @@ const Drawer = {
|
|||
);
|
||||
},
|
||||
// render drawer body dom
|
||||
renderBody(prefixCls) {
|
||||
renderBody(prefixCls: string) {
|
||||
if (this.destroyClose && !this.visible) {
|
||||
return null;
|
||||
}
|
||||
this.destroyClose = false;
|
||||
const { bodyStyle, drawerStyle } = this.$props;
|
||||
|
||||
const containerStyle = {};
|
||||
const containerStyle: CSSProperties = {};
|
||||
|
||||
const isDestroyOnClose = this.getDestroyOnClose();
|
||||
if (isDestroyOnClose) {
|
||||
|
@ -192,7 +194,7 @@ const Drawer = {
|
|||
},
|
||||
},
|
||||
render() {
|
||||
const props = getOptionProps(this);
|
||||
const props: any = getOptionProps(this);
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
width,
|
||||
|
@ -204,7 +206,7 @@ const Drawer = {
|
|||
...rest
|
||||
} = props;
|
||||
const haveMask = mask ? '' : 'no-mask';
|
||||
const offsetStyle = {};
|
||||
const offsetStyle: CSSProperties = {};
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
offsetStyle.width = typeof width === 'number' ? `${width}px` : width;
|
||||
} else {
|
||||
|
@ -213,8 +215,8 @@ const Drawer = {
|
|||
const handler = getComponent(this, 'handle') || false;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
|
||||
const { class: className } = this.$attrs;
|
||||
const vcDrawerProps = {
|
||||
const { class: className } = this.$attrs as any;
|
||||
const vcDrawerProps: any = {
|
||||
...this.$attrs,
|
||||
...omit(rest, [
|
||||
'closable',
|
||||
|
@ -249,10 +251,10 @@ const Drawer = {
|
|||
};
|
||||
return <VcDrawer {...vcDrawerProps}>{this.renderBody(prefixCls)}</VcDrawer>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
/* istanbul ignore next */
|
||||
Drawer.install = function(app) {
|
||||
Drawer.install = function(app: App) {
|
||||
app.component(Drawer.name, Drawer);
|
||||
return app;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { provide, inject } from 'vue';
|
||||
import { provide, inject, defineComponent } from 'vue';
|
||||
import Button from '../button';
|
||||
import classNames from '../_util/classNames';
|
||||
import buttonTypes from '../button/buttonTypes';
|
||||
|
@ -9,6 +9,7 @@ import { hasProp, getComponent, getSlot } from '../_util/props-util';
|
|||
import getDropdownProps from './getDropdownProps';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
|
||||
import { tuple } from '../_util/type';
|
||||
|
||||
const ButtonTypesProps = buttonTypes();
|
||||
const DropdownProps = getDropdownProps();
|
||||
|
@ -16,8 +17,8 @@ const ButtonGroup = Button.Group;
|
|||
const DropdownButtonProps = {
|
||||
...ButtonGroupProps,
|
||||
...DropdownProps,
|
||||
type: PropTypes.oneOf(['primary', 'ghost', 'dashed', 'danger', 'default']).def('default'),
|
||||
size: PropTypes.oneOf(['small', 'large', 'default']).def('default'),
|
||||
type: PropTypes.oneOf(tuple('primary', 'ghost', 'dashed', 'danger', 'default')).def('default'),
|
||||
size: PropTypes.oneOf(tuple('small', 'large', 'default')).def('default'),
|
||||
htmlType: ButtonTypesProps.htmlType,
|
||||
href: PropTypes.string,
|
||||
disabled: PropTypes.looseBool,
|
||||
|
@ -30,20 +31,21 @@ const DropdownButtonProps = {
|
|||
'onUpdate:visible': PropTypes.func,
|
||||
};
|
||||
export { DropdownButtonProps };
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'ADropdownButton',
|
||||
inheritAttrs: false,
|
||||
props: DropdownButtonProps,
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
popupRef: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
provide('savePopupRef', this.savePopupRef);
|
||||
},
|
||||
methods: {
|
||||
savePopupRef(ref) {
|
||||
savePopupRef(ref: any) {
|
||||
this.popupRef = ref;
|
||||
},
|
||||
handleClick(e) {
|
||||
|
@ -72,12 +74,12 @@ export default {
|
|||
href,
|
||||
title,
|
||||
...restProps
|
||||
} = { ...this.$props, ...this.$attrs };
|
||||
} = { ...this.$props, ...this.$attrs } as any;
|
||||
const icon = getComponent(this, 'icon') || <EllipsisOutlined />;
|
||||
const { getPopupContainer: getContextPopupContainer } = this.configProvider;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('dropdown-button', customizePrefixCls);
|
||||
const dropdownProps = {
|
||||
const dropdownProps: any = {
|
||||
align,
|
||||
disabled,
|
||||
trigger: disabled ? [] : trigger,
|
||||
|
@ -112,4 +114,4 @@ export default {
|
|||
</ButtonGroup>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
|
@ -30,13 +30,14 @@ const Dropdown = defineComponent({
|
|||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
popupRef: null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
provide('savePopupRef', this.savePopupRef);
|
||||
},
|
||||
methods: {
|
||||
savePopupRef(ref) {
|
||||
savePopupRef(ref: any) {
|
||||
this.popupRef = ref;
|
||||
},
|
||||
getTransitionName() {
|
||||
|
@ -49,13 +50,13 @@ const Dropdown = defineComponent({
|
|||
}
|
||||
return 'slide-up';
|
||||
},
|
||||
renderOverlay(prefixCls) {
|
||||
renderOverlay(prefixCls: string) {
|
||||
const overlay = getComponent(this, 'overlay');
|
||||
const overlayNode = Array.isArray(overlay) ? overlay[0] : overlay;
|
||||
// menu cannot be selectable in dropdown defaultly
|
||||
// menu should be focusable in dropdown defaultly
|
||||
const overlayProps = overlayNode && getPropsData(overlayNode);
|
||||
const { selectable = false, focusable = true } = overlayProps || {};
|
||||
const { selectable = false, focusable = true } = (overlayProps || {}) as any;
|
||||
const expandIcon = (
|
||||
<span class={`${prefixCls}-menu-submenu-arrow`}>
|
||||
<RightOutlined class={`${prefixCls}-menu-submenu-arrow-icon`} />
|
||||
|
@ -72,7 +73,7 @@ const Dropdown = defineComponent({
|
|||
: overlay;
|
||||
return fixedModeOverlay;
|
||||
},
|
||||
handleVisibleChange(val) {
|
||||
handleVisibleChange(val: boolean) {
|
||||
this.$emit('update:visible', val);
|
||||
this.$emit('visibleChange', val);
|
||||
},
|
|
@ -1,6 +1,11 @@
|
|||
import { tuple } from '../_util/type';
|
||||
import { PropType } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
export default () => ({
|
||||
trigger: PropTypes.array.def(['hover']),
|
||||
trigger: {
|
||||
type: Array as PropType<('click' | 'hover' | 'contextMenu')[]>,
|
||||
default: () => ['hover'],
|
||||
},
|
||||
overlay: PropTypes.any,
|
||||
visible: PropTypes.looseBool,
|
||||
disabled: PropTypes.looseBool,
|
||||
|
@ -8,16 +13,11 @@ export default () => ({
|
|||
getPopupContainer: PropTypes.func,
|
||||
prefixCls: PropTypes.string,
|
||||
transitionName: PropTypes.string,
|
||||
placement: PropTypes.oneOf([
|
||||
'topLeft',
|
||||
'topCenter',
|
||||
'topRight',
|
||||
'bottomLeft',
|
||||
'bottomCenter',
|
||||
'bottomRight',
|
||||
]),
|
||||
placement: PropTypes.oneOf(
|
||||
tuple('topLeft', 'topCenter', 'topRight', 'bottomLeft', 'bottomCenter', 'bottomRight'),
|
||||
),
|
||||
overlayClassName: PropTypes.string,
|
||||
overlayStyle: PropTypes.object,
|
||||
overlayStyle: PropTypes.style,
|
||||
forceRender: PropTypes.looseBool,
|
||||
mouseEnterDelay: PropTypes.number,
|
||||
mouseLeaveDelay: PropTypes.number,
|
|
@ -1,16 +1,22 @@
|
|||
import { App } from 'vue';
|
||||
import Dropdown from './dropdown';
|
||||
import DropdownButton from './dropdown-button';
|
||||
|
||||
export { DropdownProps } from './dropdown';
|
||||
export { DropdownButtonProps } from './dropdown-button';
|
||||
|
||||
type Types = typeof Dropdown;
|
||||
interface DropdownTypes extends Types {
|
||||
Button: typeof DropdownButton;
|
||||
}
|
||||
|
||||
Dropdown.Button = DropdownButton;
|
||||
|
||||
/* istanbul ignore next */
|
||||
Dropdown.install = function(app) {
|
||||
Dropdown.install = function(app: App) {
|
||||
app.component(Dropdown.name, Dropdown);
|
||||
app.component(DropdownButton.name, DropdownButton);
|
||||
return app;
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
export default Dropdown as DropdownTypes;
|
|
@ -1,10 +1,9 @@
|
|||
import { inject, provide } from 'vue';
|
||||
import { defineComponent, inject, provide, PropType, computed } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import classNames from '../_util/classNames';
|
||||
import isRegExp from 'lodash-es/isRegExp';
|
||||
import warning from '../_util/warning';
|
||||
import FormItem from './FormItem';
|
||||
import { initDefaultProps, getSlot } from '../_util/props-util';
|
||||
import { getSlot } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { getNamePath, containsNamePath } from './utils/valueUtil';
|
||||
import { defaultValidateMessages } from './utils/messages';
|
||||
|
@ -12,57 +11,65 @@ import { allPromiseFinish } from './utils/asyncUtil';
|
|||
import { toArray } from './utils/typeUtil';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import { tuple, VueNode } from '../_util/type';
|
||||
import { ColProps } from '../grid/col';
|
||||
import { InternalNamePath, NamePath, ValidateOptions } from './interface';
|
||||
|
||||
export type ValidationRule = {
|
||||
/** validation error message */
|
||||
message?: VueNode;
|
||||
/** built-in validation type, available options: https://github.com/yiminghe/async-validator#type */
|
||||
type?: string;
|
||||
/** indicates whether field is required */
|
||||
required?: boolean;
|
||||
/** treat required fields that only contain whitespace as errors */
|
||||
whitespace?: boolean;
|
||||
/** validate the exact length of a field */
|
||||
len?: number;
|
||||
/** validate the min length of a field */
|
||||
min?: number;
|
||||
/** validate the max length of a field */
|
||||
max?: number;
|
||||
/** validate the value from a list of possible values */
|
||||
enum?: string | string[];
|
||||
/** validate from a regular expression */
|
||||
pattern?: RegExp;
|
||||
/** transform a value before validation */
|
||||
transform?: (value: any) => any;
|
||||
/** custom validate function (Note: callback must be called) */
|
||||
validator?: (rule: any, value: any, callback: any, source?: any, options?: any) => any;
|
||||
|
||||
trigger?: string
|
||||
};
|
||||
|
||||
export const FormProps = {
|
||||
layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']),
|
||||
labelCol: PropTypes.object,
|
||||
wrapperCol: PropTypes.object,
|
||||
layout: PropTypes.oneOf(tuple('horizontal', 'inline', 'vertical')),
|
||||
labelCol: {type: Object as PropType<ColProps>},
|
||||
wrapperCol: {type: Object as PropType<ColProps>},
|
||||
colon: PropTypes.looseBool,
|
||||
labelAlign: PropTypes.oneOf(['left', 'right']),
|
||||
labelAlign: PropTypes.oneOf(tuple('left', 'right')),
|
||||
prefixCls: PropTypes.string,
|
||||
hideRequiredMark: PropTypes.looseBool,
|
||||
model: PropTypes.object,
|
||||
rules: PropTypes.object,
|
||||
validateMessages: PropTypes.any,
|
||||
rules: {type: Object as PropType<ValidationRule[]>},
|
||||
validateMessages: PropTypes.object,
|
||||
validateOnRuleChange: PropTypes.looseBool,
|
||||
// 提交失败自动滚动到第一个错误字段
|
||||
scrollToFirstError: PropTypes.looseBool,
|
||||
onFinish: PropTypes.func,
|
||||
onFinishFailed: PropTypes.func,
|
||||
name: PropTypes.name,
|
||||
validateTrigger: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
name: PropTypes.string,
|
||||
validateTrigger: {type: [String, Array] as PropType<string | string[]>}
|
||||
};
|
||||
|
||||
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.looseBool,
|
||||
/** treat required fields that only contain whitespace as errors */
|
||||
whitespace: PropTypes.looseBool,
|
||||
/** 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) {
|
||||
|
||||
function isEqualName(name1: any, name2: any) {
|
||||
return isEqual(toArray(name1), toArray(name2));
|
||||
}
|
||||
|
||||
const Form = {
|
||||
const Form = defineComponent({
|
||||
name: 'AForm',
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(FormProps, {
|
||||
|
@ -72,14 +79,15 @@ const Form = {
|
|||
}),
|
||||
Item: FormItem,
|
||||
created() {
|
||||
this.fields = [];
|
||||
this.form = undefined;
|
||||
this.lastValidatePromise = null;
|
||||
provide('FormContext', this);
|
||||
},
|
||||
setup() {
|
||||
setup(props) {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
fields: [],
|
||||
form: undefined,
|
||||
lastValidatePromise: null,
|
||||
vertical: computed(()=>props.layout === 'vertical')
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -89,23 +97,18 @@ const Form = {
|
|||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
vertical() {
|
||||
return this.layout === 'vertical';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addField(field) {
|
||||
addField(field: any) {
|
||||
if (field) {
|
||||
this.fields.push(field);
|
||||
}
|
||||
},
|
||||
removeField(field) {
|
||||
removeField(field: any) {
|
||||
if (field.fieldName) {
|
||||
this.fields.splice(this.fields.indexOf(field), 1);
|
||||
}
|
||||
},
|
||||
handleSubmit(e) {
|
||||
handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.$emit('submit', e);
|
||||
|
@ -129,7 +132,7 @@ const Form = {
|
|||
);
|
||||
}
|
||||
},
|
||||
resetFields(name) {
|
||||
resetFields(name: NamePath) {
|
||||
if (!this.model) {
|
||||
warning(false, 'Form', 'model is required for resetFields to work.');
|
||||
return;
|
||||
|
@ -150,10 +153,10 @@ const Form = {
|
|||
this.scrollToField(errorInfo.errorFields[0].name);
|
||||
}
|
||||
},
|
||||
validate() {
|
||||
return this.validateField(...arguments);
|
||||
validate(...args: any[]) {
|
||||
return this.validateField(...args);
|
||||
},
|
||||
scrollToField(name, options = {}) {
|
||||
scrollToField(name: string | number , options = {}) {
|
||||
const fields = this.getFieldsByNameList([name]);
|
||||
if (fields.length) {
|
||||
const fieldId = fields[0].fieldId;
|
||||
|
@ -169,20 +172,20 @@ const Form = {
|
|||
}
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getFieldsValue(nameList = true) {
|
||||
const values = {};
|
||||
getFieldsValue(nameList: NamePath[] | true = true) {
|
||||
const values: any = {};
|
||||
this.fields.forEach(({ fieldName, fieldValue }) => {
|
||||
values[fieldName] = fieldValue;
|
||||
});
|
||||
if (nameList === true) {
|
||||
return values;
|
||||
} else {
|
||||
const res = {};
|
||||
toArray(nameList).forEach(namePath => (res[namePath] = values[namePath]));
|
||||
const res: any = {};
|
||||
toArray(nameList as NamePath[]).forEach((namePath) => (res[namePath as string] = values[namePath as string]));
|
||||
return res;
|
||||
}
|
||||
},
|
||||
validateFields(nameList, options) {
|
||||
validateFields(nameList?: NamePath[], options?: ValidateOptions) {
|
||||
warning(
|
||||
!(nameList instanceof Function),
|
||||
'Form',
|
||||
|
@ -193,10 +196,13 @@ const Form = {
|
|||
return Promise.reject('Form `model` is required for validateFields to work.');
|
||||
}
|
||||
const provideNameList = !!nameList;
|
||||
const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
|
||||
const namePathList: InternalNamePath[] = provideNameList ? toArray(nameList).map(getNamePath) : [];
|
||||
|
||||
// Collect result in promise list
|
||||
const promiseList = [];
|
||||
const promiseList: Promise<{
|
||||
name: InternalNamePath;
|
||||
errors: string[];
|
||||
}>[] = [];
|
||||
|
||||
this.fields.forEach(field => {
|
||||
// Add field if not provide `nameList`
|
||||
|
@ -225,7 +231,7 @@ const Form = {
|
|||
promiseList.push(
|
||||
promise
|
||||
.then(() => ({ name: fieldNamePath, errors: [] }))
|
||||
.catch(errors =>
|
||||
.catch((errors: any) =>
|
||||
Promise.reject({
|
||||
name: fieldNamePath,
|
||||
errors,
|
||||
|
@ -259,8 +265,8 @@ const Form = {
|
|||
|
||||
return returnPromise;
|
||||
},
|
||||
validateField() {
|
||||
return this.validateFields(...arguments);
|
||||
validateField(...args: any[]) {
|
||||
return this.validateFields(...args);
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -282,6 +288,8 @@ const Form = {
|
|||
</form>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default Form;
|
||||
export default Form as typeof Form & {
|
||||
readonly Item: typeof FormItem
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { inject, provide, Transition } from 'vue';
|
||||
import { inject, provide, Transition, PropType, defineComponent, computed } from 'vue';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import classNames from '../_util/classNames';
|
||||
|
@ -6,7 +6,6 @@ import getTransitionProps from '../_util/getTransitionProps';
|
|||
import Row from '../grid/Row';
|
||||
import Col from '../grid/Col';
|
||||
import hasProp, {
|
||||
initDefaultProps,
|
||||
findDOMNode,
|
||||
getComponent,
|
||||
getOptionProps,
|
||||
|
@ -26,6 +25,9 @@ import { getNamePath } from './utils/valueUtil';
|
|||
import { toArray } from './utils/typeUtil';
|
||||
import { warning } from '../vc-util/warning';
|
||||
import find from 'lodash-es/find';
|
||||
import { ColProps } from '../grid/col';
|
||||
import { tuple, VueNode } from '../_util/type';
|
||||
import { ValidateOptions } from './interface';
|
||||
|
||||
const iconMap = {
|
||||
success: CheckCircleFilled,
|
||||
|
@ -34,7 +36,7 @@ const iconMap = {
|
|||
validating: LoadingOutlined,
|
||||
};
|
||||
|
||||
function getPropByPath(obj, namePathList, strict) {
|
||||
function getPropByPath(obj: any, namePathList: any, strict?: boolean) {
|
||||
let tempObj = obj;
|
||||
|
||||
const keyArr = namePathList;
|
||||
|
@ -69,38 +71,106 @@ export const FormItemProps = {
|
|||
id: PropTypes.string,
|
||||
htmlFor: PropTypes.string,
|
||||
prefixCls: PropTypes.string,
|
||||
label: PropTypes.any,
|
||||
help: PropTypes.any,
|
||||
extra: PropTypes.any,
|
||||
labelCol: PropTypes.object,
|
||||
wrapperCol: PropTypes.object,
|
||||
hasFeedback: PropTypes.looseBool,
|
||||
label: PropTypes.VNodeChild,
|
||||
help: PropTypes.VNodeChild,
|
||||
extra: PropTypes.VNodeChild,
|
||||
labelCol: {type: Object as PropType<ColProps>},
|
||||
wrapperCol: {type: Object as PropType<ColProps>},
|
||||
hasFeedback: PropTypes.looseBool.def(false),
|
||||
colon: PropTypes.looseBool,
|
||||
labelAlign: PropTypes.oneOf(['left', 'right']),
|
||||
prop: PropTypes.oneOfType([Array, String, Number]),
|
||||
name: PropTypes.oneOfType([Array, String, Number]),
|
||||
labelAlign: PropTypes.oneOf(tuple('left', 'right')),
|
||||
prop: {type: [String, Number, Array] as PropType<string | number | string[] | number[]>},
|
||||
name: {type: [String, Number, Array] as PropType<string | number | string[] | number[]>},
|
||||
rules: PropTypes.oneOfType([Array, Object]),
|
||||
autoLink: PropTypes.looseBool,
|
||||
autoLink: PropTypes.looseBool.def(true),
|
||||
required: PropTypes.looseBool,
|
||||
validateFirst: PropTypes.looseBool,
|
||||
validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
|
||||
validateTrigger: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
validateStatus: PropTypes.oneOf(tuple('', 'success', 'warning', 'error', 'validating')),
|
||||
validateTrigger: {type: [String, Array] as PropType<string | string[]>},
|
||||
messageVariables: {type: Object as PropType<Record<string, string>>},
|
||||
};
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'AFormItem',
|
||||
mixins: [BaseMixin],
|
||||
inheritAttrs: false,
|
||||
__ANT_NEW_FORM_ITEM: true,
|
||||
props: initDefaultProps(FormItemProps, {
|
||||
hasFeedback: false,
|
||||
autoLink: true,
|
||||
}),
|
||||
setup() {
|
||||
props: FormItemProps,
|
||||
setup(props) {
|
||||
const FormContext = inject('FormContext', {}) as any
|
||||
const fieldName = computed(()=>props.name || props.prop);
|
||||
const namePath = computed(()=>{
|
||||
const val = fieldName.value
|
||||
return getNamePath(val)
|
||||
})
|
||||
const fieldId = computed(()=>{
|
||||
const {id} = props;
|
||||
if (id) {
|
||||
return id;
|
||||
} else if (!namePath.value.length) {
|
||||
return undefined;
|
||||
} else {
|
||||
const formName = FormContext.name;
|
||||
const mergedId = namePath.value.join('_');
|
||||
return formName ? `${formName}_${mergedId}` : mergedId;
|
||||
}
|
||||
})
|
||||
const fieldValue = computed(()=> {
|
||||
const model = FormContext.model;
|
||||
if (!model || !fieldName.value) {
|
||||
return;
|
||||
}
|
||||
return getPropByPath(model, namePath.value, true).v;
|
||||
})
|
||||
const mergedValidateTrigger = computed(() => {
|
||||
let validateTrigger =
|
||||
props.validateTrigger !== undefined
|
||||
? props.validateTrigger
|
||||
: FormContext.validateTrigger;
|
||||
validateTrigger = validateTrigger === undefined ? 'change' : validateTrigger;
|
||||
return toArray(validateTrigger);
|
||||
});
|
||||
const getRules = () => {
|
||||
let formRules = FormContext.rules;
|
||||
const selfRules = props.rules;
|
||||
const requiredRule =
|
||||
props.required !== undefined
|
||||
? { required: !!props.required, trigger: mergedValidateTrigger.value }
|
||||
: [];
|
||||
const prop = getPropByPath(formRules, namePath.value);
|
||||
formRules = formRules ? prop.o[prop.k] || prop.v : [];
|
||||
const rules = [].concat(selfRules || formRules || []);
|
||||
if (find(rules, rule => rule.required)) {
|
||||
return rules;
|
||||
} else {
|
||||
return rules.concat(requiredRule);
|
||||
}
|
||||
};
|
||||
const isRequired = computed(() => {
|
||||
let rules = getRules();
|
||||
let isRequired = false;
|
||||
if (rules && rules.length) {
|
||||
rules.every(rule => {
|
||||
if (rule.required) {
|
||||
isRequired = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return isRequired || props.required;
|
||||
});
|
||||
return {
|
||||
isFormItemChildren: inject('isFormItemChildren', false),
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
FormContext: inject('FormContext', {}),
|
||||
FormContext,
|
||||
fieldId,
|
||||
fieldName,
|
||||
namePath,
|
||||
isRequired,
|
||||
getRules,
|
||||
fieldValue,
|
||||
mergedValidateTrigger
|
||||
};
|
||||
},
|
||||
data() {
|
||||
|
@ -112,56 +182,9 @@ export default {
|
|||
validator: {},
|
||||
helpShow: false,
|
||||
errors: [],
|
||||
initialValue: undefined
|
||||
};
|
||||
},
|
||||
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;
|
||||
},
|
||||
mergedValidateTrigger() {
|
||||
let validateTrigger =
|
||||
this.validateTrigger !== undefined
|
||||
? this.validateTrigger
|
||||
: this.FormContext.validateTrigger;
|
||||
validateTrigger = validateTrigger === undefined ? 'change' : validateTrigger;
|
||||
return toArray(validateTrigger);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
validateStatus(val) {
|
||||
this.validateState = val;
|
||||
|
@ -188,7 +211,7 @@ export default {
|
|||
|
||||
return fieldName !== undefined ? [...prefixName, ...this.namePath] : [];
|
||||
},
|
||||
validateRules(options) {
|
||||
validateRules(options: ValidateOptions) {
|
||||
const { validateFirst = false, messageVariables } = this.$props;
|
||||
const { triggerName } = options || {};
|
||||
const namePath = this.getNamePath();
|
||||
|
@ -296,14 +319,14 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
onHelpAnimEnd(_key, helpShow) {
|
||||
onHelpAnimEnd(_key: string, helpShow: boolean) {
|
||||
this.helpShow = helpShow;
|
||||
if (!helpShow) {
|
||||
this.$forceUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
renderHelp(prefixCls) {
|
||||
renderHelp(prefixCls: string) {
|
||||
const help = this.getHelpMessage();
|
||||
const children = help ? (
|
||||
<div class={`${prefixCls}-explain`} key="help">
|
||||
|
@ -324,12 +347,12 @@ export default {
|
|||
);
|
||||
},
|
||||
|
||||
renderExtra(prefixCls) {
|
||||
renderExtra(prefixCls: string) {
|
||||
const extra = getComponent(this, 'extra');
|
||||
return extra ? <div class={`${prefixCls}-extra`}>{extra}</div> : null;
|
||||
},
|
||||
|
||||
renderValidateWrapper(prefixCls, c1, c2, c3) {
|
||||
renderValidateWrapper(prefixCls: string, c1: VueNode, c2: VueNode, c3: VueNode) {
|
||||
const validateStatus = this.validateState;
|
||||
|
||||
let classes = `${prefixCls}-item-control`;
|
||||
|
@ -362,8 +385,8 @@ export default {
|
|||
);
|
||||
},
|
||||
|
||||
renderWrapper(prefixCls, children) {
|
||||
const { wrapperCol: contextWrapperCol } = this.isFormItemChildren ? {} : this.FormContext;
|
||||
renderWrapper(prefixCls: string, children: VueNode) {
|
||||
const { wrapperCol: contextWrapperCol } = (this.isFormItemChildren ? {} : this.FormContext) as any;
|
||||
const { wrapperCol } = this;
|
||||
const mergedWrapperCol = wrapperCol || contextWrapperCol || {};
|
||||
const { style, id, ...restProps } = mergedWrapperCol;
|
||||
|
@ -378,7 +401,7 @@ export default {
|
|||
return <Col {...colProps}>{children}</Col>;
|
||||
},
|
||||
|
||||
renderLabel(prefixCls) {
|
||||
renderLabel(prefixCls: string) {
|
||||
const {
|
||||
vertical,
|
||||
labelAlign: contextLabelAlign,
|
||||
|
@ -437,7 +460,7 @@ export default {
|
|||
</Col>
|
||||
) : null;
|
||||
},
|
||||
renderChildren(prefixCls, child) {
|
||||
renderChildren(prefixCls: string, child: VueNode) {
|
||||
return [
|
||||
this.renderLabel(prefixCls),
|
||||
this.renderWrapper(
|
||||
|
@ -451,9 +474,9 @@ export default {
|
|||
),
|
||||
];
|
||||
},
|
||||
renderFormItem(child) {
|
||||
renderFormItem(child: any[]) {
|
||||
const { prefixCls: customizePrefixCls } = this.$props;
|
||||
const { class: className, ...restProps } = this.$attrs;
|
||||
const { class: className, ...restProps } = this.$attrs as any;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('form', customizePrefixCls);
|
||||
const children = this.renderChildren(prefixCls, child);
|
||||
|
@ -480,11 +503,11 @@ export default {
|
|||
const originalChange = originalEvents.onChange;
|
||||
firstChildren = cloneElement(firstChildren, {
|
||||
...(this.fieldId ? { id: this.fieldId } : undefined),
|
||||
onBlur: (...args) => {
|
||||
onBlur: (...args: any[]) => {
|
||||
originalBlur && originalBlur(...args);
|
||||
this.onFieldBlur();
|
||||
},
|
||||
onChange: (...args) => {
|
||||
onChange: (...args: any[]) => {
|
||||
if (Array.isArray(originalChange)) {
|
||||
for (let i = 0, l = originalChange.length; i < l; i++) {
|
||||
originalChange[i](...args);
|
||||
|
@ -498,4 +521,4 @@ export default {
|
|||
}
|
||||
return this.renderFormItem([firstChildren, children.slice(1)]);
|
||||
},
|
||||
};
|
||||
});
|
|
@ -1,7 +1,6 @@
|
|||
import Form from './Form';
|
||||
|
||||
export { FormProps, ValidationRule } from './Form';
|
||||
export { FormItemProps } from './FormItem';
|
||||
export { FormProps } from './Form';
|
||||
|
||||
/* istanbul ignore next */
|
||||
Form.install = function(app) {
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
import { VueNode } from '../_util/type';
|
||||
|
||||
export type InternalNamePath = (string | number)[];
|
||||
export type NamePath = string | number | InternalNamePath;
|
||||
|
||||
export type StoreValue = any;
|
||||
export interface Store {
|
||||
[name: string]: StoreValue;
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
touched: boolean;
|
||||
validating: boolean;
|
||||
errors: string[];
|
||||
name: InternalNamePath;
|
||||
}
|
||||
|
||||
export interface InternalFieldData extends Meta {
|
||||
value: StoreValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by `setFields` config
|
||||
*/
|
||||
export interface FieldData extends Partial<Omit<InternalFieldData, 'name'>> {
|
||||
name: NamePath;
|
||||
}
|
||||
|
||||
export type RuleType =
|
||||
| 'string'
|
||||
| 'number'
|
||||
| 'boolean'
|
||||
| 'method'
|
||||
| 'regexp'
|
||||
| 'integer'
|
||||
| 'float'
|
||||
| 'object'
|
||||
| 'enum'
|
||||
| 'date'
|
||||
| 'url'
|
||||
| 'hex'
|
||||
| 'email';
|
||||
|
||||
type Validator = (
|
||||
rule: RuleObject,
|
||||
value: StoreValue,
|
||||
callback: (error?: string) => void,
|
||||
) => Promise<void> | void;
|
||||
|
||||
export interface ValidatorRule {
|
||||
message?: string | VueNode;
|
||||
validator: Validator;
|
||||
}
|
||||
|
||||
interface BaseRule {
|
||||
enum?: StoreValue[];
|
||||
len?: number;
|
||||
max?: number;
|
||||
message?: string | VueNode;
|
||||
min?: number;
|
||||
pattern?: RegExp;
|
||||
required?: boolean;
|
||||
transform?: (value: StoreValue) => StoreValue;
|
||||
type?: RuleType;
|
||||
whitespace?: boolean;
|
||||
|
||||
/** Customize rule level `validateTrigger`. Must be subset of Field `validateTrigger` */
|
||||
validateTrigger?: string | string[];
|
||||
}
|
||||
|
||||
type AggregationRule = BaseRule & Partial<ValidatorRule>;
|
||||
|
||||
interface ArrayRule extends Omit<AggregationRule, 'type'> {
|
||||
type: 'array';
|
||||
defaultField?: RuleObject;
|
||||
}
|
||||
|
||||
export type RuleObject = AggregationRule | ArrayRule;
|
||||
|
||||
export type Rule = RuleObject;
|
||||
|
||||
export interface ValidateErrorEntity<Values = any> {
|
||||
values: Values;
|
||||
errorFields: { name: InternalNamePath; errors: string[] }[];
|
||||
outOfDate: boolean;
|
||||
}
|
||||
|
||||
export interface FieldError {
|
||||
name: InternalNamePath;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export interface ValidateOptions {
|
||||
triggerName?: string;
|
||||
validateMessages?: ValidateMessages;
|
||||
}
|
||||
|
||||
export type InternalValidateFields = (
|
||||
nameList?: NamePath[],
|
||||
options?: ValidateOptions,
|
||||
) => Promise<Store>;
|
||||
export type ValidateFields = (nameList?: NamePath[]) => Promise<Store>;
|
||||
|
||||
// >>>>>> Info
|
||||
interface ValueUpdateInfo {
|
||||
type: 'valueUpdate';
|
||||
source: 'internal' | 'external';
|
||||
}
|
||||
|
||||
interface ValidateFinishInfo {
|
||||
type: 'validateFinish';
|
||||
}
|
||||
|
||||
interface ResetInfo {
|
||||
type: 'reset';
|
||||
}
|
||||
|
||||
interface SetFieldInfo {
|
||||
type: 'setField';
|
||||
data: FieldData;
|
||||
}
|
||||
|
||||
interface DependenciesUpdateInfo {
|
||||
type: 'dependenciesUpdate';
|
||||
/**
|
||||
* Contains all the related `InternalNamePath[]`.
|
||||
* a <- b <- c : change `a`
|
||||
* relatedFields=[a, b, c]
|
||||
*/
|
||||
relatedFields: InternalNamePath[];
|
||||
}
|
||||
|
||||
export type NotifyInfo =
|
||||
| ValueUpdateInfo
|
||||
| ValidateFinishInfo
|
||||
| ResetInfo
|
||||
| SetFieldInfo
|
||||
| DependenciesUpdateInfo;
|
||||
|
||||
export type ValuedNotifyInfo = NotifyInfo & {
|
||||
store: Store;
|
||||
};
|
||||
|
||||
export interface Callbacks<Values = any> {
|
||||
onValuesChange?: (changedValues: any, values: Values) => void;
|
||||
onFieldsChange?: (changedFields: FieldData[], allFields: FieldData[]) => void;
|
||||
onFinish?: (values: Values) => void;
|
||||
onFinishFailed?: (errorInfo: ValidateErrorEntity<Values>) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type EventArgs = any[];
|
||||
|
||||
type ValidateMessage = string | (() => string);
|
||||
export interface ValidateMessages {
|
||||
default?: ValidateMessage;
|
||||
required?: ValidateMessage;
|
||||
enum?: ValidateMessage;
|
||||
whitespace?: ValidateMessage;
|
||||
date?: {
|
||||
format?: ValidateMessage;
|
||||
parse?: ValidateMessage;
|
||||
invalid?: ValidateMessage;
|
||||
};
|
||||
types?: {
|
||||
string?: ValidateMessage;
|
||||
method?: ValidateMessage;
|
||||
array?: ValidateMessage;
|
||||
object?: ValidateMessage;
|
||||
number?: ValidateMessage;
|
||||
date?: ValidateMessage;
|
||||
boolean?: ValidateMessage;
|
||||
integer?: ValidateMessage;
|
||||
float?: ValidateMessage;
|
||||
regexp?: ValidateMessage;
|
||||
email?: ValidateMessage;
|
||||
url?: ValidateMessage;
|
||||
hex?: ValidateMessage;
|
||||
};
|
||||
string?: {
|
||||
len?: ValidateMessage;
|
||||
min?: ValidateMessage;
|
||||
max?: ValidateMessage;
|
||||
range?: ValidateMessage;
|
||||
};
|
||||
number?: {
|
||||
len?: ValidateMessage;
|
||||
min?: ValidateMessage;
|
||||
max?: ValidateMessage;
|
||||
range?: ValidateMessage;
|
||||
};
|
||||
array?: {
|
||||
len?: ValidateMessage;
|
||||
min?: ValidateMessage;
|
||||
max?: ValidateMessage;
|
||||
range?: ValidateMessage;
|
||||
};
|
||||
pattern?: {
|
||||
mismatch?: ValidateMessage;
|
||||
};
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export function allPromiseFinish(promiseList) {
|
||||
import { FieldError } from '../interface';
|
||||
export function allPromiseFinish(promiseList: Promise<FieldError>[]): Promise<FieldError[]> {
|
||||
let hasError = false;
|
||||
let count = promiseList.length;
|
||||
const results = [];
|
|
@ -1,4 +1,4 @@
|
|||
export function toArray(value) {
|
||||
export function toArray<T>(value?: T | T[] | null): T[] {
|
||||
if (value === undefined || value === null) {
|
||||
return [];
|
||||
}
|
|
@ -5,15 +5,16 @@ import { warning } from '../../vc-util/warning';
|
|||
import { setValues } from './valueUtil';
|
||||
import { defaultValidateMessages } from './messages';
|
||||
import { isValidElement } from '../../_util/props-util';
|
||||
import { InternalNamePath, RuleObject, ValidateMessages, ValidateOptions } from '../interface';
|
||||
|
||||
// Remove incorrect original ts define
|
||||
const AsyncValidator = RawAsyncValidator;
|
||||
const AsyncValidator: any = RawAsyncValidator;
|
||||
|
||||
/**
|
||||
* Replace with template.
|
||||
* `I'm ${name}` + { name: 'bamboo' } = I'm bamboo
|
||||
*/
|
||||
function replaceMessage(template, kv) {
|
||||
function replaceMessage(template: string, kv: Record<string, string>): string {
|
||||
return template.replace(/\$\{\w+\}/g, str => {
|
||||
const key = str.slice(2, -1);
|
||||
return kv[key];
|
||||
|
@ -24,18 +25,23 @@ function replaceMessage(template, kv) {
|
|||
* We use `async-validator` to validate rules. So have to hot replace the message with validator.
|
||||
* { required: '${name} is required' } => { required: () => 'field is required' }
|
||||
*/
|
||||
function convertMessages(messages, name, rule, messageVariables) {
|
||||
function convertMessages(
|
||||
messages: ValidateMessages,
|
||||
name: string,
|
||||
rule: RuleObject,
|
||||
messageVariables?: Record<string, string>,
|
||||
): ValidateMessages {
|
||||
const kv = {
|
||||
...rule,
|
||||
...(rule as Record<string, string | number>),
|
||||
name,
|
||||
enum: (rule.enum || []).join(', '),
|
||||
};
|
||||
|
||||
const replaceFunc = (template, additionalKV) => () =>
|
||||
const replaceFunc = (template: string, additionalKV?: Record<string, string>) => () =>
|
||||
replaceMessage(template, { ...kv, ...additionalKV });
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
function fillTemplate(source, target = {}) {
|
||||
function fillTemplate(source: ValidateMessages, target: ValidateMessages = {}) {
|
||||
Object.keys(source).forEach(ruleName => {
|
||||
const value = source[ruleName];
|
||||
if (typeof value === 'string') {
|
||||
|
@ -52,13 +58,19 @@ function convertMessages(messages, name, rule, messageVariables) {
|
|||
}
|
||||
/* eslint-enable */
|
||||
|
||||
return fillTemplate(setValues({}, defaultValidateMessages, messages));
|
||||
return fillTemplate(setValues({}, defaultValidateMessages, messages)) as ValidateMessages;
|
||||
}
|
||||
|
||||
async function validateRule(name, value, rule, options, messageVariables) {
|
||||
async function validateRule(
|
||||
name: string,
|
||||
value: any,
|
||||
rule: RuleObject,
|
||||
options: ValidateOptions,
|
||||
messageVariables?: Record<string, string>,
|
||||
): Promise<string[]> {
|
||||
const cloneRule = { ...rule };
|
||||
// We should special handle array validate
|
||||
let subRuleField = null;
|
||||
let subRuleField: RuleObject = null;
|
||||
if (cloneRule && cloneRule.type === 'array' && cloneRule.defaultField) {
|
||||
subRuleField = cloneRule.defaultField;
|
||||
delete cloneRule.defaultField;
|
||||
|
@ -77,19 +89,19 @@ async function validateRule(name, value, rule, options, messageVariables) {
|
|||
await Promise.resolve(validator.validate({ [name]: value }, { ...options }));
|
||||
} catch (errObj) {
|
||||
if (errObj.errors) {
|
||||
result = errObj.errors.map(({ message }, index) =>
|
||||
result = errObj.errors.map(({ message }, index: number) =>
|
||||
// Wrap VueNode with `key`
|
||||
isValidElement(message) ? cloneVNode(message, { key: `error_${index}` }) : message,
|
||||
);
|
||||
} else {
|
||||
console.error(errObj);
|
||||
result = [messages.default()];
|
||||
result = [(messages.default as () => string)()];
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.length && subRuleField) {
|
||||
const subResults = await Promise.all(
|
||||
value.map((subValue, i) =>
|
||||
const subResults: string[][] = await Promise.all(
|
||||
(value as any[]).map((subValue: any, i: number) =>
|
||||
validateRule(`${name}.${i}`, subValue, subRuleField, options, messageVariables),
|
||||
),
|
||||
);
|
||||
|
@ -104,11 +116,18 @@ async function validateRule(name, value, rule, options, messageVariables) {
|
|||
* We use `async-validator` to validate the value.
|
||||
* But only check one value in a time to avoid namePath validate issue.
|
||||
*/
|
||||
export function validateRules(namePath, value, rules, options, validateFirst, messageVariables) {
|
||||
export function validateRules(
|
||||
namePath: InternalNamePath,
|
||||
value: any,
|
||||
rules: RuleObject[],
|
||||
options: ValidateOptions,
|
||||
validateFirst: boolean | 'parallel',
|
||||
messageVariables?: Record<string, string>,
|
||||
) {
|
||||
const name = namePath.join('.');
|
||||
|
||||
// Fill rule with context
|
||||
const filledRules = rules.map(currentRule => {
|
||||
const filledRules: RuleObject[] = rules.map(currentRule => {
|
||||
const originValidatorFunc = currentRule.validator;
|
||||
|
||||
if (!originValidatorFunc) {
|
||||
|
@ -116,11 +135,11 @@ export function validateRules(namePath, value, rules, options, validateFirst, me
|
|||
}
|
||||
return {
|
||||
...currentRule,
|
||||
validator(rule, val, callback) {
|
||||
validator(rule: RuleObject, val: any, callback: (error?: string) => void) {
|
||||
let hasPromise = false;
|
||||
|
||||
// Wrap callback only accept when promise not provided
|
||||
const wrappedCallback = (...args) => {
|
||||
const wrappedCallback = (...args: string[]) => {
|
||||
// Wait a tick to make sure return type is a promise
|
||||
Promise.resolve().then(() => {
|
||||
warning(
|
||||
|
@ -146,7 +165,7 @@ export function validateRules(namePath, value, rules, options, validateFirst, me
|
|||
warning(hasPromise, '`callback` is deprecated. Please return a promise instead.');
|
||||
|
||||
if (hasPromise) {
|
||||
promise
|
||||
(promise as Promise<void>)
|
||||
.then(() => {
|
||||
callback();
|
||||
})
|
||||
|
@ -158,7 +177,7 @@ export function validateRules(namePath, value, rules, options, validateFirst, me
|
|||
};
|
||||
});
|
||||
|
||||
let summaryPromise;
|
||||
let summaryPromise: Promise<string[]>;
|
||||
|
||||
if (validateFirst === true) {
|
||||
// >>>>> Validate by serialization
|
||||
|
@ -184,12 +203,12 @@ export function validateRules(namePath, value, rules, options, validateFirst, me
|
|||
summaryPromise = (validateFirst
|
||||
? finishOnFirstFailed(rulePromises)
|
||||
: finishOnAllFailed(rulePromises)
|
||||
).then(errors => {
|
||||
).then((errors: string[]): string[] | Promise<string[]> => {
|
||||
if (!errors.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Promise.reject(errors);
|
||||
return Promise.reject<string[]>(errors);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -199,7 +218,7 @@ export function validateRules(namePath, value, rules, options, validateFirst, me
|
|||
return summaryPromise;
|
||||
}
|
||||
|
||||
async function finishOnAllFailed(rulePromises) {
|
||||
async function finishOnAllFailed(rulePromises: Promise<string[]>[]): Promise<string[]> {
|
||||
return Promise.all(rulePromises).then(errorsList => {
|
||||
const errors = [].concat(...errorsList);
|
||||
|
||||
|
@ -207,7 +226,7 @@ async function finishOnAllFailed(rulePromises) {
|
|||
});
|
||||
}
|
||||
|
||||
async function finishOnFirstFailed(rulePromises) {
|
||||
async function finishOnFirstFailed(rulePromises: Promise<string[]>[]): Promise<string[]> {
|
||||
let count = 0;
|
||||
|
||||
return new Promise(resolve => {
|
|
@ -1,4 +1,5 @@
|
|||
import { toArray } from './typeUtil';
|
||||
import { InternalNamePath, NamePath } from '../interface';
|
||||
|
||||
/**
|
||||
* Convert name to internal supported format.
|
||||
|
@ -7,35 +8,15 @@ import { toArray } from './typeUtil';
|
|||
* 123 => [123]
|
||||
* ['a', 123] => ['a', 123]
|
||||
*/
|
||||
export function getNamePath(path) {
|
||||
export function getNamePath(path: NamePath | null): InternalNamePath {
|
||||
return toArray(path);
|
||||
}
|
||||
|
||||
// export function getValue(store: Store, namePath: InternalNamePath) {
|
||||
// const value = get(store, namePath);
|
||||
// return value;
|
||||
// }
|
||||
|
||||
// export function setValue(store: Store, namePath: InternalNamePath, value: StoreValue): Store {
|
||||
// const newStore = set(store, namePath, value);
|
||||
// return newStore;
|
||||
// }
|
||||
|
||||
// export function cloneByNamePathList(store: Store, namePathList: InternalNamePath[]): Store {
|
||||
// let newStore = {};
|
||||
// namePathList.forEach(namePath => {
|
||||
// const value = getValue(store, namePath);
|
||||
// newStore = setValue(newStore, namePath, value);
|
||||
// });
|
||||
|
||||
// return newStore;
|
||||
// }
|
||||
|
||||
export function containsNamePath(namePathList, namePath) {
|
||||
export function containsNamePath(namePathList: InternalNamePath[], namePath: InternalNamePath) {
|
||||
return namePathList && namePathList.some(path => matchNamePath(path, namePath));
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
function isObject(obj: any) {
|
||||
return typeof obj === 'object' && obj !== null && Object.getPrototypeOf(obj) === Object.prototype;
|
||||
}
|
||||
|
||||
|
@ -43,8 +24,8 @@ function isObject(obj) {
|
|||
* Copy values into store and return a new values object
|
||||
* ({ a: 1, b: { c: 2 } }, { a: 4, b: { d: 5 } }) => { a: 4, b: { c: 2, d: 5 } }
|
||||
*/
|
||||
function internalSetValues(store, values) {
|
||||
const newStore = Array.isArray(store) ? [...store] : { ...store };
|
||||
function internalSetValues<T>(store: T, values: T): T {
|
||||
const newStore: T = (Array.isArray(store) ? [...store] : { ...store }) as T;
|
||||
|
||||
if (!values) {
|
||||
return newStore;
|
||||
|
@ -62,11 +43,17 @@ function internalSetValues(store, values) {
|
|||
return newStore;
|
||||
}
|
||||
|
||||
export function setValues(store, ...restValues) {
|
||||
return restValues.reduce((current, newStore) => internalSetValues(current, newStore), store);
|
||||
export function setValues<T>(store: T, ...restValues: T[]): T {
|
||||
return restValues.reduce(
|
||||
(current: T, newStore: T) => internalSetValues(current, newStore),
|
||||
store,
|
||||
);
|
||||
}
|
||||
|
||||
export function matchNamePath(namePath, changedNamePath) {
|
||||
export function matchNamePath(
|
||||
namePath: InternalNamePath,
|
||||
changedNamePath: InternalNamePath | null,
|
||||
) {
|
||||
if (!namePath || !changedNamePath || namePath.length !== changedNamePath.length) {
|
||||
return false;
|
||||
}
|
Loading…
Reference in New Issue