refactor: form
							parent
							
								
									0a2940e4ad
								
							
						
					
					
						commit
						3d2a04d23d
					
				|  | @ -13,11 +13,13 @@ const useProvideSize = <T = SizeType>(props: Record<any, any>): ComputedRef<T> = | |||
|   return size; | ||||
| }; | ||||
| 
 | ||||
| const useInjectSize = <T = SizeType>(): ComputedRef<T> => { | ||||
|   const size: ComputedRef<T> = inject( | ||||
|     sizeProvider, | ||||
|     computed(() => ('default' as unknown) as T), | ||||
|   ); | ||||
| const useInjectSize = <T = SizeType>(props?: Record<any, any>): ComputedRef<T> => { | ||||
|   const size: ComputedRef<T> = props | ||||
|     ? computed(() => props.size) | ||||
|     : inject( | ||||
|         sizeProvider, | ||||
|         computed(() => ('default' as unknown) as T), | ||||
|       ); | ||||
|   return size; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| import { useInjectFormItemPrefix } from './context'; | ||||
| import { VueNode } from '../_util/type'; | ||||
| import { computed, defineComponent, ref, watch } from '@vue/runtime-core'; | ||||
| import { defineComponent, ref, watch } from '@vue/runtime-core'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import Transition, { getTransitionProps } from '../_util/transition'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| 
 | ||||
| export interface ErrorListProps { | ||||
|   errors?: VueNode[]; | ||||
|  | @ -12,29 +13,53 @@ export interface ErrorListProps { | |||
|   onDomErrorVisibleChange?: (visible: boolean) => void; | ||||
| } | ||||
| 
 | ||||
| export default defineComponent<ErrorListProps>({ | ||||
| export default defineComponent({ | ||||
|   name: 'ErrorList', | ||||
|   props: ['errors', 'help', 'onDomErrorVisibleChange'], | ||||
|   setup(props) { | ||||
|     const { prefixCls: rootPrefixCls } = useConfigInject('', props); | ||||
|     const { prefixCls, status } = useInjectFormItemPrefix(); | ||||
|     const visible = computed(() => props.errors && props.errors.length); | ||||
|     const visible = ref(!!(props.errors && props.errors.length)); | ||||
|     const innerStatus = ref(status.value); | ||||
|     let timeout = ref(); | ||||
|     watch([() => props.errors, () => props.help], () => { | ||||
|       window.clearTimeout(timeout.value); | ||||
|       if (props.help) { | ||||
|         visible.value = !!(props.errors && props.errors.length); | ||||
|       } else { | ||||
|         timeout.value = window.setTimeout(() => { | ||||
|           visible.value = !!(props.errors && props.errors.length); | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|     // Memo status in same visible | ||||
|     watch([() => visible, () => status], () => { | ||||
|     watch([visible, status], () => { | ||||
|       if (visible.value && status.value) { | ||||
|         innerStatus.value = status.value; | ||||
|       } | ||||
|     }); | ||||
|     watch( | ||||
|       visible, | ||||
|       () => { | ||||
|         if (visible.value) { | ||||
|           props.onDomErrorVisibleChange?.(true); | ||||
|         } | ||||
|       }, | ||||
|       { immediate: true, flush: 'post' }, | ||||
|     ); | ||||
|     return () => { | ||||
|       const baseClassName = `${prefixCls.value}-item-explain`; | ||||
|       const transitionProps = getTransitionProps('show-help', { | ||||
|         onAfterLeave: () => props.onDomErrorVisibleChange?.(false), | ||||
|       const transitionProps = getTransitionProps(`${rootPrefixCls.value}-show-help`, { | ||||
|         onAfterLeave: () => { | ||||
|           props.onDomErrorVisibleChange?.(false); | ||||
|         }, | ||||
|       }); | ||||
|       return ( | ||||
|         <Transition {...transitionProps}> | ||||
|           {visible ? ( | ||||
|           {visible.value ? ( | ||||
|             <div | ||||
|               class={classNames(baseClassName, { | ||||
|                 [`${baseClassName}-${innerStatus}`]: innerStatus, | ||||
|                 [`${baseClassName}-${innerStatus.value}`]: innerStatus.value, | ||||
|               })} | ||||
|               key="help" | ||||
|             > | ||||
|  |  | |||
|  | @ -1,18 +1,16 @@ | |||
| import { | ||||
|   defineComponent, | ||||
|   inject, | ||||
|   provide, | ||||
|   PropType, | ||||
|   computed, | ||||
|   ExtractPropTypes, | ||||
|   HTMLAttributes, | ||||
|   watch, | ||||
|   ref, | ||||
| } from 'vue'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import warning from '../_util/warning'; | ||||
| import FormItem from './FormItem'; | ||||
| import { getSlot } from '../_util/props-util'; | ||||
| import { defaultConfigProvider, SizeType } from '../config-provider'; | ||||
| import FormItem, { FieldExpose } from './FormItem'; | ||||
| import { getNamePath, containsNamePath } from './utils/valueUtil'; | ||||
| import { defaultValidateMessages } from './utils/messages'; | ||||
| import { allPromiseFinish } from './utils/asyncUtil'; | ||||
|  | @ -23,6 +21,10 @@ import initDefaultProps from '../_util/props-util/initDefaultProps'; | |||
| import { tuple, VueNode } from '../_util/type'; | ||||
| import { ColProps } from '../grid/Col'; | ||||
| import { InternalNamePath, NamePath, ValidateErrorEntity, ValidateOptions } from './interface'; | ||||
| import { useInjectSize } from '../_util/hooks/useSize'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| import { useProvideForm } from './context'; | ||||
| import { SizeType } from '../config-provider'; | ||||
| 
 | ||||
| export type RequiredMark = boolean | 'optional'; | ||||
| export type FormLayout = 'horizontal' | 'inline' | 'vertical'; | ||||
|  | @ -61,7 +63,7 @@ export const formProps = { | |||
|   colon: PropTypes.looseBool, | ||||
|   labelAlign: PropTypes.oneOf(tuple('left', 'right')), | ||||
|   prefixCls: PropTypes.string, | ||||
|   requiredMark: { type: [String, Boolean] as PropType<RequiredMark> }, | ||||
|   requiredMark: { type: [String, Boolean] as PropType<RequiredMark | ''>, default: undefined }, | ||||
|   /** @deprecated Will warning in future branch. Pls use `requiredMark` instead. */ | ||||
|   hideRequiredMark: PropTypes.looseBool, | ||||
|   model: PropTypes.object, | ||||
|  | @ -93,92 +95,88 @@ const Form = defineComponent({ | |||
|     colon: true, | ||||
|   }), | ||||
|   Item: FormItem, | ||||
|   setup(props) { | ||||
|     return { | ||||
|       configProvider: inject('configProvider', defaultConfigProvider), | ||||
|       fields: [], | ||||
|       form: undefined, | ||||
|       lastValidatePromise: null, | ||||
|       vertical: computed(() => props.layout === 'vertical'), | ||||
|   emits: ['finishFailed', 'submit', 'finish'], | ||||
|   setup(props, { emit, slots, expose }) { | ||||
|     const size = useInjectSize(props); | ||||
|     const { prefixCls, direction, form: contextForm } = useConfigInject('form', props); | ||||
|     const requiredMark = computed(() => props.requiredMark === '' || props.requiredMark); | ||||
|     const mergedRequiredMark = computed(() => { | ||||
|       if (requiredMark.value !== undefined) { | ||||
|         return requiredMark.value; | ||||
|       } | ||||
| 
 | ||||
|       if (contextForm && contextForm.value?.requiredMark !== undefined) { | ||||
|         return contextForm.value.requiredMark; | ||||
|       } | ||||
| 
 | ||||
|       if (props.hideRequiredMark) { | ||||
|         return false; | ||||
|       } | ||||
|       return true; | ||||
|     }); | ||||
| 
 | ||||
|     const formClassName = computed(() => | ||||
|       classNames(prefixCls.value, { | ||||
|         [`${prefixCls.value}-${props.layout}`]: true, | ||||
|         [`${prefixCls.value}-hide-required-mark`]: mergedRequiredMark.value === false, | ||||
|         [`${prefixCls.value}-rtl`]: direction.value === 'rtl', | ||||
|         [`${prefixCls.value}-${size.value}`]: size.value, | ||||
|       }), | ||||
|     ); | ||||
|     const lastValidatePromise = ref(); | ||||
|     const fields: Record<string, FieldExpose> = {}; | ||||
| 
 | ||||
|     const addField = (eventKey: string, field: FieldExpose) => { | ||||
|       fields[eventKey] = field; | ||||
|     }; | ||||
|   }, | ||||
|   watch: { | ||||
|     rules() { | ||||
|       if (this.validateOnRuleChange) { | ||||
|         this.validateFields(); | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     provide('FormContext', this); | ||||
|   }, | ||||
|   methods: { | ||||
|     addField(field: any) { | ||||
|       if (field) { | ||||
|         this.fields.push(field); | ||||
|       } | ||||
|     }, | ||||
|     removeField(field: any) { | ||||
|       if (field.fieldName) { | ||||
|         this.fields.splice(this.fields.indexOf(field), 1); | ||||
|       } | ||||
|     }, | ||||
|     handleSubmit(e: Event) { | ||||
|       e.preventDefault(); | ||||
|       e.stopPropagation(); | ||||
|       this.$emit('submit', e); | ||||
|       const res = this.validateFields(); | ||||
|       res | ||||
|         .then(values => { | ||||
|           this.$emit('finish', values); | ||||
|         }) | ||||
|         .catch(errors => { | ||||
|           this.handleFinishFailed(errors); | ||||
|         }); | ||||
|     }, | ||||
|     getFieldsByNameList(nameList: NamePath) { | ||||
|     const removeField = (eventKey: string) => { | ||||
|       delete fields[eventKey]; | ||||
|     }; | ||||
| 
 | ||||
|     const getFieldsByNameList = (nameList: NamePath) => { | ||||
|       const provideNameList = !!nameList; | ||||
|       const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : []; | ||||
|       if (!provideNameList) { | ||||
|         return this.fields; | ||||
|         return Object.values(fields); | ||||
|       } else { | ||||
|         return this.fields.filter( | ||||
|           field => namePathList.findIndex(namePath => isEqualName(namePath, field.fieldName)) > -1, | ||||
|         return Object.values(fields).filter( | ||||
|           field => | ||||
|             namePathList.findIndex(namePath => isEqualName(namePath, field.fieldName.value)) > -1, | ||||
|         ); | ||||
|       } | ||||
|     }, | ||||
|     resetFields(name: NamePath) { | ||||
|       if (!this.model) { | ||||
|     }; | ||||
|     const resetFields = (name: NamePath) => { | ||||
|       if (!props.model) { | ||||
|         warning(false, 'Form', 'model is required for resetFields to work.'); | ||||
|         return; | ||||
|       } | ||||
|       this.getFieldsByNameList(name).forEach(field => { | ||||
|       getFieldsByNameList(name).forEach(field => { | ||||
|         field.resetField(); | ||||
|       }); | ||||
|     }, | ||||
|     clearValidate(name: NamePath) { | ||||
|       this.getFieldsByNameList(name).forEach(field => { | ||||
|     }; | ||||
|     const clearValidate = (name: NamePath) => { | ||||
|       getFieldsByNameList(name).forEach(field => { | ||||
|         field.clearValidate(); | ||||
|       }); | ||||
|     }, | ||||
|     handleFinishFailed(errorInfo: ValidateErrorEntity) { | ||||
|       const { scrollToFirstError } = this; | ||||
|       this.$emit('finishFailed', errorInfo); | ||||
|     }; | ||||
|     const handleFinishFailed = (errorInfo: ValidateErrorEntity) => { | ||||
|       const { scrollToFirstError } = props; | ||||
|       emit('finishFailed', errorInfo); | ||||
|       if (scrollToFirstError && errorInfo.errorFields.length) { | ||||
|         let scrollToFieldOptions: Options = {}; | ||||
|         if (typeof scrollToFirstError === 'object') { | ||||
|           scrollToFieldOptions = scrollToFirstError; | ||||
|         } | ||||
|         this.scrollToField(errorInfo.errorFields[0].name, scrollToFieldOptions); | ||||
|         scrollToField(errorInfo.errorFields[0].name, scrollToFieldOptions); | ||||
|       } | ||||
|     }, | ||||
|     validate(...args: any[]) { | ||||
|       return this.validateField(...args); | ||||
|     }, | ||||
|     scrollToField(name: NamePath, options = {}) { | ||||
|       const fields = this.getFieldsByNameList(name); | ||||
|     }; | ||||
|     const validate = (...args: any[]) => { | ||||
|       return validateField(...args); | ||||
|     }; | ||||
|     const scrollToField = (name: NamePath, options = {}) => { | ||||
|       const fields = getFieldsByNameList(name); | ||||
|       if (fields.length) { | ||||
|         const fieldId = fields[0].fieldId; | ||||
|         const fieldId = fields[0].fieldId.value; | ||||
|         const node = fieldId ? document.getElementById(fieldId) : null; | ||||
| 
 | ||||
|         if (node) { | ||||
|  | @ -189,12 +187,12 @@ const Form = defineComponent({ | |||
|           }); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     }; | ||||
|     // eslint-disable-next-line no-unused-vars | ||||
|     getFieldsValue(nameList: NamePath[] | true = true) { | ||||
|     const getFieldsValue = (nameList: NamePath[] | true = true) => { | ||||
|       const values: any = {}; | ||||
|       this.fields.forEach(({ fieldName, fieldValue }) => { | ||||
|         values[fieldName] = fieldValue; | ||||
|       Object.values(fields).forEach(({ fieldName, fieldValue }) => { | ||||
|         values[fieldName.value] = fieldValue.value; | ||||
|       }); | ||||
|       if (nameList === true) { | ||||
|         return values; | ||||
|  | @ -205,14 +203,14 @@ const Form = defineComponent({ | |||
|         ); | ||||
|         return res; | ||||
|       } | ||||
|     }, | ||||
|     validateFields(nameList?: NamePath[], options?: ValidateOptions) { | ||||
|     }; | ||||
|     const validateFields = (nameList?: NamePath[], options?: ValidateOptions) => { | ||||
|       warning( | ||||
|         !(nameList instanceof Function), | ||||
|         'Form', | ||||
|         'validateFields/validateField/validate not support callback, please use promise instead', | ||||
|       ); | ||||
|       if (!this.model) { | ||||
|       if (!props.model) { | ||||
|         warning(false, 'Form', 'model is required for validateFields to work.'); | ||||
|         return Promise.reject('Form `model` is required for validateFields to work.'); | ||||
|       } | ||||
|  | @ -227,25 +225,25 @@ const Form = defineComponent({ | |||
|         errors: string[]; | ||||
|       }>[] = []; | ||||
| 
 | ||||
|       this.fields.forEach(field => { | ||||
|       Object.values(fields).forEach(field => { | ||||
|         // Add field if not provide `nameList` | ||||
|         if (!provideNameList) { | ||||
|           namePathList.push(field.getNamePath()); | ||||
|           namePathList.push(field.namePath.value); | ||||
|         } | ||||
| 
 | ||||
|         // Skip if without rule | ||||
|         if (!field.getRules().length) { | ||||
|         if (!field.rules?.value.length) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         const fieldNamePath = field.getNamePath(); | ||||
|         const fieldNamePath = field.namePath.value; | ||||
| 
 | ||||
|         // Add field validate rule in to promise list | ||||
|         if (!provideNameList || containsNamePath(namePathList, fieldNamePath)) { | ||||
|           const promise = field.validateRules({ | ||||
|             validateMessages: { | ||||
|               ...defaultValidateMessages, | ||||
|               ...this.validateMessages, | ||||
|               ...props.validateMessages, | ||||
|             }, | ||||
|             ...options, | ||||
|           }); | ||||
|  | @ -265,21 +263,21 @@ const Form = defineComponent({ | |||
|       }); | ||||
| 
 | ||||
|       const summaryPromise = allPromiseFinish(promiseList); | ||||
|       this.lastValidatePromise = summaryPromise; | ||||
|       lastValidatePromise.value = summaryPromise; | ||||
| 
 | ||||
|       const returnPromise = summaryPromise | ||||
|         .then(() => { | ||||
|           if (this.lastValidatePromise === summaryPromise) { | ||||
|             return Promise.resolve(this.getFieldsValue(namePathList)); | ||||
|           if (lastValidatePromise.value === summaryPromise) { | ||||
|             return Promise.resolve(getFieldsValue(namePathList)); | ||||
|           } | ||||
|           return Promise.reject([]); | ||||
|         }) | ||||
|         .catch(results => { | ||||
|           const errorList = results.filter(result => result && result.errors.length); | ||||
|           return Promise.reject({ | ||||
|             values: this.getFieldsValue(namePathList), | ||||
|             values: getFieldsValue(namePathList), | ||||
|             errorFields: errorList, | ||||
|             outOfDate: this.lastValidatePromise !== summaryPromise, | ||||
|             outOfDate: lastValidatePromise.value !== summaryPromise, | ||||
|           }); | ||||
|         }); | ||||
| 
 | ||||
|  | @ -287,29 +285,65 @@ const Form = defineComponent({ | |||
|       returnPromise.catch(e => e); | ||||
| 
 | ||||
|       return returnPromise; | ||||
|     }, | ||||
|     validateField(...args: any[]) { | ||||
|       return this.validateFields(...args); | ||||
|     }, | ||||
|   }, | ||||
|     }; | ||||
|     const validateField = (...args: any[]) => { | ||||
|       return validateFields(...args); | ||||
|     }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { prefixCls: customizePrefixCls, hideRequiredMark, layout, handleSubmit, size } = this; | ||||
|     const getPrefixCls = this.configProvider.getPrefixCls; | ||||
|     const prefixCls = getPrefixCls('form', customizePrefixCls); | ||||
|     const { class: className, ...restProps } = this.$attrs; | ||||
|     const handleSubmit = (e: Event) => { | ||||
|       e.preventDefault(); | ||||
|       e.stopPropagation(); | ||||
|       emit('submit', e); | ||||
|       const res = validateFields(); | ||||
|       res | ||||
|         .then(values => { | ||||
|           emit('finish', values); | ||||
|         }) | ||||
|         .catch(errors => { | ||||
|           handleFinishFailed(errors); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     const formClassName = classNames(prefixCls, className, { | ||||
|       [`${prefixCls}-${layout}`]: true, | ||||
|       // [`${prefixCls}-rtl`]: direction === 'rtl', | ||||
|       [`${prefixCls}-${size}`]: size, | ||||
|       [`${prefixCls}-hide-required-mark`]: hideRequiredMark, | ||||
|     expose({ | ||||
|       resetFields, | ||||
|       clearValidate, | ||||
|       validateFields, | ||||
|       getFieldsValue, | ||||
|       validate, | ||||
|       scrollToField, | ||||
|     }); | ||||
|     return ( | ||||
|       <form onSubmit={handleSubmit} class={formClassName} {...restProps}> | ||||
|         {getSlot(this)} | ||||
|       </form> | ||||
| 
 | ||||
|     useProvideForm({ | ||||
|       model: computed(() => props.model), | ||||
|       name: computed(() => props.name), | ||||
|       labelAlign: computed(() => props.labelAlign), | ||||
|       labelCol: computed(() => props.labelCol), | ||||
|       wrapperCol: computed(() => props.wrapperCol), | ||||
|       vertical: computed(() => props.layout === 'vertical'), | ||||
|       colon: computed(() => props.colon), | ||||
|       requiredMark: mergedRequiredMark, | ||||
|       validateTrigger: computed(() => props.validateTrigger), | ||||
|       rules: computed(() => props.rules), | ||||
|       addField, | ||||
|       removeField, | ||||
|     }); | ||||
| 
 | ||||
|     watch( | ||||
|       () => props.rules, | ||||
|       () => { | ||||
|         if (props.validateOnRuleChange) { | ||||
|           validateFields(); | ||||
|         } | ||||
|       }, | ||||
|     ); | ||||
| 
 | ||||
|     return () => { | ||||
|       return ( | ||||
|         <form onSubmit={handleSubmit} class={formClassName.value}> | ||||
|           {slots.default?.()} | ||||
|         </form> | ||||
|       ); | ||||
|     }; | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,50 +1,47 @@ | |||
| import { | ||||
|   inject, | ||||
|   provide, | ||||
|   PropType, | ||||
|   defineComponent, | ||||
|   computed, | ||||
|   nextTick, | ||||
|   ExtractPropTypes, | ||||
|   ref, | ||||
|   watchEffect, | ||||
|   onBeforeUnmount, | ||||
|   ComputedRef, | ||||
| } from 'vue'; | ||||
| import cloneDeep from 'lodash-es/cloneDeep'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import { getTransitionProps, Transition } from '../_util/transition'; | ||||
| import Row from '../grid/Row'; | ||||
| import Col, { ColProps } from '../grid/Col'; | ||||
| import hasProp, { | ||||
|   findDOMNode, | ||||
|   getComponent, | ||||
|   getOptionProps, | ||||
|   getEvents, | ||||
|   isValidElement, | ||||
|   getSlot, | ||||
| } from '../_util/props-util'; | ||||
| import { ColProps } from '../grid/Col'; | ||||
| import { isValidElement, flattenChildren } from '../_util/props-util'; | ||||
| import BaseMixin from '../_util/BaseMixin'; | ||||
| import { defaultConfigProvider } from '../config-provider'; | ||||
| import { cloneElement } from '../_util/vnode'; | ||||
| import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled'; | ||||
| import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled'; | ||||
| import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; | ||||
| import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; | ||||
| import { validateRules } from './utils/validateUtil'; | ||||
| import { validateRules as validateRulesUtil } from './utils/validateUtil'; | ||||
| import { getNamePath } from './utils/valueUtil'; | ||||
| import { toArray } from './utils/typeUtil'; | ||||
| import { warning } from '../vc-util/warning'; | ||||
| import find from 'lodash-es/find'; | ||||
| import { tuple, VueNode } from '../_util/type'; | ||||
| import { ValidateOptions } from './interface'; | ||||
| import { tuple } from '../_util/type'; | ||||
| import { InternalNamePath, RuleObject, ValidateOptions } from './interface'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| import { useInjectForm } from './context'; | ||||
| import FormItemLabel from './FormItemLabel'; | ||||
| import FormItemInput from './FormItemInput'; | ||||
| import { ValidationRule } from './Form'; | ||||
| 
 | ||||
| const ValidateStatuses = tuple('success', 'warning', 'error', 'validating', ''); | ||||
| export type ValidateStatus = typeof ValidateStatuses[number]; | ||||
| 
 | ||||
| const iconMap = { | ||||
|   success: CheckCircleFilled, | ||||
|   warning: ExclamationCircleFilled, | ||||
|   error: CloseCircleFilled, | ||||
|   validating: LoadingOutlined, | ||||
| }; | ||||
| export interface FieldExpose { | ||||
|   fieldValue: ComputedRef<any>; | ||||
|   fieldId: ComputedRef<any>; | ||||
|   fieldName: ComputedRef<any>; | ||||
|   resetField: () => void; | ||||
|   clearValidate: () => void; | ||||
|   namePath: ComputedRef<InternalNamePath>; | ||||
|   rules?: ComputedRef<ValidationRule[]>; | ||||
|   validateRules: (options: ValidateOptions) => Promise<void> | Promise<string[]>; | ||||
| } | ||||
| 
 | ||||
| function getPropByPath(obj: any, namePathList: any, strict?: boolean) { | ||||
|   let tempObj = obj; | ||||
|  | @ -103,15 +100,25 @@ export const formItemProps = { | |||
| 
 | ||||
| export type FormItemProps = Partial<ExtractPropTypes<typeof formItemProps>>; | ||||
| 
 | ||||
| let indexGuid = 0; | ||||
| export default defineComponent({ | ||||
|   name: 'AFormItem', | ||||
|   mixins: [BaseMixin], | ||||
|   inheritAttrs: false, | ||||
|   __ANT_NEW_FORM_ITEM: true, | ||||
|   props: formItemProps, | ||||
|   setup(props) { | ||||
|     const FormContext = inject('FormContext', {}) as any; | ||||
|   slots: ['help', 'label', 'extra'], | ||||
|   setup(props, { slots }) { | ||||
|     warning(props.prop === undefined, `\`prop\` is deprecated. Please use \`name\` instead.`); | ||||
|     const eventKey = `form-item-${++indexGuid}`; | ||||
|     const { prefixCls } = useConfigInject('form', props); | ||||
|     const formContext = useInjectForm(); | ||||
|     const fieldName = computed(() => props.name || props.prop); | ||||
|     const errors = ref([]); | ||||
|     const validateMessage = ref(''); | ||||
|     const validateDisabled = ref(false); | ||||
|     const domErrorVisible = ref(false); | ||||
|     const inputRef = ref(); | ||||
|     const namePath = computed(() => { | ||||
|       const val = fieldName.value; | ||||
|       return getNamePath(val); | ||||
|  | @ -123,26 +130,30 @@ export default defineComponent({ | |||
|       } else if (!namePath.value.length) { | ||||
|         return undefined; | ||||
|       } else { | ||||
|         const formName = FormContext.name; | ||||
|         const formName = formContext.name.value; | ||||
|         const mergedId = namePath.value.join('_'); | ||||
|         return formName ? `${formName}_${mergedId}` : mergedId; | ||||
|       } | ||||
|     }); | ||||
|     const fieldValue = computed(() => { | ||||
|       const model = FormContext.model; | ||||
|       const model = formContext.model.value; | ||||
|       if (!model || !fieldName.value) { | ||||
|         return; | ||||
|       } | ||||
|       return getPropByPath(model, namePath.value, true).v; | ||||
|     }); | ||||
| 
 | ||||
|     const initialValue = ref(cloneDeep(fieldValue.value)); | ||||
|     const mergedValidateTrigger = computed(() => { | ||||
|       let validateTrigger = | ||||
|         props.validateTrigger !== undefined ? props.validateTrigger : FormContext.validateTrigger; | ||||
|         props.validateTrigger !== undefined | ||||
|           ? props.validateTrigger | ||||
|           : formContext.validateTrigger.value; | ||||
|       validateTrigger = validateTrigger === undefined ? 'change' : validateTrigger; | ||||
|       return toArray(validateTrigger); | ||||
|     }); | ||||
|     const getRules = () => { | ||||
|       let formRules = FormContext.rules; | ||||
|     const rulesRef = computed<ValidationRule[]>(() => { | ||||
|       let formRules = formContext.rules.value; | ||||
|       const selfRules = props.rules; | ||||
|       const requiredRule = | ||||
|         props.required !== undefined | ||||
|  | @ -156,9 +167,9 @@ export default defineComponent({ | |||
|       } else { | ||||
|         return rules.concat(requiredRule); | ||||
|       } | ||||
|     }; | ||||
|     }); | ||||
|     const isRequired = computed(() => { | ||||
|       const rules = getRules(); | ||||
|       const rules = rulesRef.value; | ||||
|       let isRequired = false; | ||||
|       if (rules && rules.length) { | ||||
|         rules.every(rule => { | ||||
|  | @ -171,360 +182,234 @@ export default defineComponent({ | |||
|       } | ||||
|       return isRequired || props.required; | ||||
|     }); | ||||
|     return { | ||||
|       isFormItemChildren: inject('isFormItemChildren', false), | ||||
|       configProvider: inject('configProvider', defaultConfigProvider), | ||||
|       FormContext, | ||||
|       fieldId, | ||||
|       fieldName, | ||||
|       namePath, | ||||
|       isRequired, | ||||
|       getRules, | ||||
|       fieldValue, | ||||
|       mergedValidateTrigger, | ||||
|     }; | ||||
|   }, | ||||
|   data() { | ||||
|     warning(!hasProp(this, 'prop'), `\`prop\` is deprecated. Please use \`name\` instead.`); | ||||
|     return { | ||||
|       validateState: this.validateStatus, | ||||
|       validateMessage: '', | ||||
|       validateDisabled: false, | ||||
|       validator: {}, | ||||
|       helpShow: false, | ||||
|       errors: [], | ||||
|       initialValue: undefined, | ||||
|     }; | ||||
|   }, | ||||
|   watch: { | ||||
|     validateStatus(val) { | ||||
|       this.validateState = val; | ||||
|     }, | ||||
|   }, | ||||
|   created() { | ||||
|     provide('isFormItemChildren', true); | ||||
|   }, | ||||
|   mounted() { | ||||
|     if (this.fieldName) { | ||||
|       const { addField } = this.FormContext; | ||||
|       addField && addField(this); | ||||
|       this.initialValue = cloneDeep(this.fieldValue); | ||||
|     } | ||||
|   }, | ||||
|   beforeUnmount() { | ||||
|     const { removeField } = this.FormContext; | ||||
|     removeField && removeField(this); | ||||
|   }, | ||||
|   methods: { | ||||
|     getNamePath() { | ||||
|       const { fieldName } = this; | ||||
|       const { prefixName = [] } = this.FormContext; | ||||
| 
 | ||||
|       return fieldName !== undefined ? [...prefixName, ...this.namePath] : []; | ||||
|     }, | ||||
|     validateRules(options: ValidateOptions) { | ||||
|       const { validateFirst = false, messageVariables } = this.$props; | ||||
|     const validateState = ref(); | ||||
|     watchEffect(() => { | ||||
|       validateState.value = props.validateStatus; | ||||
|     }); | ||||
| 
 | ||||
|     const validateRules = (options: ValidateOptions) => { | ||||
|       const { validateFirst = false, messageVariables } = props; | ||||
|       const { triggerName } = options || {}; | ||||
|       const namePath = this.getNamePath(); | ||||
| 
 | ||||
|       let filteredRules = this.getRules(); | ||||
|       let filteredRules = rulesRef.value; | ||||
|       if (triggerName) { | ||||
|         filteredRules = filteredRules.filter(rule => { | ||||
|           const { trigger } = rule; | ||||
|           if (!trigger && !this.mergedValidateTrigger.length) { | ||||
|           if (!trigger && !mergedValidateTrigger.value.length) { | ||||
|             return true; | ||||
|           } | ||||
|           const triggerList = toArray(trigger || this.mergedValidateTrigger); | ||||
|           const triggerList = toArray(trigger || mergedValidateTrigger.value); | ||||
|           return triggerList.includes(triggerName); | ||||
|         }); | ||||
|       } | ||||
|       if (!filteredRules.length) { | ||||
|         return Promise.resolve(); | ||||
|       } | ||||
|       const promise = validateRules( | ||||
|         namePath, | ||||
|         this.fieldValue, | ||||
|         filteredRules, | ||||
|       const promise = validateRulesUtil( | ||||
|         namePath.value, | ||||
|         fieldValue.value, | ||||
|         filteredRules as RuleObject[], | ||||
|         options, | ||||
|         validateFirst, | ||||
|         messageVariables, | ||||
|       ); | ||||
|       this.validateState = 'validating'; | ||||
|       this.errors = []; | ||||
|       validateState.value = 'validating'; | ||||
|       errors.value = []; | ||||
| 
 | ||||
|       promise | ||||
|         .catch(e => e) | ||||
|         .then((errors = []) => { | ||||
|           if (this.validateState === 'validating') { | ||||
|             this.validateState = errors.length ? 'error' : 'success'; | ||||
|             this.validateMessage = errors[0]; | ||||
|             this.errors = errors; | ||||
|         .then((ers = []) => { | ||||
|           if (validateState.value === 'validating') { | ||||
|             validateState.value = ers.length ? 'error' : 'success'; | ||||
|             validateMessage.value = ers[0]; | ||||
|             errors.value = ers; | ||||
|           } | ||||
|         }); | ||||
| 
 | ||||
|       return promise; | ||||
|     }, | ||||
|     onFieldBlur() { | ||||
|       this.validateRules({ triggerName: 'blur' }); | ||||
|     }, | ||||
|     onFieldChange() { | ||||
|       if (this.validateDisabled) { | ||||
|         this.validateDisabled = false; | ||||
|     }; | ||||
| 
 | ||||
|     const onFieldBlur = () => { | ||||
|       validateRules({ triggerName: 'blur' }); | ||||
|     }; | ||||
|     const onFieldChange = () => { | ||||
|       if (validateDisabled.value) { | ||||
|         validateDisabled.value = false; | ||||
|         return; | ||||
|       } | ||||
|       this.validateRules({ triggerName: 'change' }); | ||||
|     }, | ||||
|     clearValidate() { | ||||
|       this.validateState = ''; | ||||
|       this.validateMessage = ''; | ||||
|       this.validateDisabled = false; | ||||
|     }, | ||||
|     resetField() { | ||||
|       this.validateState = ''; | ||||
|       this.validateMessage = ''; | ||||
|       const model = this.FormContext.model || {}; | ||||
|       const value = this.fieldValue; | ||||
|       const prop = getPropByPath(model, this.namePath, true); | ||||
|       this.validateDisabled = true; | ||||
|       validateRules({ triggerName: 'change' }); | ||||
|     }; | ||||
|     const clearValidate = () => { | ||||
|       validateState.value = ''; | ||||
|       validateMessage.value = ''; | ||||
|       validateDisabled.value = false; | ||||
|     }; | ||||
| 
 | ||||
|     const resetField = () => { | ||||
|       validateState.value = ''; | ||||
|       validateMessage.value = ''; | ||||
|       const model = formContext.model.value || {}; | ||||
|       const value = fieldValue.value; | ||||
|       const prop = getPropByPath(model, namePath.value, true); | ||||
|       validateDisabled.value = true; | ||||
|       if (Array.isArray(value)) { | ||||
|         prop.o[prop.k] = [].concat(this.initialValue); | ||||
|         prop.o[prop.k] = [].concat(initialValue.value); | ||||
|       } else { | ||||
|         prop.o[prop.k] = this.initialValue; | ||||
|         prop.o[prop.k] = initialValue.value; | ||||
|       } | ||||
|       // reset validateDisabled after onFieldChange triggered | ||||
|       nextTick(() => { | ||||
|         this.validateDisabled = false; | ||||
|         validateDisabled.value = false; | ||||
|       }); | ||||
|     }, | ||||
|     getHelpMessage() { | ||||
|       const help = getComponent(this, 'help'); | ||||
|     }; | ||||
| 
 | ||||
|       return this.validateMessage || help; | ||||
|     }, | ||||
| 
 | ||||
|     onLabelClick() { | ||||
|       const id = this.fieldId; | ||||
|       if (!id) { | ||||
|     const onLabelClick = () => { | ||||
|       const id = fieldId.value; | ||||
|       if (!id || !inputRef.value) { | ||||
|         return; | ||||
|       } | ||||
|       const formItemNode = findDOMNode(this); | ||||
|       const control = formItemNode.querySelector(`[id="${id}"]`); | ||||
|       const control = inputRef.value.$el.querySelector(`[id="${id}"]`); | ||||
|       if (control && control.focus) { | ||||
|         control.focus(); | ||||
|       } | ||||
|     }, | ||||
|     }; | ||||
|     formContext.addField(eventKey, { | ||||
|       fieldValue, | ||||
|       fieldId, | ||||
|       fieldName, | ||||
|       resetField, | ||||
|       clearValidate, | ||||
|       namePath, | ||||
|       validateRules, | ||||
|       rules: rulesRef, | ||||
|     }); | ||||
|     onBeforeUnmount(() => { | ||||
|       formContext.removeField(eventKey); | ||||
|     }); | ||||
|     // const onHelpAnimEnd = (_key: string, helpShow: boolean) => { | ||||
|     //   this.helpShow = helpShow; | ||||
|     //   if (!helpShow) { | ||||
|     //     this.$forceUpdate(); | ||||
|     //   } | ||||
|     // }; | ||||
|     const itemClassName = computed(() => ({ | ||||
|       [`${prefixCls.value}-item`]: true, | ||||
| 
 | ||||
|     onHelpAnimEnd(_key: string, helpShow: boolean) { | ||||
|       this.helpShow = helpShow; | ||||
|       if (!helpShow) { | ||||
|         this.$forceUpdate(); | ||||
|       } | ||||
|     }, | ||||
| 
 | ||||
|     renderHelp(prefixCls: string) { | ||||
|       const help = this.getHelpMessage(); | ||||
|       const children = help ? ( | ||||
|         <div class={`${prefixCls}-explain`} key="help"> | ||||
|           {help} | ||||
|         </div> | ||||
|       ) : null; | ||||
|       if (children) { | ||||
|         this.helpShow = !!children; | ||||
|       } | ||||
|       const transitionProps = getTransitionProps('show-help', { | ||||
|         onAfterEnter: () => this.onHelpAnimEnd('help', true), | ||||
|         onAfterLeave: () => this.onHelpAnimEnd('help', false), | ||||
|       }); | ||||
|       return ( | ||||
|         <Transition {...transitionProps} key="help"> | ||||
|           {children} | ||||
|         </Transition> | ||||
|       ); | ||||
|     }, | ||||
| 
 | ||||
|     renderExtra(prefixCls: string) { | ||||
|       const extra = getComponent(this, 'extra'); | ||||
|       return extra ? <div class={`${prefixCls}-extra`}>{extra}</div> : null; | ||||
|     }, | ||||
| 
 | ||||
|     renderValidateWrapper(prefixCls: string, c1: VueNode, c2: VueNode, c3: VueNode) { | ||||
|       const validateStatus = this.validateState; | ||||
| 
 | ||||
|       let classes = `${prefixCls}-item-control`; | ||||
|       if (validateStatus) { | ||||
|         classes = classNames(`${prefixCls}-item-control`, { | ||||
|           'has-feedback': validateStatus && this.hasFeedback, | ||||
|           'has-success': validateStatus === 'success', | ||||
|           'has-warning': validateStatus === 'warning', | ||||
|           'has-error': validateStatus === 'error', | ||||
|           'is-validating': validateStatus === 'validating', | ||||
|       // Status | ||||
|       [`${prefixCls.value}-item-has-feedback`]: validateState.value && props.hasFeedback, | ||||
|       [`${prefixCls.value}-item-has-success`]: validateState.value === 'success', | ||||
|       [`${prefixCls.value}-item-has-warning`]: validateState.value === 'warning', | ||||
|       [`${prefixCls.value}-item-has-error`]: validateState.value === 'error', | ||||
|       [`${prefixCls.value}-item-is-validating`]: validateState.value === 'validating', | ||||
|       [`${prefixCls.value}-item-hidden`]: props.hidden, | ||||
|     })); | ||||
|     return () => { | ||||
|       const help = props.help ?? slots.help?.(); | ||||
|       const children = flattenChildren(slots.default?.()); | ||||
|       let firstChildren = children[0]; | ||||
|       if (fieldName.value && props.autoLink && isValidElement(firstChildren)) { | ||||
|         const originalEvents = firstChildren.props; | ||||
|         const originalBlur = originalEvents.onBlur; | ||||
|         const originalChange = originalEvents.onChange; | ||||
|         firstChildren = cloneElement(firstChildren, { | ||||
|           ...(fieldId.value ? { id: fieldId.value } : undefined), | ||||
|           onBlur: (...args: any[]) => { | ||||
|             if (Array.isArray(originalChange)) { | ||||
|               for (let i = 0, l = originalChange.length; i < l; i++) { | ||||
|                 originalBlur[i](...args); | ||||
|               } | ||||
|             } else if (originalBlur) { | ||||
|               originalBlur(...args); | ||||
|             } | ||||
|             onFieldBlur(); | ||||
|           }, | ||||
|           onChange: (...args: any[]) => { | ||||
|             if (Array.isArray(originalChange)) { | ||||
|               for (let i = 0, l = originalChange.length; i < l; i++) { | ||||
|                 originalChange[i](...args); | ||||
|               } | ||||
|             } else if (originalChange) { | ||||
|               originalChange(...args); | ||||
|             } | ||||
|             onFieldChange(); | ||||
|           }, | ||||
|         }); | ||||
|       } | ||||
|       const IconNode = validateStatus && iconMap[validateStatus]; | ||||
| 
 | ||||
|       const icon = | ||||
|         this.hasFeedback && IconNode ? ( | ||||
|           <span class={`${prefixCls}-item-children-icon`}> | ||||
|             <IconNode /> | ||||
|           </span> | ||||
|         ) : null; | ||||
|       return ( | ||||
|         <div class={classes}> | ||||
|           <span class={`${prefixCls}-item-children`}> | ||||
|             {c1} | ||||
|             {icon} | ||||
|           </span> | ||||
|           {c2} | ||||
|           {c3} | ||||
|         </div> | ||||
|       ); | ||||
|     }, | ||||
| 
 | ||||
|     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; | ||||
|       const className = classNames(`${prefixCls}-item-control`, mergedWrapperCol.class); | ||||
|       const colProps = { | ||||
|         ...restProps, | ||||
|         class: className, | ||||
|         key: 'wrapper', | ||||
|         style, | ||||
|         id, | ||||
|       }; | ||||
|       return <Col {...colProps}>{children}</Col>; | ||||
|     }, | ||||
| 
 | ||||
|     renderLabel(prefixCls: string) { | ||||
|       const { | ||||
|         vertical, | ||||
|         labelAlign: contextLabelAlign, | ||||
|         labelCol: contextLabelCol, | ||||
|         colon: contextColon, | ||||
|       } = this.FormContext; | ||||
|       const { labelAlign, labelCol, colon, fieldId, htmlFor } = this; | ||||
|       const label = getComponent(this, 'label'); | ||||
|       const required = this.isRequired; | ||||
|       const mergedLabelCol = labelCol || contextLabelCol || {}; | ||||
| 
 | ||||
|       const mergedLabelAlign = labelAlign || contextLabelAlign; | ||||
|       const labelClsBasic = `${prefixCls}-item-label`; | ||||
|       const labelColClassName = classNames( | ||||
|         labelClsBasic, | ||||
|         mergedLabelAlign === 'left' && `${labelClsBasic}-left`, | ||||
|         mergedLabelCol.class, | ||||
|       ); | ||||
|       const { | ||||
|         class: labelColClass, | ||||
|         style: labelColStyle, | ||||
|         id: labelColId, | ||||
|         ...restProps | ||||
|       } = mergedLabelCol; | ||||
|       let labelChildren = label; | ||||
|       // Keep label is original where there should have no colon | ||||
|       const computedColon = colon === true || (contextColon !== false && colon !== false); | ||||
|       const haveColon = computedColon && !vertical; | ||||
|       // Remove duplicated user input colon | ||||
|       if (haveColon && typeof label === 'string' && label.trim() !== '') { | ||||
|         labelChildren = label.replace(/[::]\s*$/, ''); | ||||
|       } | ||||
| 
 | ||||
|       const labelClassName = classNames({ | ||||
|         [`${prefixCls}-item-required`]: required, | ||||
|         [`${prefixCls}-item-no-colon`]: !computedColon, | ||||
|       }); | ||||
|       const colProps = { | ||||
|         ...restProps, | ||||
|         class: labelColClassName, | ||||
|         key: 'label', | ||||
|         style: labelColStyle, | ||||
|         id: labelColId, | ||||
|       }; | ||||
| 
 | ||||
|       return label ? ( | ||||
|         <Col {...colProps}> | ||||
|           <label | ||||
|             for={htmlFor || fieldId} | ||||
|             class={labelClassName} | ||||
|             title={typeof label === 'string' ? label : ''} | ||||
|             onClick={this.onLabelClick} | ||||
|         <Row | ||||
|           class={[ | ||||
|             itemClassName.value, | ||||
|             domErrorVisible.value || !!help ? `${prefixCls.value}-item-with-help` : '', | ||||
|           ]} | ||||
|           key="row" | ||||
|         > | ||||
|           {/* Label */} | ||||
|           <FormItemLabel | ||||
|             {...props} | ||||
|             htmlFor={fieldId.value} | ||||
|             required={isRequired.value} | ||||
|             requiredMark={formContext.requiredMark.value} | ||||
|             prefixCls={prefixCls.value} | ||||
|             onClick={onLabelClick} | ||||
|             label={props.label ?? slots.label?.()} | ||||
|           /> | ||||
|           {/* Input Group */} | ||||
|           <FormItemInput | ||||
|             {...props} | ||||
|             errors={errors.value} | ||||
|             prefixCls={prefixCls.value} | ||||
|             status={validateState.value} | ||||
|             onDomErrorVisibleChange={(v: boolean) => (domErrorVisible.value = v)} | ||||
|             validateStatus={validateState.value} | ||||
|             ref={inputRef} | ||||
|             help={help} | ||||
|             extra={props.extra ?? slots.extra?.()} | ||||
|           > | ||||
|             {labelChildren} | ||||
|           </label> | ||||
|         </Col> | ||||
|       ) : null; | ||||
|     }, | ||||
|     renderChildren(prefixCls: string, child: VueNode) { | ||||
|       return [ | ||||
|         this.renderLabel(prefixCls), | ||||
|         this.renderWrapper( | ||||
|           prefixCls, | ||||
|           this.renderValidateWrapper( | ||||
|             prefixCls, | ||||
|             child, | ||||
|             this.renderHelp(prefixCls), | ||||
|             this.renderExtra(prefixCls), | ||||
|           ), | ||||
|         ), | ||||
|       ]; | ||||
|     }, | ||||
|     renderFormItem(child: any[]) { | ||||
|       const validateStatus = this.validateState; | ||||
|       const { prefixCls: customizePrefixCls, hidden, hasFeedback } = this.$props; | ||||
|       const { class: className, ...restProps } = this.$attrs as any; | ||||
|       const getPrefixCls = this.configProvider.getPrefixCls; | ||||
|       const prefixCls = getPrefixCls('form', customizePrefixCls); | ||||
|       const children = this.renderChildren(prefixCls, child); | ||||
|       const itemClassName = { | ||||
|         [className]: className, | ||||
|         [`${prefixCls}-item`]: true, | ||||
|         [`${prefixCls}-item-with-help`]: this.helpShow, | ||||
| 
 | ||||
|         // Status | ||||
|         [`${prefixCls}-item-has-feedback`]: validateStatus && hasFeedback, | ||||
|         [`${prefixCls}-item-has-success`]: validateStatus === 'success', | ||||
|         [`${prefixCls}-item-has-warning`]: validateStatus === 'warning', | ||||
|         [`${prefixCls}-item-has-error`]: validateStatus === 'error', | ||||
|         [`${prefixCls}-item-is-validating`]: validateStatus === 'validating', | ||||
|         [`${prefixCls}-item-hidden`]: hidden, | ||||
|       }; | ||||
| 
 | ||||
|       return ( | ||||
|         <Row class={classNames(itemClassName)} key="row" {...restProps}> | ||||
|           {children} | ||||
|             {[firstChildren, children.slice(1)]} | ||||
|           </FormItemInput> | ||||
|         </Row> | ||||
|       ); | ||||
|     }, | ||||
|   }, | ||||
|   render() { | ||||
|     const { autoLink } = getOptionProps(this); | ||||
|     const children = getSlot(this); | ||||
|     let firstChildren = children[0]; | ||||
|     if (this.fieldName && autoLink && isValidElement(firstChildren)) { | ||||
|       const originalEvents = getEvents(firstChildren); | ||||
|       const originalBlur = originalEvents.onBlur; | ||||
|       const originalChange = originalEvents.onChange; | ||||
|       firstChildren = cloneElement(firstChildren, { | ||||
|         ...(this.fieldId ? { id: this.fieldId } : undefined), | ||||
|         onBlur: (...args: any[]) => { | ||||
|           originalBlur && originalBlur(...args); | ||||
|           this.onFieldBlur(); | ||||
|         }, | ||||
|         onChange: (...args: any[]) => { | ||||
|           if (Array.isArray(originalChange)) { | ||||
|             for (let i = 0, l = originalChange.length; i < l; i++) { | ||||
|               originalChange[i](...args); | ||||
|             } | ||||
|           } else if (originalChange) { | ||||
|             originalChange(...args); | ||||
|           } | ||||
|           this.onFieldChange(); | ||||
|         }, | ||||
|       }); | ||||
|     } | ||||
|     return this.renderFormItem([firstChildren, children.slice(1)]); | ||||
|     }; | ||||
|   }, | ||||
|   // data() { | ||||
|   //   warning(!hasProp(this, 'prop'), `\`prop\` is deprecated. Please use \`name\` instead.`); | ||||
|   //   return { | ||||
|   //     validateState: this.validateStatus, | ||||
|   //     validateMessage: '', | ||||
|   //     validateDisabled: false, | ||||
|   //     validator: {}, | ||||
|   //     helpShow: false, | ||||
|   //     errors: [], | ||||
|   //     initialValue: undefined, | ||||
|   //   }; | ||||
|   // }, | ||||
|   // render() { | ||||
|   //   const { autoLink } = getOptionProps(this); | ||||
|   //   const children = getSlot(this); | ||||
|   //   let firstChildren = children[0]; | ||||
|   //   if (this.fieldName && autoLink && isValidElement(firstChildren)) { | ||||
|   //     const originalEvents = getEvents(firstChildren); | ||||
|   //     const originalBlur = originalEvents.onBlur; | ||||
|   //     const originalChange = originalEvents.onChange; | ||||
|   //     firstChildren = cloneElement(firstChildren, { | ||||
|   //       ...(this.fieldId ? { id: this.fieldId } : undefined), | ||||
|   //       onBlur: (...args: any[]) => { | ||||
|   //         originalBlur && originalBlur(...args); | ||||
|   //         this.onFieldBlur(); | ||||
|   //       }, | ||||
|   //       onChange: (...args: any[]) => { | ||||
|   //         if (Array.isArray(originalChange)) { | ||||
|   //           for (let i = 0, l = originalChange.length; i < l; i++) { | ||||
|   //             originalChange[i](...args); | ||||
|   //           } | ||||
|   //         } else if (originalChange) { | ||||
|   //           originalChange(...args); | ||||
|   //         } | ||||
|   //         this.onFieldChange(); | ||||
|   //       }, | ||||
|   //     }); | ||||
|   //   } | ||||
|   //   return this.renderFormItem([firstChildren, children.slice(1)]); | ||||
|   // }, | ||||
| }); | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled'; | |||
| import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled'; | ||||
| import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled'; | ||||
| 
 | ||||
| import Col, { ColProps } from '../grid/col'; | ||||
| import Col, { ColProps } from '../grid/Col'; | ||||
| import { useProvideForm, useInjectForm, useProvideFormItemPrefix } from './context'; | ||||
| import ErrorList from './ErrorList'; | ||||
| import classNames from '../_util/classNames'; | ||||
|  | @ -11,7 +11,7 @@ import { ValidateStatus } from './FormItem'; | |||
| import { VueNode } from '../_util/type'; | ||||
| import { computed, defineComponent, HTMLAttributes, onUnmounted } from 'vue'; | ||||
| 
 | ||||
| interface FormItemInputMiscProps { | ||||
| export interface FormItemInputMiscProps { | ||||
|   prefixCls: string; | ||||
|   errors: VueNode[]; | ||||
|   hasFeedback?: boolean; | ||||
|  | @ -32,8 +32,20 @@ const iconMap: { [key: string]: any } = { | |||
|   error: CloseCircleFilled, | ||||
|   validating: LoadingOutlined, | ||||
| }; | ||||
| const FormItemInput = defineComponent<FormItemInputProps & FormItemInputMiscProps>({ | ||||
| const FormItemInput = defineComponent({ | ||||
|   slots: ['help', 'extra', 'errors'], | ||||
|   inheritAttrs: false, | ||||
|   props: [ | ||||
|     'prefixCls', | ||||
|     'errors', | ||||
|     'hasFeedback', | ||||
|     'validateStatus', | ||||
|     'onDomErrorVisibleChange', | ||||
|     'wrapperCol', | ||||
|     'help', | ||||
|     'extra', | ||||
|     'status', | ||||
|   ], | ||||
|   setup(props, { slots }) { | ||||
|     const formContext = useInjectForm(); | ||||
|     const { wrapperCol: contextWrapperCol } = formContext; | ||||
|  | @ -43,12 +55,15 @@ const FormItemInput = defineComponent<FormItemInputProps & FormItemInputMiscProp | |||
|     delete subFormContext.labelCol; | ||||
|     delete subFormContext.wrapperCol; | ||||
|     useProvideForm(subFormContext); | ||||
| 
 | ||||
|     useProvideFormItemPrefix({ | ||||
|       prefixCls: computed(() => props.prefixCls), | ||||
|       status: computed(() => props.status), | ||||
|     }); | ||||
| 
 | ||||
|     onUnmounted(() => { | ||||
|       props.onDomErrorVisibleChange(false); | ||||
|     }); | ||||
| 
 | ||||
|     return () => { | ||||
|       const { | ||||
|         prefixCls, | ||||
|  | @ -67,10 +82,6 @@ const FormItemInput = defineComponent<FormItemInputProps & FormItemInputMiscProp | |||
| 
 | ||||
|       const className = classNames(`${baseClassName}-control`, mergedWrapperCol.class); | ||||
| 
 | ||||
|       onUnmounted(() => { | ||||
|         onDomErrorVisibleChange(false); | ||||
|       }); | ||||
| 
 | ||||
|       // Should provides additional icon if `hasFeedback` | ||||
|       const IconNode = validateStatus && iconMap[validateStatus]; | ||||
|       const icon = | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import Col, { ColProps } from '../grid/col'; | ||||
| import Col, { ColProps } from '../grid/Col'; | ||||
| import { FormLabelAlign } from './interface'; | ||||
| import { useInjectForm } from './context'; | ||||
| import { RequiredMark } from './Form'; | ||||
|  | @ -17,10 +17,14 @@ export interface FormItemLabelProps { | |||
|   requiredMark?: RequiredMark; | ||||
|   required?: boolean; | ||||
|   prefixCls: string; | ||||
|   onClick: Function; | ||||
| } | ||||
| 
 | ||||
| const FormItemLabel: FunctionalComponent<FormItemLabelProps> = (props, { slots }) => { | ||||
|   const { prefixCls, htmlFor, labelCol, labelAlign, colon, required, requiredMark } = props; | ||||
| const FormItemLabel: FunctionalComponent<FormItemLabelProps> = (props, { slots, emit, attrs }) => { | ||||
|   const { prefixCls, htmlFor, labelCol, labelAlign, colon, required, requiredMark } = { | ||||
|     ...props, | ||||
|     ...attrs, | ||||
|   }; | ||||
|   const [formLocale] = useLocaleReceiver('Form'); | ||||
|   const label = props.label ?? slots.label?.(); | ||||
|   if (!label) return null; | ||||
|  | @ -68,7 +72,6 @@ const FormItemLabel: FunctionalComponent<FormItemLabelProps> = (props, { slots } | |||
|       </> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   const labelClassName = classNames({ | ||||
|     [`${prefixCls}-item-required`]: required, | ||||
|     [`${prefixCls}-item-required-mark-optional`]: requiredMark === 'optional', | ||||
|  | @ -80,6 +83,7 @@ const FormItemLabel: FunctionalComponent<FormItemLabelProps> = (props, { slots } | |||
|         html-for={htmlFor} | ||||
|         class={labelClassName} | ||||
|         title={typeof label === 'string' ? label : ''} | ||||
|         onClick={e => emit('click', e)} | ||||
|       > | ||||
|         {labelChildren} | ||||
|       </label> | ||||
|  | @ -88,5 +92,6 @@ const FormItemLabel: FunctionalComponent<FormItemLabelProps> = (props, { slots } | |||
| }; | ||||
| 
 | ||||
| FormItemLabel.displayName = 'FormItemLabel'; | ||||
| FormItemLabel.inheritAttrs = false; | ||||
| 
 | ||||
| export default FormItemLabel; | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| import { inject, InjectionKey, provide, ComputedRef, computed } from 'vue'; | ||||
| import { ColProps } from '../grid'; | ||||
| import { RequiredMark } from './Form'; | ||||
| import { ValidateStatus } from './FormItem'; | ||||
| import { RequiredMark, ValidationRule } from './Form'; | ||||
| import { ValidateStatus, FieldExpose } from './FormItem'; | ||||
| import { FormLabelAlign } from './interface'; | ||||
| 
 | ||||
| export interface FormContextProps { | ||||
|   model?: ComputedRef<any>; | ||||
|   vertical: ComputedRef<boolean>; | ||||
|   name?: ComputedRef<string>; | ||||
|   colon?: ComputedRef<boolean>; | ||||
|  | @ -13,6 +14,10 @@ export interface FormContextProps { | |||
|   wrapperCol?: ComputedRef<ColProps>; | ||||
|   requiredMark?: ComputedRef<RequiredMark>; | ||||
|   //itemRef: (name: (string | number)[]) => (node: React.ReactElement) => void;
 | ||||
|   addField: (eventKey: string, field: FieldExpose) => void; | ||||
|   removeField: (eventKey: string) => void; | ||||
|   validateTrigger?: ComputedRef<string | string[]>; | ||||
|   rules?: ComputedRef<{ [k: string]: ValidationRule[] | ValidationRule }>; | ||||
| } | ||||
| 
 | ||||
| export const FormContextKey: InjectionKey<FormContextProps> = Symbol('formContextKey'); | ||||
|  | @ -25,6 +30,10 @@ export const useInjectForm = () => { | |||
|   return inject(FormContextKey, { | ||||
|     labelAlign: computed(() => 'right' as FormLabelAlign), | ||||
|     vertical: computed(() => false), | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     addField: (_eventKey: string, _field: FieldExpose) => {}, | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     removeField: (_eventKey: string) => {}, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import { inject, defineComponent, CSSProperties, ExtractPropTypes, computed } from 'vue'; | ||||
| import { defineComponent, CSSProperties, ExtractPropTypes, computed } from 'vue'; | ||||
| import classNames from '../_util/classNames'; | ||||
| import PropTypes from '../_util/vue-types'; | ||||
| import { rowContextState } from './Row'; | ||||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| import { useInjectRow } from './context'; | ||||
| 
 | ||||
|  | @ -102,7 +101,7 @@ export default defineComponent({ | |||
|     const mergedStyle = computed(() => { | ||||
|       const { flex } = props; | ||||
|       const gutterVal = gutter.value; | ||||
|       let style: CSSProperties = {}; | ||||
|       const style: CSSProperties = {}; | ||||
|       // Horizontal gutter use padding | ||||
|       if (gutterVal && gutterVal[0] > 0) { | ||||
|         const horizontalGutter = `${gutterVal[0] / 2}px`; | ||||
|  |  | |||
|  | @ -211,7 +211,7 @@ export default defineComponent({ | |||
|       return ( | ||||
|         <aside {...attrs} class={siderCls} style={divStyle} ref={ref}> | ||||
|           <div class={`${pre}-children`}>{slots.default?.()}</div> | ||||
|           {collapsible || (below && zeroWidthTrigger) ? triggerDom : null} | ||||
|           {collapsible || (below.value && zeroWidthTrigger) ? triggerDom : null} | ||||
|         </aside> | ||||
|       ); | ||||
|     }; | ||||
|  |  | |||
|  | @ -3,8 +3,7 @@ import classNames from '../_util/classNames'; | |||
| import useConfigInject from '../_util/hooks/useConfigInject'; | ||||
| import { skeletonElementProps, SkeletonElementProps } from './Element'; | ||||
| 
 | ||||
| export interface SkeletonImageProps | ||||
|   extends Omit<SkeletonElementProps, 'size' | 'shape' | 'active'> {} | ||||
| export type SkeletonImageProps = Omit<SkeletonElementProps, 'size' | 'shape' | 'active'>; | ||||
| 
 | ||||
| const path = | ||||
|   'M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z'; | ||||
|  |  | |||
|  | @ -12,8 +12,8 @@ export const skeletonParagraphProps = { | |||
| export type SkeletonParagraphProps = Partial<ExtractPropTypes<typeof skeletonParagraphProps>>; | ||||
| 
 | ||||
| const SkeletonParagraph = defineComponent({ | ||||
|   props: skeletonParagraphProps, | ||||
|   name: 'SkeletonParagraph', | ||||
|   props: skeletonParagraphProps, | ||||
|   setup(props) { | ||||
|     const getWidth = (index: number) => { | ||||
|       const { width, rows = 2 } = props; | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ import useConfigInject from '../_util/hooks/useConfigInject'; | |||
| import Element from './Element'; | ||||
| 
 | ||||
| /* This only for skeleton internal. */ | ||||
| interface SkeletonAvatarProps extends Omit<AvatarProps, 'active'> {} | ||||
| type SkeletonAvatarProps = Omit<AvatarProps, 'active'>; | ||||
| 
 | ||||
| export const skeletonProps = { | ||||
|   active: PropTypes.looseBool, | ||||
|  |  | |||
|  | @ -9,8 +9,8 @@ export const skeletonTitleProps = { | |||
| export type SkeletonTitleProps = Partial<ExtractPropTypes<typeof skeletonTitleProps>>; | ||||
| 
 | ||||
| const SkeletonTitle = defineComponent({ | ||||
|   props: skeletonTitleProps, | ||||
|   name: 'SkeletonTitle', | ||||
|   props: skeletonTitleProps, | ||||
|   setup(props) { | ||||
|     return () => { | ||||
|       const { prefixCls, width } = props; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 tangjinzhou
						tangjinzhou