UNPKG

hongluan-ui

Version:
1 lines 17.4 kB
{"version":3,"file":"form-item.mjs","sources":["../../../../../../packages/components/form/src/form-item.vue"],"sourcesContent":["<template>\n <hl-row\n ref=\"formItemRef\"\n :role=\"isGroup ? 'group' : undefined\"\n :aria-labelledby=\"isGroup ? labelId : undefined\"\n :class=\"[formItemClass]\"\n :gap=\"itemGap\"\n :gap-x=\"formContext?.itemGapX\"\n :gap-y=\"formContext?.itemGapY\"\n >\n <hl-col v-if=\"label || $slots.label\" :class=\"[labelClass, 'form-label']\" :style=\"{ width: labelStyle }\">\n <component\n :is=\"labelFor ? 'label' : 'div'\"\n v-if=\"hasLabel\"\n :id=\"labelId\"\n :for=\"labelFor\"\n >\n <slot name=\"label\" :label=\"currentLabel\">{{ currentLabel }}</slot>\n </component>\n </hl-col>\n <hl-col class=\"form-content\" :class=\"[contentClass, offset]\" :style=\"contentStyle\">\n <slot></slot>\n <transition-group name=\"zoom-in-top\">\n <slot v-if=\"shouldShowError\" name=\"error\" :error=\"validateMessage\">\n <div class=\"form-content-info\">\n {{ validateMessage }}\n </div>\n </slot>\n </transition-group>\n </hl-col>\n </hl-row>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, provide, inject, ref, watch, computed, onMounted, onBeforeUnmount, toRefs, reactive, nextTick } from 'vue'\nimport AsyncValidator from 'async-validator'\nimport { clone } from 'lodash-unified'\nimport { refDebounced } from '@vueuse/core'\nimport { ensureArray, getProp, isFunction, isString } from '@hongluan-ui/utils'\nimport { useGlobalConfig, useId, useNamespace } from '@hongluan-ui/hooks'\nimport { formContextKey, formItemContextKey } from '@hongluan-ui/tokens'\nimport HlRow from '@hongluan-ui/components/row'\nimport HlCol from '@hongluan-ui/components/col'\nimport { formItemProps } from './form-item'\n\nimport type { CSSProperties } from 'vue'\nimport type { RuleItem } from 'async-validator'\nimport type { ComponentSize } from '@hongluan-ui/constants'\nimport type { Arrayable } from '@hongluan-ui/utils'\nimport type { FormItemContext, FormItemRule, FormValidateFailure } from '@hongluan-ui/tokens'\nimport type { FormItemValidateState } from './form-item'\n\nexport default defineComponent({\n name: 'FormItem',\n components: { HlRow, HlCol },\n props: formItemProps,\n setup(props, { slots }) {\n const HL = useGlobalConfig()\n const { namespace } = useNamespace('form-item')\n\n const formContext = inject(formContextKey, undefined)\n\n const labelId = useId().value\n const inputIds = ref<string[]>([])\n\n const validateState = ref<FormItemValidateState>('')\n const validateStateDebounced = refDebounced(validateState, 100)\n const validateMessage = ref('')\n\n const formItemRef = ref<{ $el: HTMLElement; }>()\n\n // special inline value.\n let initialValue: any = undefined\n let isResettingField = false\n\n const propString = computed(() => {\n if (!props.prop) return ''\n return isString(props.prop) ? props.prop : props.prop.join('.')\n })\n\n const hasLabel = computed<boolean>(() => {\n return !!(props.label || slots.label)\n })\n\n const labelFor = computed<string | undefined>(() => {\n return (\n props.for || (inputIds.value.length === 1 ? inputIds.value[0] : undefined)\n )\n })\n\n const isGroup = computed<boolean>(() => {\n return !labelFor.value && hasLabel.value\n })\n\n const labelStyle = computed(() => {\n let width = props.width || formContext?.width\n width = Array.isArray(width) ? width : [width]\n const labelWidth = width && (width[0] as string)\n return labelWidth\n })\n\n const contentStyle = computed(() => {\n let width = props.width || formContext?.width\n width = Array.isArray(width) ? width : [width]\n const contentWidth = width && (width[1] as string)\n const style = {} as CSSProperties\n if (contentWidth && !Number.isNaN(Number.parseInt(contentWidth, 10))) {\n style.width = contentWidth\n }\n return style\n })\n\n const labelClass = computed(() => {\n let width = props.width || formContext?.width\n width = Array.isArray(width) ? width : [width]\n const labelClazz = width && (width[0] as string)\n let labelPosition = props.labelPosition || formContext?.labelPosition\n let clazz = ''\n if (labelPosition) {\n labelPosition = Array.isArray(labelPosition) ? labelPosition : [labelPosition]\n clazz = labelPosition.join(' ')\n }\n if (labelClazz && Number.isNaN(Number.parseInt(labelClazz, 10))) {\n clazz += ' ' + labelClazz\n }\n return clazz\n })\n\n const contentClass = computed(() => {\n let width = props.width || formContext?.width\n width = Array.isArray(width) ? width : [width]\n const contentClazz = width && (width[1] as string)\n if (contentClazz && Number.isNaN(Number.parseInt(contentClazz, 10))) {\n return contentClazz\n }\n return ''\n })\n\n const itemGap = computed(() => {\n return props.gap || formContext?.itemGap\n })\n\n const sizeClass = computed<ComponentSize>(() => {\n return props.size || formContext?.size || HL.value.size\n })\n\n const formItemClass = computed(() => [\n {\n [namespace.value]: true,\n 'form-item-feedback': formContext?.statusIcon,\n 'is-error': validateState.value === 'error',\n 'is-validating': validateState.value === 'validating',\n 'is-success': validateState.value === 'success',\n 'is-required': isRequired.value || props.required,\n 'is-no-asterisk': formContext?.hideRequiredAsterisk,\n },\n sizeClass.value ? sizeClass.value : '',\n formContext?.requireAsteriskPosition === 'right' ? 'asterisk-right' : 'asterisk-left',\n ])\n\n const fieldValue = computed(() => {\n const model = formContext?.model\n if (!model || !props.prop) {\n return\n }\n\n return getProp(model, props.prop).value\n })\n\n const normalizedRules = computed(() => {\n const { required } = props\n\n const rules: FormItemRule[] = []\n\n if (props.rules) {\n rules.push(...ensureArray(props.rules))\n }\n\n const formRules = formContext?.rules\n if (formRules && props.prop) {\n const _rules = getProp<Arrayable<FormItemRule> | undefined>(\n formRules,\n props.prop,\n ).value\n if (_rules) {\n rules.push(...ensureArray(_rules))\n }\n }\n\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\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 return rules\n })\n\n const validateEnabled = computed(() => normalizedRules.value.length > 0)\n\n const getFilteredRule = (trigger: string) => {\n const rules = normalizedRules.value\n return (\n rules\n .filter(rule => {\n if (!rule.trigger || !trigger) return true\n if (Array.isArray(rule.trigger)) {\n return rule.trigger.includes(trigger)\n } else {\n return rule.trigger === trigger\n }\n })\n // exclude trigger\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n .map(({ trigger, ...rule }): RuleItem => rule)\n )\n }\n\n const isRequired = computed(() =>\n normalizedRules.value.some(rule => rule.required),\n )\n\n const shouldShowError = computed(() => {\n return validateStateDebounced.value === 'error' && props.showMessage && formContext.showMessage\n })\n\n const currentLabel = computed(() => (formContext?.labelPrefix || '') + (props.label || '') + (formContext?.labelSuffix || ''))\n\n const setValidationState = (state: FormItemValidateState) => {\n validateState.value = state\n }\n\n const onValidationFailed = (error: FormValidateFailure) => {\n const { errors, fields } = error\n if (!errors || !fields) {\n console.error(error)\n }\n\n setValidationState('error')\n validateMessage.value = errors\n ? errors?.[0]?.message ?? `${props.prop} is required`\n : ''\n\n formContext?.emit('validate', props.prop, false, validateMessage.value)\n }\n const onValidationSucceeded = () => {\n setValidationState('success')\n formContext?.emit('validate', props.prop, true, '')\n }\n\n const doValidate = async (rules: RuleItem[]): Promise<true> => {\n const modelName = propString.value\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 const validate: FormItemContext['validate'] = async (trigger, callback) => {\n // skip validation if its resetting\n if (isResettingField || !props.prop) {\n return false\n }\n\n const hasCallback = isFunction(callback)\n if (!validateEnabled.value) {\n callback?.(false)\n return false\n }\n\n\n const rules = getFilteredRule(trigger)\n if (rules.length === 0) {\n callback?.(true)\n return true\n }\n\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 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 const computedValue = getProp(model, props.prop)\n // prevent validation from being triggered\n isResettingField = true\n\n computedValue.value = clone(initialValue)\n\n await nextTick()\n clearValidate()\n isResettingField = false\n }\n\n const addInputId: FormItemContext['addInputId'] = (id: string) => {\n if (!inputIds.value.includes(id)) {\n inputIds.value.push(id)\n }\n }\n\n const removeInputId: FormItemContext['removeInputId'] = (id: string) => {\n inputIds.value = inputIds.value.filter(listId => listId !== id)\n }\n\n watch(\n () => props.error,\n val => {\n validateMessage.value = val || ''\n setValidationState(val ? 'error' : '')\n },\n { immediate: true },\n )\n\n watch(\n () => props.validateStatus,\n val => setValidationState(val || ''),\n )\n\n const formItemContext: FormItemContext = reactive({\n ...toRefs(props),\n size: sizeClass,\n labelId,\n inputIds,\n isGroup,\n fieldValue,\n addInputId,\n removeInputId,\n validateState,\n $el: formItemRef,\n resetField,\n clearValidate,\n validate,\n })\n\n provide(formItemContextKey, formItemContext)\n\n onMounted(() => {\n if (props.prop) {\n formContext?.addField(formItemContext)\n initialValue = clone(fieldValue.value)\n }\n })\n onBeforeUnmount(() => {\n formContext?.removeField(formItemContext)\n })\n\n return {\n formItemRef,\n formItemClass,\n validate,\n resetField,\n clearValidate,\n validateState,\n hasLabel,\n labelId,\n inputIds,\n isGroup,\n shouldShowError,\n formContext,\n labelStyle,\n contentStyle,\n labelClass,\n contentClass,\n validateMessage,\n labelFor,\n itemGap,\n currentLabel,\n }\n },\n})\n</script>\n"],"names":["ensureArray"],"mappings":"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"}