import { computed , defineComponent , inject , nextTick , onBeforeUnmount , onMounted , PropType , provide , reactive , ref , toRefs , } from 'vue' ; import { Schema } from 'b-validate' ; import { FormItemInfo , formItemKey , formKey } from './context' ; import { FieldData , FieldRule , ValidateStatus , ValidateTrigger , } from './interface' ; import { Row as ArcoRow , Col as ArcoCol } from '../grid' ; import FormItemLabel from './form-item-label.vue' ; import FormItemMessage from './form-item-message.vue' ; import { getPrefixCls } from '../_utils/global-config' ; import { getValueByPath } from '../_utils/get-value-by-path' ; import { Data } from '../_utils/types' ; import { mergeFirstChild } from '../_utils/vue-utils' ; import { getFinalValidateMessage , getFinalValidateStatus } from './utils' ; import { isFunction } from '../_utils/is' ; export default defineComponent ( { name : 'FormItem' , components : { ArcoRow , ArcoCol , FormItemLabel , FormItemMessage } , inheritAttrs : false , props : { zh en field : { type : String , default : '' } , zh en label : String , zh en showColon : { type : Boolean , default : false } , zh en noStyle : { type : Boolean , default : false } , zh en disabled : { type : Boolean , default : undefined } , zh en help : String , zh en extra : String , zh en required : { type : Boolean , default : false } , zh en rules : { type : [ Object , Array ] as PropType < FieldRule | FieldRule [ ] > } , zh en values validateStatus : { type : String as PropType < ValidateStatus > } , zh en values validateTrigger : { type : [ String , Array ] as PropType < ValidateTrigger | ValidateTrigger [ ] > , default : 'change' } , zh en labelColProps : Object , zh en wrapperColProps : Object , zh en hideLabel : { type : Boolean , default : false } , zh en hideAsterisk : { type : Boolean , default : false } , zh en version labelColStyle : Object , zh en version wrapperColStyle : Object , zh en version rowProps : Object , zh en version rowClass : [ String , Array , Object ] , zh en version contentClass : [ String , Array , Object ] , zh en version contentFlex : { type : Boolean , default : true } , zh en version mergeProps : { type : [ Boolean , Function ] as PropType < boolean | ( ( props : Record < string , any > ) => Record < string , any > ) > , default : true } , zh en version labelColFlex : { type : [ Number , String ] } } , zh en slot zh en slot zh en slot setup ( props , { slots , attrs } ) { const prefixCls = getPrefixCls ( 'form-item' ) ; const { field } = toRefs ( props ) ; const formCtx = inject ( formKey , undefined ) ; const mergedLabelCol = computed ( ( ) => { const colProps = { ... ( props . labelColProps ?? formCtx ?. labelColProps ) } ; if ( props . labelColFlex ) { colProps . flex = props . labelColFlex ; } else if ( formCtx ?. autoLabelWidth ) { colProps . flex = `${ formCtx ?. maxLabelWidth }px` ; } return colProps ; } ) ; const mergedWrapperCol = computed ( ( ) => { const colProps = { ... ( props . wrapperColProps ?? formCtx ?. wrapperColProps ) } ; if ( props . labelColFlex || formCtx ?. autoLabelWidth ) { colProps . flex = 'auto' ; } return colProps ; } ) ; const mergedLabelStyle = computed ( ( ) => props . labelColStyle ?? formCtx ?. labelColStyle ) ; const mergedWrapperStyle = computed ( ( ) => props . wrapperColStyle ?? formCtx ?. wrapperColStyle ) ; const initialValue = getValueByPath ( formCtx ?. model , props . field ) ; const validateStatus = reactive < Record < string , ValidateStatus | '' > > ( { } ) ; const validateMessage = reactive < Record < string , string > > ( { } ) ; const finalStatus = computed ( ( ) => getFinalValidateStatus ( validateStatus ) ) ; const finalMessage = computed ( ( ) => getFinalValidateMessage ( validateMessage ) ) ; const validateDisabled = ref ( false ) ; const fieldValue = computed ( ( ) => getValueByPath ( formCtx ?. model , props . field ) ) ; const computedDisabled = computed ( ( ) => Boolean ( props . disabled ?? formCtx ?. disabled ) ) ; const computedValidateStatus = computed ( ( ) => props . validateStatus ?? finalStatus . value ) ; const isError = computed ( ( ) => finalStatus . value === 'error' ) ; const mergedRules = computed ( ( ) => { const baseRules = ( [ ] as FieldRule [ ] ) . concat ( props . rules ?? formCtx ?. rules ?. [ props . field ] ?? [ ] ) ; const hasRequiredRule = baseRules . some ( ( item ) => item . required ) ; if ( props . required && ! hasRequiredRule ) { return ( [ { required : true } ] as FieldRule [ ] ) . concat ( baseRules ) ; } return baseRules ; } ) ; const isRequired = computed ( ( ) => mergedRules . value . some ( ( item ) => item . required ) ) ; const formItemCtx = props . noStyle ? inject ( formItemKey , undefined ) : undefined ; const updateValidateState = ( field , { status , message } ) => { validateStatus [ field ] = status ; validateMessage [ field ] = message ; if ( props . noStyle ) { formItemCtx ?. updateValidateState ( field , { status , message } ) ; } } ; provide ( formItemKey , reactive ( { updateValidateState } ) ) ; const validateField = ( ) : Promise < any > => { if ( validateDisabled . value ) { return Promise . resolve ( ) ; } const rules = mergedRules . value ; if ( ! field . value || rules . length === 0 ) { if ( finalStatus . value ) { clearValidate ( ) ; } return Promise . resolve ( ) ; } const _field = field . value ; const _value = fieldValue . value ; updateValidateState ( _field , { status : 'validating' , message : '' } ) ; const schema = new Schema ( { [ _field ] : rules . map ( ( rule ) => { if ( ! rule . type && ! rule . validator ) { rule . type = 'string' ; } return rule ; } ) } , { ignoreEmptyString : true } ) ; return new Promise ( ( resolve ) => { schema . validate ( { [ _field ] : _value } , ( err ) => { const isError = Boolean ( err ?. [ _field ] ) ; updateValidateState ( _field , { status : isError ? 'error' : 'success' , message : err ?. [ _field ] . message ?? '' } ) ; const error = isError ? { field : field . value , value : err [ _field ] . value , type : err [ _field ] . type , isRequiredError : Boolean ( err [ _field ] . requiredError ) , message : err [ _field ] . message } : undefined ; resolve ( error ) ; } ) ; } ) ; } ; const validateTriggers = computed ( ( ) => ( [ ] as ValidateTrigger [ ] ) . concat ( props . validateTrigger ) ) ; const event = computed ( ( ) => validateTriggers . value . reduce ( ( event , trigger ) => { switch ( trigger ) { case 'change' : event . onChange = ( ) => { validateField ( ) ; } ; return event ; case 'input' : event . onInput = ( ) => { nextTick ( ( ) => { validateField ( ) ; } ) ; } ; return event ; case 'focus' : event . onFocus = ( ) => { validateField ( ) ; } ; return event ; case 'blur' : event . onBlur = ( ) => { validateField ( ) ; } ; return event ; default : return event ; } } , { } as Record < string , ( ) => void > ) ) ; const clearValidate = ( ) => { if ( field . value ) { updateValidateState ( field . value , { status : '' , message : '' } ) ; } } ; const setField = ( data ) => { if ( field . value ) { validateDisabled . value = true ; if ( 'value' in data && formCtx ?. model && field . value in formCtx . model ) { formCtx . model [ field . value ] = data . value ; } if ( data . status || data . message ) { updateValidateState ( field . value , { status : data . status ?? '' , message : data . message ?? '' } ) ; } nextTick ( ( ) => { validateDisabled . value = false ; } ) ; } } ; const resetField = ( ) => { clearValidate ( ) ; validateDisabled . value = true ; if ( formCtx ?. model [ field . value ] ) { formCtx . model [ field . value ] = initialValue ; } nextTick ( ( ) => { validateDisabled . value = false ; } ) ; } ; const formItemInfo = reactive ( { field , disabled : computedDisabled , error : isError , validate : validateField , clearValidate , resetField , setField } ) ; onMounted ( ( ) => { if ( formItemInfo . field ) { formCtx ?. addField ( formItemInfo ) ; } } ) ; onBeforeUnmount ( ( ) => { if ( formItemInfo . field ) { formCtx ?. removeField ( formItemInfo ) ; } } ) ; const cls = computed ( ( ) => [ prefixCls , `${ prefixCls }-layout-${ formCtx ?. layout }` , { [ `${ prefixCls }-error` ] : isError . value , [ `${ prefixCls }-status-${ computedValidateStatus . value }` ] : Boolean ( computedValidateStatus . value ) } , props . rowClass , ] ) ; const labelColCls = computed ( ( ) => [ `${ prefixCls }-label-col` , { [ `${ prefixCls }-label-col-left` ] : formCtx ?. labelAlign === 'left' , [ `${ prefixCls }-label-col-flex` ] : formCtx ?. autoLabelWidth || props . labelColFlex } , ] ) ; const wrapperColCls = computed ( ( ) => [ `${ prefixCls }-wrapper-col` , { [ `${ prefixCls }-wrapper-col-flex` ] : ! mergedWrapperCol . value } , ] ) ; return ( ) => { const children = slots . default ?. ( ) ?? [ ] ; if ( props . mergeProps ) { mergeFirstChild ( children , ( vn ) => { const extraProps = { ... attrs , disabled : vn . props ?. disabled ?? computedDisabled . value , error : vn . props ?. error ?? isError . value , size : vn . props ?. size ?? formCtx ?. size , ... event . value } ; return isFunction ( props . mergeProps ) ? props . mergeProps ( extraProps ) : extraProps ; } ) ; } if ( props . noStyle ) { return children ; } return ( < ArcoRow class = { [ ... cls . value , { [ `${ prefixCls }-has-help` ] : Boolean ( slots . help ?? props . help ) } , ] } wrap = { ! ( props . labelColFlex || formCtx ?. autoLabelWidth ) } div = { formCtx ?. layout !== 'horizontal' || props . hideLabel } { ... props . rowProps } > { ! props . hideLabel && ( < ArcoCol class = { labelColCls . value } style = { mergedLabelStyle . value } { ... mergedLabelCol . value } > < FormItemLabel required = { props . hideAsterisk ? false : isRequired . value } showColon = { props . showColon } > { slots . label ?. ( ) ?? props . label } < /FormItemLabel> < /ArcoCol>)} < ArcoCol class = { wrapperColCls . value } style = { mergedWrapperStyle . value } { ... mergedWrapperCol . value } > < div class = { `${ prefixCls }-content-wrapper` } > < div class = { [ `${ prefixCls }-content` , { [ `${ prefixCls }-content-flex` ] : props . contentFlex } , props . contentClass , ] } > { children } < /div> < /div> { ( isError . value || props . help || slots . help ) && ( < FormItemMessage v - slots = { { help : slots . help } } error = { finalMessage . value } help = { props . help } / > ) } { ( props . extra || slots . extra ) && ( < div class = { `${ prefixCls }-extra` } > { slots . extra ?. ( ) ?? props . extra } < /div>)} < /ArcoCol> < /ArcoRow>); } ; } } ) ;