91e69acb0318c586b8cf6478a81674d8fb99e73885c60156491372aa59f0a18010c51f9ceaccf5f9a99d366eaa8e2a107919873e436100580184b31cadc090 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import { defineComponent, useSlots, inject, ref, computed, watch, reactive, toRefs, provide, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, unref, createVNode, withCtx, createBlock, resolveDynamicComponent, normalizeStyle, renderSlot, createTextVNode, toDisplayString, createCommentVNode, createElementVNode, TransitionGroup, nextTick } from 'vue';
  2. import AsyncValidator from 'async-validator';
  3. import { castArray, clone } from 'lodash-unified';
  4. import { refDebounced } from '@vueuse/core';
  5. import { formItemProps } from './form-item.mjs';
  6. import FormLabelWrap from './form-label-wrap.mjs';
  7. import { formContextKey, formItemContextKey } from './constants.mjs';
  8. import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs';
  9. import { useId } from '../../../hooks/use-id/index.mjs';
  10. import { getProp } from '../../../utils/objects.mjs';
  11. import { useFormSize } from './hooks/use-form-common-props.mjs';
  12. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  13. import { addUnit } from '../../../utils/dom/style.mjs';
  14. import { isBoolean } from '../../../utils/types.mjs';
  15. import { isArray, isFunction } from '@vue/shared';
  16. const __default__ = defineComponent({
  17. name: "ElFormItem"
  18. });
  19. const _sfc_main = /* @__PURE__ */ defineComponent({
  20. ...__default__,
  21. props: formItemProps,
  22. setup(__props, { expose }) {
  23. const props = __props;
  24. const slots = useSlots();
  25. const formContext = inject(formContextKey, void 0);
  26. const parentFormItemContext = inject(formItemContextKey, void 0);
  27. const _size = useFormSize(void 0, { formItem: false });
  28. const ns = useNamespace("form-item");
  29. const labelId = useId().value;
  30. const inputIds = ref([]);
  31. const validateState = ref("");
  32. const validateStateDebounced = refDebounced(validateState, 100);
  33. const validateMessage = ref("");
  34. const formItemRef = ref();
  35. let initialValue = void 0;
  36. let isResettingField = false;
  37. const labelPosition = computed(() => props.labelPosition || (formContext == null ? void 0 : formContext.labelPosition));
  38. const labelStyle = computed(() => {
  39. if (labelPosition.value === "top") {
  40. return {};
  41. }
  42. const labelWidth = addUnit(props.labelWidth || (formContext == null ? void 0 : formContext.labelWidth) || "");
  43. if (labelWidth)
  44. return { width: labelWidth };
  45. return {};
  46. });
  47. const contentStyle = computed(() => {
  48. if (labelPosition.value === "top" || (formContext == null ? void 0 : formContext.inline)) {
  49. return {};
  50. }
  51. if (!props.label && !props.labelWidth && isNested) {
  52. return {};
  53. }
  54. const labelWidth = addUnit(props.labelWidth || (formContext == null ? void 0 : formContext.labelWidth) || "");
  55. if (!props.label && !slots.label) {
  56. return { marginLeft: labelWidth };
  57. }
  58. return {};
  59. });
  60. const formItemClasses = computed(() => [
  61. ns.b(),
  62. ns.m(_size.value),
  63. ns.is("error", validateState.value === "error"),
  64. ns.is("validating", validateState.value === "validating"),
  65. ns.is("success", validateState.value === "success"),
  66. ns.is("required", isRequired.value || props.required),
  67. ns.is("no-asterisk", formContext == null ? void 0 : formContext.hideRequiredAsterisk),
  68. (formContext == null ? void 0 : formContext.requireAsteriskPosition) === "right" ? "asterisk-right" : "asterisk-left",
  69. {
  70. [ns.m("feedback")]: formContext == null ? void 0 : formContext.statusIcon,
  71. [ns.m(`label-${labelPosition.value}`)]: labelPosition.value
  72. }
  73. ]);
  74. const _inlineMessage = computed(() => isBoolean(props.inlineMessage) ? props.inlineMessage : (formContext == null ? void 0 : formContext.inlineMessage) || false);
  75. const validateClasses = computed(() => [
  76. ns.e("error"),
  77. { [ns.em("error", "inline")]: _inlineMessage.value }
  78. ]);
  79. const propString = computed(() => {
  80. if (!props.prop)
  81. return "";
  82. return isArray(props.prop) ? props.prop.join(".") : props.prop;
  83. });
  84. const hasLabel = computed(() => {
  85. return !!(props.label || slots.label);
  86. });
  87. const labelFor = computed(() => {
  88. var _a;
  89. return (_a = props.for) != null ? _a : inputIds.value.length === 1 ? inputIds.value[0] : void 0;
  90. });
  91. const isGroup = computed(() => {
  92. return !labelFor.value && hasLabel.value;
  93. });
  94. const isNested = !!parentFormItemContext;
  95. const fieldValue = computed(() => {
  96. const model = formContext == null ? void 0 : formContext.model;
  97. if (!model || !props.prop) {
  98. return;
  99. }
  100. return getProp(model, props.prop).value;
  101. });
  102. const normalizedRules = computed(() => {
  103. const { required } = props;
  104. const rules = [];
  105. if (props.rules) {
  106. rules.push(...castArray(props.rules));
  107. }
  108. const formRules = formContext == null ? void 0 : formContext.rules;
  109. if (formRules && props.prop) {
  110. const _rules = getProp(formRules, props.prop).value;
  111. if (_rules) {
  112. rules.push(...castArray(_rules));
  113. }
  114. }
  115. if (required !== void 0) {
  116. const requiredRules = rules.map((rule, i) => [rule, i]).filter(([rule]) => "required" in rule);
  117. if (requiredRules.length > 0) {
  118. for (const [rule, i] of requiredRules) {
  119. if (rule.required === required)
  120. continue;
  121. rules[i] = { ...rule, required };
  122. }
  123. } else {
  124. rules.push({ required });
  125. }
  126. }
  127. return rules;
  128. });
  129. const validateEnabled = computed(() => normalizedRules.value.length > 0);
  130. const getFilteredRule = (trigger) => {
  131. const rules = normalizedRules.value;
  132. return rules.filter((rule) => {
  133. if (!rule.trigger || !trigger)
  134. return true;
  135. if (isArray(rule.trigger)) {
  136. return rule.trigger.includes(trigger);
  137. } else {
  138. return rule.trigger === trigger;
  139. }
  140. }).map(({ trigger: trigger2, ...rule }) => rule);
  141. };
  142. const isRequired = computed(() => normalizedRules.value.some((rule) => rule.required));
  143. const shouldShowError = computed(() => {
  144. var _a;
  145. return validateStateDebounced.value === "error" && props.showMessage && ((_a = formContext == null ? void 0 : formContext.showMessage) != null ? _a : true);
  146. });
  147. const currentLabel = computed(() => `${props.label || ""}${(formContext == null ? void 0 : formContext.labelSuffix) || ""}`);
  148. const setValidationState = (state) => {
  149. validateState.value = state;
  150. };
  151. const onValidationFailed = (error) => {
  152. var _a, _b;
  153. const { errors, fields } = error;
  154. if (!errors || !fields) {
  155. console.error(error);
  156. }
  157. setValidationState("error");
  158. validateMessage.value = errors ? (_b = (_a = errors == null ? void 0 : errors[0]) == null ? void 0 : _a.message) != null ? _b : `${props.prop} is required` : "";
  159. formContext == null ? void 0 : formContext.emit("validate", props.prop, false, validateMessage.value);
  160. };
  161. const onValidationSucceeded = () => {
  162. setValidationState("success");
  163. formContext == null ? void 0 : formContext.emit("validate", props.prop, true, "");
  164. };
  165. const doValidate = async (rules) => {
  166. const modelName = propString.value;
  167. const validator = new AsyncValidator({
  168. [modelName]: rules
  169. });
  170. return validator.validate({ [modelName]: fieldValue.value }, { firstFields: true }).then(() => {
  171. onValidationSucceeded();
  172. return true;
  173. }).catch((err) => {
  174. onValidationFailed(err);
  175. return Promise.reject(err);
  176. });
  177. };
  178. const validate = async (trigger, callback) => {
  179. if (isResettingField || !props.prop) {
  180. return false;
  181. }
  182. const hasCallback = isFunction(callback);
  183. if (!validateEnabled.value) {
  184. callback == null ? void 0 : callback(false);
  185. return false;
  186. }
  187. const rules = getFilteredRule(trigger);
  188. if (rules.length === 0) {
  189. callback == null ? void 0 : callback(true);
  190. return true;
  191. }
  192. setValidationState("validating");
  193. return doValidate(rules).then(() => {
  194. callback == null ? void 0 : callback(true);
  195. return true;
  196. }).catch((err) => {
  197. const { fields } = err;
  198. callback == null ? void 0 : callback(false, fields);
  199. return hasCallback ? false : Promise.reject(fields);
  200. });
  201. };
  202. const clearValidate = () => {
  203. setValidationState("");
  204. validateMessage.value = "";
  205. isResettingField = false;
  206. };
  207. const resetField = async () => {
  208. const model = formContext == null ? void 0 : formContext.model;
  209. if (!model || !props.prop)
  210. return;
  211. const computedValue = getProp(model, props.prop);
  212. isResettingField = true;
  213. computedValue.value = clone(initialValue);
  214. await nextTick();
  215. clearValidate();
  216. isResettingField = false;
  217. };
  218. const addInputId = (id) => {
  219. if (!inputIds.value.includes(id)) {
  220. inputIds.value.push(id);
  221. }
  222. };
  223. const removeInputId = (id) => {
  224. inputIds.value = inputIds.value.filter((listId) => listId !== id);
  225. };
  226. watch(() => props.error, (val) => {
  227. validateMessage.value = val || "";
  228. setValidationState(val ? "error" : "");
  229. }, { immediate: true });
  230. watch(() => props.validateStatus, (val) => setValidationState(val || ""));
  231. const context = reactive({
  232. ...toRefs(props),
  233. $el: formItemRef,
  234. size: _size,
  235. validateMessage,
  236. validateState,
  237. labelId,
  238. inputIds,
  239. isGroup,
  240. hasLabel,
  241. fieldValue,
  242. addInputId,
  243. removeInputId,
  244. resetField,
  245. clearValidate,
  246. validate,
  247. propString
  248. });
  249. provide(formItemContextKey, context);
  250. onMounted(() => {
  251. if (props.prop) {
  252. formContext == null ? void 0 : formContext.addField(context);
  253. initialValue = clone(fieldValue.value);
  254. }
  255. });
  256. onBeforeUnmount(() => {
  257. formContext == null ? void 0 : formContext.removeField(context);
  258. });
  259. expose({
  260. size: _size,
  261. validateMessage,
  262. validateState,
  263. validate,
  264. clearValidate,
  265. resetField
  266. });
  267. return (_ctx, _cache) => {
  268. var _a;
  269. return openBlock(), createElementBlock("div", {
  270. ref_key: "formItemRef",
  271. ref: formItemRef,
  272. class: normalizeClass(unref(formItemClasses)),
  273. role: unref(isGroup) ? "group" : void 0,
  274. "aria-labelledby": unref(isGroup) ? unref(labelId) : void 0
  275. }, [
  276. createVNode(unref(FormLabelWrap), {
  277. "is-auto-width": unref(labelStyle).width === "auto",
  278. "update-all": ((_a = unref(formContext)) == null ? void 0 : _a.labelWidth) === "auto"
  279. }, {
  280. default: withCtx(() => [
  281. unref(hasLabel) ? (openBlock(), createBlock(resolveDynamicComponent(unref(labelFor) ? "label" : "div"), {
  282. key: 0,
  283. id: unref(labelId),
  284. for: unref(labelFor),
  285. class: normalizeClass(unref(ns).e("label")),
  286. style: normalizeStyle(unref(labelStyle))
  287. }, {
  288. default: withCtx(() => [
  289. renderSlot(_ctx.$slots, "label", { label: unref(currentLabel) }, () => [
  290. createTextVNode(toDisplayString(unref(currentLabel)), 1)
  291. ])
  292. ]),
  293. _: 3
  294. }, 8, ["id", "for", "class", "style"])) : createCommentVNode("v-if", true)
  295. ]),
  296. _: 3
  297. }, 8, ["is-auto-width", "update-all"]),
  298. createElementVNode("div", {
  299. class: normalizeClass(unref(ns).e("content")),
  300. style: normalizeStyle(unref(contentStyle))
  301. }, [
  302. renderSlot(_ctx.$slots, "default"),
  303. createVNode(TransitionGroup, {
  304. name: `${unref(ns).namespace.value}-zoom-in-top`
  305. }, {
  306. default: withCtx(() => [
  307. unref(shouldShowError) ? renderSlot(_ctx.$slots, "error", {
  308. key: 0,
  309. error: validateMessage.value
  310. }, () => [
  311. createElementVNode("div", {
  312. class: normalizeClass(unref(validateClasses))
  313. }, toDisplayString(validateMessage.value), 3)
  314. ]) : createCommentVNode("v-if", true)
  315. ]),
  316. _: 3
  317. }, 8, ["name"])
  318. ], 6)
  319. ], 10, ["role", "aria-labelledby"]);
  320. };
  321. }
  322. });
  323. var FormItem = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "form-item.vue"]]);
  324. export { FormItem as default };
  325. //# sourceMappingURL=form-item2.mjs.map