{"version":3,"file":"form-item.vue.mjs","sources":["../../../../../../packages/components/form/src/form-item.vue"],"sourcesContent":["<script lang=\"ts\">\n  import {\n    computed,\n    defineComponent,\n    provide,\n    inject,\n    reactive,\n    ref,\n    nextTick,\n    onMounted,\n    onBeforeUnmount\n  } from 'vue'\n  import type { StyleValue, CSSProperties } from 'vue'\n\n  import type { RuleItem } from 'async-validator'\n  import AsyncValidator from 'async-validator'\n  import { getComponentNamespace, getNamespace } from '../../../utils/global-config'\n\n  import { getProp, setProp, deepClone, addUnit } from '../../../shared/utils'\n  import { isString, isFunction } from '../../../utils/is'\n  import { formItemProps } from './form-item'\n  import type { FormItemValidateState } from './form-item'\n\n  import { formItemContextKey, formContextKey } from './constants'\n\n  import { registerCustomValidator } from './validators/index'\n\n  import type { FormValidateFailure, FormItemContext, FormItemRule } from './types'\n\n  export default defineComponent({\n    name: getComponentNamespace('FormItem'),\n    props: formItemProps,\n    setup(props, { slots }) {\n      const ns = getNamespace('form-item')\n\n      // 合并验证规则 form上的rules和form-item上的验证规则合并\n      const normalizedRules = computed(() => {\n        const { required } = props\n        const rules: FormItemRule[] = []\n        if (props.rules) {\n          rules.push(...(props.rules as []))\n        }\n\n        const formRules = formContext?.rules\n\n        if (formRules && props.prop) {\n          const field = props.prop as string\n          const _rules = formRules[field] as []\n          if (_rules) {\n            rules.push(..._rules)\n          }\n        }\n        // required优先取 form-item上的数据\n        // 如果form表单上rules里面有required字段 如果form-item上也有required字段 会合并required字段到rules上 required取值为form-item上的值\n        // 如果form表单上rules里面没有required字段 增加一条验证规则 required false\n        if (required !== undefined) {\n          const requiredRules = rules\n            .map((rule, i) => [rule, i] as const)\n            .filter(([rule]) => Object.keys(rule).includes('required'))\n          if (requiredRules.length > 0) {\n            for (const [rule, i] of requiredRules) {\n              if (rule.required === required) continue\n              rules[i] = { ...rule, required }\n            }\n          } else {\n            rules.push({ required })\n          }\n        }\n        // 内置正则校验器\n        registerCustomValidator(rules)\n\n        return rules\n      })\n      const validateEnabled = computed(() => normalizedRules.value.length > 0)\n\n      const getFilteredRule = (trigger: string) => {\n        const rules = normalizedRules.value\n        return rules\n          .filter((rule) => {\n            // 如果当前没有触发行为 则表明在form里面调用验证器的时候当前规则全部都要验证\n            if (!rule.trigger || !trigger) return true\n            // rule [change,..] 'change'\n            if (Array.isArray(rule.trigger)) {\n              return rule.trigger.includes(trigger)\n            } else {\n              return rule.trigger === trigger\n            }\n          })\n          .map(({ ...rule }): RuleItem => rule as RuleItem)\n      }\n      const validateState = ref<FormItemValidateState>('')\n\n      const validateMessage = ref('')\n\n      const formItemRef = ref<HTMLDivElement>()\n\n      const isRequired = computed(() => normalizedRules.value.some((rule) => rule.required))\n\n      // cls\n      const forItemCls = computed(() => [\n        ns,\n        isRequired.value && 'is-required',\n        validateState.value === 'error' && 'is-error',\n        validateState.value === 'success' && 'is-success',\n        validateState.value === 'validating' && 'is-validating'\n      ])\n\n      const contentCls = computed<StyleValue>(() => [`${ns}__content`])\n\n      const formContext = inject(formContextKey, undefined)\n      const parentFormItemContext = inject(formItemContextKey, undefined)\n\n      // 是否是嵌套表单\n      const isNested = !!parentFormItemContext\n\n      // style\n      const labelStyle = computed<CSSProperties>(() => {\n        if (formContext?.labelPosition === 'top') {\n          return {}\n        }\n        const style: CSSProperties = {\n          width: addUnit(props?.labelWidth || formContext?.labelWidth) || 0\n        }\n        return style\n      })\n      const contentStyle = computed<CSSProperties>(() => {\n        if (formContext?.labelPosition === 'top') {\n          return {}\n        }\n        if (formContext?.inline) {\n          return {}\n        }\n        // 嵌套form-item不展示label\n        if (!props.label && !props.labelWidth && isNested) {\n          return {}\n        }\n\n        const labelWidth = addUnit(props?.labelWidth || formContext?.labelWidth)\n        if (labelWidth || slots.label) {\n          return { marginLeft: labelWidth }\n        }\n        return {}\n      })\n\n      // 是否显示label\n      const hasLabel = computed<boolean>(() => {\n        return !!(props.label || slots.label)\n      })\n\n      let initialValue: any\n\n      const fieldValue = computed(() => {\n        return getProp(formContext?.model, props.prop as string, '')\n      })\n\n      const currentLabel = computed(() => props.label || '')\n\n      const setValidationState = (state: FormItemValidateState) => {\n        validateState.value = state\n      }\n\n      const shouldShowError = computed(\n        () =>\n          props.showMessage && (formContext?.showMessage ?? true) && validateState.value === 'error'\n      )\n\n      // 表单验证失败\n      const onValidationFailed = (error: FormValidateFailure) => {\n        const { errors, fields } = error\n        if (!errors || !fields) {\n          console.error(error)\n        }\n        setValidationState('error')\n        validateMessage.value = errors ? errors?.[0]?.message ?? `${props.prop}是必填项` : ''\n\n        formContext?.emit('validate', props.prop!, false, validateMessage.value)\n      }\n\n      // 表单验证成功\n      const onValidationSucceeded = () => {\n        setValidationState('success')\n        formContext?.emit('validate', props.prop!, true, '')\n      }\n\n      const propString = computed(() => {\n        if (!props.prop) return ''\n        return isString(props.prop) ? props.prop : props.prop.join('.')\n      })\n\n      // 执行验证器\n      const doValidate = async (rules: RuleItem[]): Promise<boolean> => {\n        const modelName = propString.value\n        // 创建校验对象\n        const validator = new AsyncValidator({\n          [modelName]: rules\n        })\n        return validator\n          .validate({ [modelName]: fieldValue.value }, { firstFields: true })\n          .then(() => {\n            onValidationSucceeded()\n            return true as const\n          })\n          .catch((err: FormValidateFailure) => {\n            onValidationFailed(err as FormValidateFailure)\n            return Promise.reject(err)\n          })\n      }\n\n      // 组件的验证器对象 子组件可直接调用此方法来触发验证器\n      const validate: FormItemContext['validate'] = async (trigger, callback) => {\n        if (isResettingField || !props.prop) {\n          return false\n        }\n        const hasCallback = isFunction(callback)\n        if (!validateEnabled.value) {\n          callback?.(false)\n          return false\n        }\n\n        const rules = getFilteredRule(trigger)\n\n        if (rules.length === 0) {\n          callback?.(true)\n          return true\n        }\n        // 异步验证时候这个需要加上loading\n        setValidationState('validating')\n\n        return doValidate(rules)\n          .then(() => {\n            callback?.(true)\n            return true as const\n          })\n          .catch((err: FormValidateFailure) => {\n            const { fields } = err\n            callback?.(false, fields)\n            return hasCallback ? false : Promise.reject(fields)\n          })\n      }\n\n      let isResettingField = false\n\n      const clearValidate: FormItemContext['clearValidate'] = () => {\n        setValidationState('')\n        validateMessage.value = ''\n        isResettingField = false\n      }\n\n      const resetField: FormItemContext['resetField'] = async () => {\n        const model = formContext?.model\n        if (!model || !props.prop) return\n        // 恢复初始值\n        setProp(model, props.prop, deepClone(initialValue))\n        isResettingField = true\n        await nextTick()\n        clearValidate()\n        isResettingField = false\n      }\n\n      // 向下面的input组件 注入FormItemContext 主要是验证器 validate 对象\n      const context = reactive({\n        $el: formItemRef,\n        prop: props.prop,\n        validateState,\n        validate,\n        resetField,\n        clearValidate\n      })\n      provide(formItemContextKey, context as any)\n\n      onBeforeUnmount(() => {\n        formContext?.removeField(context as any)\n      })\n\n      onMounted(() => {\n        // 将所有的form-item验证器保存到form上，方便后面在form里面直接循环验证\n        if (props.prop) {\n          formContext?.addField(context as any)\n          initialValue = deepClone(fieldValue.value)\n        }\n      })\n\n      return {\n        forItemCls,\n        contentCls,\n        currentLabel,\n        hasLabel,\n        shouldShowError,\n        labelStyle,\n        contentStyle,\n        formItemRef,\n        validateMessage,\n        validateState,\n\n        validate,\n        clearValidate,\n        resetField\n      }\n    },\n    expose: ['validateMessage', 'validateState', 'validate', 'clearValidate', 'resetField']\n  })\n</script>\n\n<template>\n  <div ref=\"formItemRef\" :class=\"forItemCls\">\n    <div v-if=\"hasLabel\" class=\"bn-form-item__label\" :style=\"labelStyle\">\n      <slot name=\"label\">\n        {{ currentLabel }}\n      </slot>\n    </div>\n\n    <div :class=\"contentCls\" :style=\"contentStyle\">\n      <slot></slot>\n      <transition name=\"form-blink\">\n        <slot v-if=\"shouldShowError\" name=\"error\" :error=\"validateMessage\">\n          <div class=\"bn-form-item__error\">\n            {{ validateMessage }}\n          </div>\n        </slot>\n      </transition>\n    </div>\n  </div>\n</template>\n"],"names":["_openBlock","_createElementBlock","hasLabel","_normalizeStyle","_createCommentVNode","contentCls","contentStyle","_renderSlot","_createVNode","shouldShowError","_withCtx","validateMessage"],"mappings":";;;;;SAgTW,YAAa,IAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,QAAA,EAAA;AAAO,EAAA,OAAAA,WAAA,EAAAC,kBAAA;AAAA,IAAY,KAAA;AAAA,IAAA;AAAA,MAAA,GAAA,EAAA,aAAA;AAAA,aAC5BC,cAAQ,CAAA,IAAA,CAAA,UAAA,CAAA;AAAA,KAAA;AAAA;MAAQ,IAAA,CAAA,QAAA,IAAAF,SAAA,EAAA,EAAAC,kBAAA;AAAA,QAAqB,KAAA;AAAA,QAAA;AAAA,UAAE,GAAK,EAAA,CAAA;AAAA,UAAA,KAAA,EAAA,qBAAA;AAAA,UACrD,KAAA,EAAAE,cAEO,CAAA,IAAA,CAFP,UAAA,CAAA;AAAA,SAAA;AAAA;;;;;;;;SAKF;AAAA,QAAA,CAAA;AAAA;AAAA,OASM,IATAC,kBAAK,CAAA,MAAA,EAAEC,IAAAA,CAAAA;AAAAA,MAAAA,kBAAAA;AAAAA,QAAkB,KAAA;AAAA,QAAEC;AAAAA,UAAAA,KAAAA,EAAAA,cAAAA,CAAAA,IAAAA,CAAAA,UAAAA,CAAAA;AAAAA,UAC/B,KAAA,EAAaH,cAAA,CAAA,IAAA,CAAA,YAAA,CAAA;AAAA,SAAA;AAAA,QACb;AAAA,UAKSI,UAAA,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,CAAA;AAAA,UAAAC,WAJKC,CAAAA,UAAAA,EAAe,EAAA,IAAA,EAAA,cAAA,EAAA;AAAA,YAAA,OAAA,EAA3BC,QAIO,MAAA;AAAA,cAAA,IAAA,CAAA,eAAA,GAAAH,UAJ2CI,CAAAA,IAAAA,CAAe,QAAA,OAAA,EAAA;AAAA,gBAI1D,GAAA,EAAA,CAAA;AAAA,gBAHL,OAAA,IAAA,CAAA,eAAA;AAAA,iBAAA,MAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;"}