@veltra/desktop 1.1.36 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/dist/components/action/action-group.vue.d.ts +1 -1
  2. package/dist/components/action/action.vue.d.ts +1 -1
  3. package/dist/components/auto-complete/auto-complete.js +1 -0
  4. package/dist/components/auto-complete/auto-complete.js.map +1 -1
  5. package/dist/components/batch-edit/batch-edit-form.js +12 -5
  6. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  7. package/dist/components/batch-edit/batch-edit.js +8 -3
  8. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  9. package/dist/components/batch-edit/batch-edit.vue.d.ts +40 -35
  10. package/dist/components/batch-edit/di.js.map +1 -1
  11. package/dist/components/batch-edit/use-edit.js +41 -23
  12. package/dist/components/batch-edit/use-edit.js.map +1 -1
  13. package/dist/components/cascade/cascade.js +1 -0
  14. package/dist/components/cascade/cascade.js.map +1 -1
  15. package/dist/components/cascade/cascade.vue.d.ts +4 -4
  16. package/dist/components/checkbox/checkbox-button.js +1 -0
  17. package/dist/components/checkbox/checkbox-button.js.map +1 -1
  18. package/dist/components/checkbox/checkbox-button.vue.d.ts +1 -1
  19. package/dist/components/checkbox/checkbox.js +1 -0
  20. package/dist/components/checkbox/checkbox.js.map +1 -1
  21. package/dist/components/checkbox-group/checkbox-group.js +1 -0
  22. package/dist/components/checkbox-group/checkbox-group.js.map +1 -1
  23. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  24. package/dist/components/code-editor/code-editor.js +1 -0
  25. package/dist/components/code-editor/code-editor.js.map +1 -1
  26. package/dist/components/condition-editor/core/evaluator.js +3 -3
  27. package/dist/components/condition-editor/core/evaluator.js.map +1 -1
  28. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  29. package/dist/components/date-picker/date-picker.js +1 -0
  30. package/dist/components/date-picker/date-picker.js.map +1 -1
  31. package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
  32. package/dist/components/date-range-picker/date-range-picker.js +2 -0
  33. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  34. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
  35. package/dist/components/empty/empty.vue.d.ts +1 -1
  36. package/dist/components/expression-editor/expression-editor.js +1 -0
  37. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  38. package/dist/components/file-picker/file-picker.js +1 -0
  39. package/dist/components/file-picker/file-picker.js.map +1 -1
  40. package/dist/components/form/form.js.map +1 -1
  41. package/dist/components/form/form.vue.d.ts +11 -19
  42. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +24 -26
  43. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  44. package/dist/components/form/index.d.ts +0 -3
  45. package/dist/components/form/use-form-fields.js +88 -0
  46. package/dist/components/form/use-form-fields.js.map +1 -0
  47. package/dist/components/form/use-node-interceptor.js +19 -30
  48. package/dist/components/form/use-node-interceptor.js.map +1 -1
  49. package/dist/components/form-item/form-item.js.map +1 -1
  50. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +44 -18
  51. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  52. package/dist/components/form-item/helper.js +4 -1
  53. package/dist/components/form-item/helper.js.map +1 -1
  54. package/dist/components/form-item/validate.d.ts +26 -0
  55. package/dist/components/form-item/validate.js +97 -0
  56. package/dist/components/form-item/validate.js.map +1 -0
  57. package/dist/components/group-input/group-input.js +1 -0
  58. package/dist/components/group-input/group-input.js.map +1 -1
  59. package/dist/components/index.d.ts +0 -3
  60. package/dist/components/index.js +0 -7
  61. package/dist/components/input/input.vue.d.ts +4 -4
  62. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js +1 -0
  63. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js.map +1 -1
  64. package/dist/components/multi-select/multi-select.js +1 -0
  65. package/dist/components/multi-select/multi-select.js.map +1 -1
  66. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  67. package/dist/components/multi-tree-select/multi-tree-select.js +1 -0
  68. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  69. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +4 -4
  70. package/dist/components/number-input/number-input.js +1 -0
  71. package/dist/components/number-input/number-input.js.map +1 -1
  72. package/dist/components/number-range-input/number-range-input.js +1 -0
  73. package/dist/components/number-range-input/number-range-input.js.map +1 -1
  74. package/dist/components/number-range-input/number-range-input.vue.d.ts +1 -1
  75. package/dist/components/palette/palette.js +1 -0
  76. package/dist/components/palette/palette.js.map +1 -1
  77. package/dist/components/password-input/password-input.js +1 -0
  78. package/dist/components/password-input/password-input.js.map +1 -1
  79. package/dist/components/radio/radio.js +1 -0
  80. package/dist/components/radio/radio.js.map +1 -1
  81. package/dist/components/radio-group/radio-group.js +1 -0
  82. package/dist/components/radio-group/radio-group.js.map +1 -1
  83. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  84. package/dist/components/rich-text-editor/rich-text-editor.js +3 -2
  85. package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -1
  86. package/dist/components/rich-text-editor/toolbar.js +10 -7
  87. package/dist/components/rich-text-editor/toolbar.js.map +1 -1
  88. package/dist/components/select/select.js +6 -3
  89. package/dist/components/select/select.js.map +1 -1
  90. package/dist/components/select/select.vue.d.ts +2 -2
  91. package/dist/components/slider/slider.js +15 -2
  92. package/dist/components/slider/slider.js.map +1 -1
  93. package/dist/components/switch/switch.js +1 -0
  94. package/dist/components/switch/switch.js.map +1 -1
  95. package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
  96. package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
  97. package/dist/components/tabs/tabs.vue.d.ts +2 -2
  98. package/dist/components/textarea/textarea.js +1 -0
  99. package/dist/components/textarea/textarea.js.map +1 -1
  100. package/dist/components/tip/tip.vue.d.ts +3 -3
  101. package/dist/components/tree-select/tree-select.js +1 -0
  102. package/dist/components/tree-select/tree-select.js.map +1 -1
  103. package/dist/components/tree-select/tree-select.vue.d.ts +4 -4
  104. package/dist/index.d.ts +5 -6
  105. package/dist/index.js +1 -4
  106. package/dist/types/auto-complete.d.ts +3 -2
  107. package/dist/types/batch-edit.d.ts +4 -5
  108. package/dist/types/cascade.d.ts +3 -2
  109. package/dist/types/checkbox-group.d.ts +2 -2
  110. package/dist/types/checkbox.d.ts +4 -3
  111. package/dist/types/code-editor.d.ts +3 -2
  112. package/dist/types/date-picker.d.ts +3 -2
  113. package/dist/types/date-range-picker.d.ts +3 -2
  114. package/dist/types/expression-editor.d.ts +3 -2
  115. package/dist/types/file-picker.d.ts +3 -2
  116. package/dist/types/form-field.d.ts +12 -0
  117. package/dist/types/form-item.d.ts +2 -2
  118. package/dist/types/form.d.ts +9 -48
  119. package/dist/types/group-input.d.ts +3 -2
  120. package/dist/types/index.d.ts +4 -2
  121. package/dist/types/input.d.ts +3 -2
  122. package/dist/types/multi-select.d.ts +3 -2
  123. package/dist/types/multi-tree-select.d.ts +3 -2
  124. package/dist/types/palette.d.ts +3 -2
  125. package/dist/types/radio-group.d.ts +3 -2
  126. package/dist/types/radio.d.ts +3 -2
  127. package/dist/types/rich-text-editor.d.ts +3 -2
  128. package/dist/types/select.d.ts +3 -2
  129. package/dist/types/slider.d.ts +3 -2
  130. package/dist/types/switch.d.ts +3 -2
  131. package/dist/types/text-editor.d.ts +3 -2
  132. package/dist/types/textarea.d.ts +3 -2
  133. package/dist/types/tree-select.d.ts +3 -2
  134. package/package.json +6 -6
  135. package/src/components/batch-edit/batch-edit-form.vue +20 -13
  136. package/src/components/batch-edit/batch-edit.vue +7 -8
  137. package/src/components/batch-edit/di.ts +1 -2
  138. package/src/components/batch-edit/use-edit.ts +58 -36
  139. package/src/components/date-range-picker/date-range-picker.vue +2 -0
  140. package/src/components/form/form.vue +22 -38
  141. package/src/components/form/index.ts +0 -6
  142. package/src/components/form/use-form-fields.ts +126 -0
  143. package/src/components/form/use-node-interceptor.ts +27 -56
  144. package/src/components/form-item/form-item.vue +69 -18
  145. package/src/components/form-item/helper.ts +10 -4
  146. package/src/components/form-item/validate.ts +155 -0
  147. package/src/components/rich-text-editor/rich-text-editor.vue +28 -25
  148. package/src/components/rich-text-editor/toolbar.vue +23 -19
  149. package/src/components/select/select.vue +6 -4
  150. package/src/components/slider/slider.vue +12 -2
  151. package/src/types/auto-complete.ts +4 -2
  152. package/src/types/batch-edit.ts +4 -5
  153. package/src/types/cascade.ts +4 -2
  154. package/src/types/checkbox-group.ts +2 -2
  155. package/src/types/checkbox.ts +5 -3
  156. package/src/types/code-editor.ts +4 -2
  157. package/src/types/date-picker.ts +4 -2
  158. package/src/types/date-range-picker.ts +4 -2
  159. package/src/types/expression-editor.ts +4 -2
  160. package/src/types/file-picker.ts +4 -2
  161. package/src/types/form-field.ts +11 -0
  162. package/src/types/form-item.ts +2 -2
  163. package/src/types/form.ts +9 -82
  164. package/src/types/group-input.ts +4 -2
  165. package/src/types/index.ts +1 -0
  166. package/src/types/input.ts +4 -2
  167. package/src/types/multi-select.ts +4 -2
  168. package/src/types/multi-tree-select.ts +5 -2
  169. package/src/types/palette.ts +4 -2
  170. package/src/types/radio-group.ts +4 -2
  171. package/src/types/radio.ts +4 -2
  172. package/src/types/rich-text-editor.ts +4 -2
  173. package/src/types/select.ts +4 -2
  174. package/src/types/slider.ts +4 -2
  175. package/src/types/switch.ts +4 -2
  176. package/src/types/text-editor.ts +4 -2
  177. package/src/types/textarea.ts +4 -2
  178. package/src/types/tree-select.ts +5 -2
  179. package/dist/components/form/dynamic-form-model.d.ts +0 -73
  180. package/dist/components/form/dynamic-form-model.js +0 -184
  181. package/dist/components/form/dynamic-form-model.js.map +0 -1
  182. package/dist/components/form/form-model.d.ts +0 -73
  183. package/dist/components/form/form-model.js +0 -179
  184. package/dist/components/form/form-model.js.map +0 -1
  185. package/dist/components/form/helper.d.ts +0 -16
  186. package/dist/components/form/helper.js +0 -20
  187. package/dist/components/form/helper.js.map +0 -1
  188. package/src/components/form/__test__/form-model.test.ts +0 -190
  189. package/src/components/form/dynamic-form-model.ts +0 -258
  190. package/src/components/form/form-model.ts +0 -255
  191. package/src/components/form/helper.ts +0 -23
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"gridRef\"\n @submit.prevent\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <component v-if=\"isFormItem || !field\" :is=\"node\" />\n\n <u-form-item v-else v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model?.data ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n\n <div :class=\"cls.e('data-before')\" v-if=\"showInitialNode(field)\">\n <i :class=\"cls.e('changed-tag')\">变更前:</i>\n\n <component :is=\"node\" readonly :model-value=\"o(model?.initialData ?? {}).get(field)\" />\n </div>\n </u-form-item>\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup generic=\"Model extends FormModel | DynamicFormModel\">\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { shallowRef, toRef } from 'vue'\n\nimport type { BreakCols, GridExposed, FormProps, _FormExposed } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport type { DynamicFormModel } from './dynamic-form-model'\nimport type { FormModel } from './form-model'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'Form' })\n\nconst props = defineProps<FormProps<Model>>()\n\ndefineSlots<{\n default(props: {\n /** 表单数据 */\n data: Model['data']\n /** 表单模型 */\n model: Model\n }): any\n}>()\n\nconst cls = bem('form')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nprovideFormContext(props)\n\nconst { getSlotsNodes } = useNodeInterceptor({ props })\n\nfunction handleUpdateValue(field: string, value: any) {\n const { model } = props\n if (!model) return\n o(model.data).set(field, value)\n}\n\nfunction showInitialNode(field: string) {\n const { data, initialData } = props.model || {}\n\n const currentValue = o(data ?? {}).get(field)\n const initialValue = o(initialData ?? {}).get(field)\n const notEqual = !(initialValue === currentValue || (!initialValue && !currentValue))\n\n return props.showInitialData && notEqual\n}\n\nconst gridRef = shallowRef<GridExposed>()\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el) })\n</script>\n"],"mappings":""}
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"gridRef\"\n @submit.prevent\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { shallowRef, toRef } from 'vue'\n\nimport type { BreakCols, GridExposed, FormProps, _FormExposed } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'Form' })\n\nconst props = defineProps<FormProps>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst { validate, clearValidate, reset, registerField, unregisterField, shouldValidate } =\n useFormFields({ props })\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nconst gridRef = shallowRef<GridExposed>()\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":""}
@@ -1,26 +1,18 @@
1
1
  import { FormProps, _FormExposed } from "../../types/form.js";
2
- import { DynamicFormModel } from "./dynamic-form-model.js";
3
- import { FormModel } from "./form-model.js";
4
-
5
2
  //#region src/components/form/form.vue.d.ts
6
- declare const __VLS_export: <Model extends FormModel | DynamicFormModel>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
7
- props: import("vue").PublicProps & __VLS_PrettifyLocal<FormProps<Model>> & (typeof globalThis extends {
8
- __VLS_PROPS_FALLBACK: infer P;
9
- } ? P : {});
10
- expose: (exposed: import("vue").ShallowUnwrapRef<_FormExposed>) => void;
11
- attrs: any;
12
- slots: {
13
- default(props: {
14
- /** 表单数据 */data: Model["data"]; /** 表单模型 */
15
- model: Model;
16
- }): any;
17
- };
18
- emit: {};
19
- }>) => import("vue").VNode & {
20
- __ctx?: Awaited<typeof __VLS_setup>;
3
+ type __VLS_Slots = {
4
+ default(props?: {
5
+ data: Record<string, any> | undefined;
6
+ }): any;
21
7
  };
8
+ declare const __VLS_base: import("vue").DefineComponent<FormProps, _FormExposed, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<FormProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
22
10
  declare const _default: typeof __VLS_export;
23
- type __VLS_PrettifyLocal<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
11
+ type __VLS_WithSlots<T, S> = T & {
12
+ new (): {
13
+ $slots: S;
14
+ };
15
+ };
24
16
  //#endregion
25
17
  export { _default };
26
18
  //# sourceMappingURL=form.vue.d.ts.map
@@ -1,7 +1,8 @@
1
1
  import _sfc_main from "../grid/grid.js";
2
2
  import form_item_default from "../form-item/form-item.js";
3
+ import { useFormFields } from "./use-form-fields.js";
3
4
  import { useNodeInterceptor } from "./use-node-interceptor.js";
4
- import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, normalizeClass, openBlock, renderList, resolveDynamicComponent, shallowRef, toRef, unref, withCtx, withModifiers } from "vue";
5
+ import { Fragment, createBlock, createElementBlock, defineComponent, mergeProps, normalizeClass, openBlock, renderList, resolveDynamicComponent, shallowRef, toRef, unref, withCtx, withModifiers } from "vue";
5
6
  import { bem, provideFormContext } from "@veltra/utils";
6
7
  import { o } from "@cat-kit/core";
7
8
  //#region src/components/form/form.vue?vue&type=script&setup=true&lang.tsx
@@ -10,7 +11,6 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
10
11
  __name: "form",
11
12
  props: {
12
13
  cols: {},
13
- showInitialData: { type: Boolean },
14
14
  model: {},
15
15
  labelWidth: {},
16
16
  noTips: { type: Boolean },
@@ -28,22 +28,26 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
28
28
  xl: 4,
29
29
  default: 4
30
30
  };
31
- provideFormContext(props);
32
- const { getSlotsNodes } = useNodeInterceptor({ props });
31
+ const { validate, clearValidate, reset, registerField, unregisterField, shouldValidate } = useFormFields({ props });
32
+ provideFormContext({
33
+ formProps: props,
34
+ registerField,
35
+ unregisterField,
36
+ validateFields: validate,
37
+ shouldValidate
38
+ });
39
+ const { getSlotsNodes } = useNodeInterceptor();
33
40
  function handleUpdateValue(field, value) {
34
- const { model } = props;
35
- if (!model) return;
36
- o(model.data).set(field, value);
37
- }
38
- function showInitialNode(field) {
39
- const { data, initialData } = props.model || {};
40
- const currentValue = o(data ?? {}).get(field);
41
- const initialValue = o(initialData ?? {}).get(field);
42
- const notEqual = !(initialValue === currentValue || !initialValue && !currentValue);
43
- return props.showInitialData && notEqual;
41
+ if (!props.model) return;
42
+ o(props.model).set(field, value);
44
43
  }
45
44
  const gridRef = shallowRef();
46
- __expose({ el: toRef(() => gridRef.value?.el) });
45
+ __expose({
46
+ el: toRef(() => gridRef.value?.el),
47
+ validate,
48
+ clearValidate,
49
+ reset
50
+ });
47
51
  return (_ctx, _cache) => {
48
52
  return openBlock(), createBlock(unref(_sfc_main), {
49
53
  tag: "form",
@@ -54,22 +58,16 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
54
58
  class: normalizeClass([unref(cls).b, unref(bem).is("readonly", __props.readonly)])
55
59
  }, {
56
60
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(getSlotsNodes)(), ({ node, isFormItem, formItemProps, field, modelValue }) => {
57
- return openBlock(), createElementBlock(Fragment, { key: node.key }, [isFormItem || !field ? (openBlock(), createBlock(resolveDynamicComponent(node), { key: 0 })) : (openBlock(), createBlock(unref(form_item_default), mergeProps({
58
- key: 1,
61
+ return openBlock(), createElementBlock(Fragment, { key: node.key }, [!isFormItem && field ? (openBlock(), createBlock(unref(form_item_default), mergeProps({
62
+ key: 0,
59
63
  ref_for: true
60
64
  }, formItemProps), {
61
65
  default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(node), {
62
- "model-value": modelValue ?? unref(o)(__props.model?.data ?? {}).get(field),
66
+ "model-value": modelValue ?? unref(o)(__props.model ?? {}).get(field),
63
67
  "onUpdate:modelValue": ($event) => handleUpdateValue(field, $event)
64
- }, null, 8, ["model-value", "onUpdate:modelValue"])), showInitialNode(field) ? (openBlock(), createElementBlock("div", {
65
- key: 0,
66
- class: normalizeClass(unref(cls).e("data-before"))
67
- }, [createElementVNode("i", { class: normalizeClass(unref(cls).e("changed-tag")) }, "变更前:", 2), (openBlock(), createBlock(resolveDynamicComponent(node), {
68
- readonly: "",
69
- "model-value": unref(o)(__props.model?.initialData ?? {}).get(field)
70
- }, null, 8, ["model-value"]))], 2)) : createCommentVNode("v-if", true)]),
68
+ }, null, 8, ["model-value", "onUpdate:modelValue"]))]),
71
69
  _: 2
72
- }, 1040))], 64);
70
+ }, 1040)) : (openBlock(), createBlock(resolveDynamicComponent(node), { key: 1 }))], 64);
73
71
  }), 128))]),
74
72
  _: 1
75
73
  }, 8, ["cols", "class"]);
@@ -1 +1 @@
1
- {"version":3,"file":"form.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"gridRef\"\n @submit.prevent\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <component v-if=\"isFormItem || !field\" :is=\"node\" />\n\n <u-form-item v-else v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model?.data ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n\n <div :class=\"cls.e('data-before')\" v-if=\"showInitialNode(field)\">\n <i :class=\"cls.e('changed-tag')\">变更前:</i>\n\n <component :is=\"node\" readonly :model-value=\"o(model?.initialData ?? {}).get(field)\" />\n </div>\n </u-form-item>\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup generic=\"Model extends FormModel | DynamicFormModel\">\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { shallowRef, toRef } from 'vue'\n\nimport type { BreakCols, GridExposed, FormProps, _FormExposed } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport type { DynamicFormModel } from './dynamic-form-model'\nimport type { FormModel } from './form-model'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'Form' })\n\nconst props = defineProps<FormProps<Model>>()\n\ndefineSlots<{\n default(props: {\n /** 表单数据 */\n data: Model['data']\n /** 表单模型 */\n model: Model\n }): any\n}>()\n\nconst cls = bem('form')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nprovideFormContext(props)\n\nconst { getSlotsNodes } = useNodeInterceptor({ props })\n\nfunction handleUpdateValue(field: string, value: any) {\n const { model } = props\n if (!model) return\n o(model.data).set(field, value)\n}\n\nfunction showInitialNode(field: string) {\n const { data, initialData } = props.model || {}\n\n const currentValue = o(data ?? {}).get(field)\n const initialValue = o(initialData ?? {}).get(field)\n const notEqual = !(initialValue === currentValue || (!initialValue && !currentValue))\n\n return props.showInitialData && notEqual\n}\n\nconst gridRef = shallowRef<GridExposed>()\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el) })\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;EA8CA,MAAM,QAAQ;EAWd,MAAM,MAAM,IAAI,MAAM;EAEtB,MAAM,iBAA4B;GAAE,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,SAAS;EAAE;EAE3E,mBAAmB,KAAK;EAExB,MAAM,EAAE,kBAAkB,mBAAmB,EAAE,MAAM,CAAC;EAEtD,SAAS,kBAAkB,OAAe,OAAY;GACpD,MAAM,EAAE,UAAU;GAClB,IAAI,CAAC,OAAO;GACZ,EAAE,MAAM,IAAI,EAAE,IAAI,OAAO,KAAK;EAChC;EAEA,SAAS,gBAAgB,OAAe;GACtC,MAAM,EAAE,MAAM,gBAAgB,MAAM,SAAS,CAAC;GAE9C,MAAM,eAAe,EAAE,QAAQ,CAAC,CAAC,EAAE,IAAI,KAAK;GAC5C,MAAM,eAAe,EAAE,eAAe,CAAC,CAAC,EAAE,IAAI,KAAK;GACnD,MAAM,WAAW,EAAE,iBAAiB,gBAAiB,CAAC,gBAAgB,CAAC;GAEvE,OAAO,MAAM,mBAAmB;EAClC;EAEA,MAAM,UAAU,WAAwB;EAExC,SAA2B,EAAE,IAAI,YAAY,QAAQ,OAAO,EAAE,EAAE,CAAC;;uBAlF/D,YA2BS,MAAA,SAAA,GAAA;IA1BP,KAAI;aACA;IAAJ,KAAI;IACH,UAAM,OAAA,OAAA,OAAA,KAAA,oBAAP,CAAA,GAAe,CAAA,SAAA,CAAA;IACd,MAAM,QAAA,QAAQ;IACd,OAAK,eAAA,CAAG,MAAA,GAAA,EAAI,GAAG,MAAA,GAAA,EAAI,GAAE,YAAa,QAAA,QAAQ,CAAA,CAAA;;2BAGwC,EAAA,UAAA,IAAA,GADnF,mBAmBW,UAAA,MAAA,WAlBwD,MAAA,aAAA,EAAa,IAAA,EAArE,MAAM,YAAY,eAAe,OAAO,iBAAU;6DACrD,KAAK,IAAA,GAAA,CAEM,cAAU,CAAK,SAAA,UAAA,GAAhC,YAAoD,wBAAR,IAAI,GAAA,EAAA,KAAA,EAAA,CAAA,MAAA,UAAA,GAEhD,YAYc,MAAA,iBAAA,GAZd,WAYc;;;QAZc,aAAa,GAAA;6BAKrC,EAAA,UAAA,GAJF,YAIE,wBAHK,IAAI,GAAA;OACR,eAAa,cAAc,MAAA,CAAA,EAAE,QAAA,OAAO,QAAI,CAAA,CAAA,EAAQ,IAAI,KAAK;OACzD,wBAAkB,WAAE,kBAAkB,OAAO,MAAM;4DAGb,gBAAgB,KAAK,KAAA,UAAA,GAA9D,mBAIM,OAAA;;OAJA,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,aAAA,CAAA;UAChB,mBAAyC,KAAA,EAArC,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,aAAA,CAAA,EAAA,GAAiB,QAAI,CAAA,IAAA,UAAA,GAErC,YAAuF,wBAAvE,IAAI,GAAA;OAAE,UAAA;OAAU,eAAa,MAAA,CAAA,EAAE,QAAA,OAAO,eAAW,CAAA,CAAA,EAAQ,IAAI,KAAK"}
1
+ {"version":3,"file":"form.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"gridRef\"\n @submit.prevent\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { shallowRef, toRef } from 'vue'\n\nimport type { BreakCols, GridExposed, FormProps, _FormExposed } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'Form' })\n\nconst props = defineProps<FormProps>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst { validate, clearValidate, reset, registerField, unregisterField, shouldValidate } =\n useFormFields({ props })\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nconst gridRef = shallowRef<GridExposed>()\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;EAuCA,MAAM,QAAQ;EAId,MAAM,MAAM,IAAI,MAAM;EAEtB,MAAM,iBAA4B;GAAE,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,SAAS;EAAE;EAE3E,MAAM,EAAE,UAAU,eAAe,OAAO,eAAe,iBAAiB,mBACtE,cAAc,EAAE,MAAM,CAAC;EAEzB,mBAAmB;GACjB,WAAW;GACX;GACA;GACA,gBAAgB;GAChB;EACF,CAAC;EAED,MAAM,EAAE,kBAAkB,mBAAmB;EAE7C,SAAS,kBAAkB,OAAe,OAAY;GACpD,IAAI,CAAC,MAAM,OAAO;GAClB,EAAE,MAAM,KAAK,EAAE,IAAI,OAAO,KAAK;EACjC;EAEA,MAAM,UAAU,WAAwB;EAExC,SAA2B;GAAE,IAAI,YAAY,QAAQ,OAAO,EAAE;GAAG;GAAU;GAAe;EAAM,CAAC;;uBAlE/F,YAqBS,MAAA,SAAA,GAAA;IApBP,KAAI;aACA;IAAJ,KAAI;IACH,UAAM,OAAA,OAAA,OAAA,KAAA,oBAAP,CAAA,GAAe,CAAA,SAAA,CAAA;IACd,MAAM,QAAA,QAAQ;IACd,OAAK,eAAA,CAAG,MAAA,GAAA,EAAI,GAAG,MAAA,GAAA,EAAI,GAAE,YAAa,QAAA,QAAQ,CAAA,CAAA;;2BAGwC,EAAA,UAAA,IAAA,GADnF,mBAaW,UAAA,MAAA,WAZwD,MAAA,aAAA,EAAa,IAAA,EAArE,MAAM,YAAY,eAAe,OAAO,iBAAU;6DACrD,KAAK,IAAA,GAAA,CAAA,CAES,cAAc,SAAA,UAAA,GAAlC,YAMc,MAAA,iBAAA,GANd,WAMc;;;QANmC,aAAa,GAAA;6BAK1D,EAAA,UAAA,GAJF,YAIE,wBAHK,IAAI,GAAA;OACR,eAAa,cAAc,MAAA,CAAA,EAAE,QAAA,SAAK,CAAA,CAAA,EAAQ,IAAI,KAAK;OACnD,wBAAkB,WAAE,kBAAkB,OAAO,MAAM;;;+BAIxD,YAA+B,wBAAR,IAAI,GAAA,EAAA,KAAA,EAAA,CAAA,EAAA,GAAA,EAAA"}
@@ -1,4 +1 @@
1
- import { NestedFieldMarker, formField, nestField } from "./helper.js";
2
- import { DynamicFormModel } from "./dynamic-form-model.js";
3
- import { FormModel } from "./form-model.js";
4
1
  import { _default } from "./form.vue.js";
@@ -0,0 +1,88 @@
1
+ import { nextTick, watch } from "vue";
2
+ import { o } from "@cat-kit/core";
3
+ //#region src/components/form/use-form-fields.ts
4
+ /** 深拷贝 model,保留 undefined 等 o().copy() 会丢弃的值 */
5
+ function snapshotModel(model) {
6
+ const result = {};
7
+ for (const key of Object.keys(model)) {
8
+ const value = model[key];
9
+ if (Array.isArray(value)) result[key] = value.map((item) => item !== null && typeof item === "object" ? snapshotModel(item) : item);
10
+ else if (value !== null && typeof value === "object") result[key] = snapshotModel(value);
11
+ else result[key] = value;
12
+ }
13
+ return result;
14
+ }
15
+ /** 按快照逐字段写回 model,支持 undefined 与嵌套对象 */
16
+ function applySnapshot(target, source) {
17
+ for (const key of Object.keys(source)) {
18
+ const sourceValue = source[key];
19
+ const targetValue = target[key];
20
+ if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) {
21
+ applySnapshot(targetValue, sourceValue);
22
+ continue;
23
+ }
24
+ if (Array.isArray(sourceValue)) {
25
+ o(target).set(key, sourceValue.map((item) => item !== null && typeof item === "object" ? snapshotModel(item) : item));
26
+ continue;
27
+ }
28
+ o(target).set(key, sourceValue);
29
+ }
30
+ }
31
+ function useFormFields(options) {
32
+ const { props } = options;
33
+ const fields = {};
34
+ let willValidate = true;
35
+ /** model 初始快照,供 reset 恢复 */
36
+ let initialSnapshot;
37
+ function runWithoutChangeValidate(fn) {
38
+ willValidate = false;
39
+ fn();
40
+ nextTick(() => {
41
+ willValidate = true;
42
+ });
43
+ }
44
+ function shouldValidate() {
45
+ return willValidate;
46
+ }
47
+ watch(() => props.model, (model) => {
48
+ initialSnapshot = model ? snapshotModel(model) : void 0;
49
+ }, {
50
+ immediate: true,
51
+ deep: false
52
+ });
53
+ function registerField(field, item) {
54
+ fields[field] = item;
55
+ }
56
+ function unregisterField(field) {
57
+ delete fields[field];
58
+ }
59
+ function clearValidate() {
60
+ Object.values(fields).forEach((item) => item.clearValidate?.());
61
+ }
62
+ async function validate(keys) {
63
+ let validList;
64
+ if (keys) validList = await Promise.all(keys.map((key) => fields[key]?.validate() ?? Promise.resolve(true)));
65
+ else validList = await Promise.all(Object.values(fields).map((field) => field.validate()));
66
+ return validList.every((valid) => valid);
67
+ }
68
+ function reset() {
69
+ if (!props.model || !initialSnapshot) return;
70
+ runWithoutChangeValidate(() => {
71
+ applySnapshot(props.model, initialSnapshot);
72
+ Object.values(fields).forEach((item) => item.clearValidate?.());
73
+ });
74
+ }
75
+ return {
76
+ fields,
77
+ registerField,
78
+ validate,
79
+ unregisterField,
80
+ clearValidate,
81
+ reset,
82
+ shouldValidate
83
+ };
84
+ }
85
+ //#endregion
86
+ export { useFormFields };
87
+
88
+ //# sourceMappingURL=use-form-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-form-fields.js","names":[],"sources":["../../../src/components/form/use-form-fields.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport type { FormFieldItem } from '@veltra/utils'\nimport { nextTick, watch } from 'vue'\n\nimport type { FormProps } from '../../types/form'\n\ninterface Options {\n props: FormProps\n}\n\n/** 深拷贝 model,保留 undefined 等 o().copy() 会丢弃的值 */\nfunction snapshotModel(model: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {}\n\n for (const key of Object.keys(model)) {\n const value = model[key]\n if (Array.isArray(value)) {\n result[key] = value.map((item) =>\n item !== null && typeof item === 'object' ? snapshotModel(item) : item\n )\n } else if (value !== null && typeof value === 'object') {\n result[key] = snapshotModel(value)\n } else {\n result[key] = value\n }\n }\n\n return result\n}\n\n/** 按快照逐字段写回 model,支持 undefined 与嵌套对象 */\nfunction applySnapshot(target: Record<string, any>, source: Record<string, any>) {\n for (const key of Object.keys(source)) {\n const sourceValue = source[key]\n const targetValue = target[key]\n\n if (\n sourceValue !== null &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue !== null &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n applySnapshot(targetValue, sourceValue)\n continue\n }\n\n if (Array.isArray(sourceValue)) {\n o(target).set(\n key,\n sourceValue.map((item) =>\n item !== null && typeof item === 'object' ? snapshotModel(item) : item\n )\n )\n continue\n }\n\n o(target).set(key, sourceValue)\n }\n}\n\nexport function useFormFields(options: Options) {\n const { props } = options\n const fields: Record<string, FormFieldItem> = {}\n\n let willValidate = true\n\n /** model 初始快照,供 reset 恢复 */\n let initialSnapshot: Record<string, any> | undefined\n\n function runWithoutChangeValidate(fn: () => void) {\n willValidate = false\n fn()\n nextTick(() => {\n willValidate = true\n })\n }\n\n function shouldValidate() {\n return willValidate\n }\n\n watch(\n () => props.model,\n (model) => {\n initialSnapshot = model ? snapshotModel(model) : undefined\n },\n { immediate: true, deep: false }\n )\n\n function registerField(field: string, item: FormFieldItem) {\n fields[field] = item\n }\n\n function unregisterField(field: string) {\n delete fields[field]\n }\n\n function clearValidate() {\n Object.values(fields).forEach((item) => item.clearValidate?.())\n }\n\n async function validate(keys?: string[]) {\n let validList: boolean[]\n if (keys) {\n validList = await Promise.all(\n keys.map((key) => fields[key]?.validate() ?? Promise.resolve(true))\n )\n } else {\n validList = await Promise.all(Object.values(fields).map((field) => field.validate()))\n }\n return validList.every((valid) => valid)\n }\n\n function reset() {\n if (!props.model || !initialSnapshot) return\n\n runWithoutChangeValidate(() => {\n applySnapshot(props.model!, initialSnapshot!)\n Object.values(fields).forEach((item) => item.clearValidate?.())\n })\n }\n\n return { fields, registerField, validate, unregisterField, clearValidate, reset, shouldValidate }\n}\n"],"mappings":";;;;AAWA,SAAS,cAAc,OAAiD;CACtE,MAAM,SAA8B,CAAC;CAErC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GAAG;EACpC,MAAM,QAAQ,MAAM;EACpB,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,OAAO,MAAM,KAAK,SACvB,SAAS,QAAQ,OAAO,SAAS,WAAW,cAAc,IAAI,IAAI,IACpE;OACK,IAAI,UAAU,QAAQ,OAAO,UAAU,UAC5C,OAAO,OAAO,cAAc,KAAK;OAEjC,OAAO,OAAO;CAElB;CAEA,OAAO;AACT;;AAGA,SAAS,cAAc,QAA6B,QAA6B;CAC/E,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG;EACrC,MAAM,cAAc,OAAO;EAC3B,MAAM,cAAc,OAAO;EAE3B,IACE,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,KAC1B,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,CAAC,MAAM,QAAQ,WAAW,GAC1B;GACA,cAAc,aAAa,WAAW;GACtC;EACF;EAEA,IAAI,MAAM,QAAQ,WAAW,GAAG;GAC9B,EAAE,MAAM,EAAE,IACR,KACA,YAAY,KAAK,SACf,SAAS,QAAQ,OAAO,SAAS,WAAW,cAAc,IAAI,IAAI,IACpE,CACF;GACA;EACF;EAEA,EAAE,MAAM,EAAE,IAAI,KAAK,WAAW;CAChC;AACF;AAEA,SAAgB,cAAc,SAAkB;CAC9C,MAAM,EAAE,UAAU;CAClB,MAAM,SAAwC,CAAC;CAE/C,IAAI,eAAe;;CAGnB,IAAI;CAEJ,SAAS,yBAAyB,IAAgB;EAChD,eAAe;EACf,GAAG;EACH,eAAe;GACb,eAAe;EACjB,CAAC;CACH;CAEA,SAAS,iBAAiB;EACxB,OAAO;CACT;CAEA,YACQ,MAAM,QACX,UAAU;EACT,kBAAkB,QAAQ,cAAc,KAAK,IAAI,KAAA;CACnD,GACA;EAAE,WAAW;EAAM,MAAM;CAAM,CACjC;CAEA,SAAS,cAAc,OAAe,MAAqB;EACzD,OAAO,SAAS;CAClB;CAEA,SAAS,gBAAgB,OAAe;EACtC,OAAO,OAAO;CAChB;CAEA,SAAS,gBAAgB;EACvB,OAAO,OAAO,MAAM,EAAE,SAAS,SAAS,KAAK,gBAAgB,CAAC;CAChE;CAEA,eAAe,SAAS,MAAiB;EACvC,IAAI;EACJ,IAAI,MACF,YAAY,MAAM,QAAQ,IACxB,KAAK,KAAK,QAAQ,OAAO,MAAM,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC,CACpE;OAEA,YAAY,MAAM,QAAQ,IAAI,OAAO,OAAO,MAAM,EAAE,KAAK,UAAU,MAAM,SAAS,CAAC,CAAC;EAEtF,OAAO,UAAU,OAAO,UAAU,KAAK;CACzC;CAEA,SAAS,QAAQ;EACf,IAAI,CAAC,MAAM,SAAS,CAAC,iBAAiB;EAEtC,+BAA+B;GAC7B,cAAc,MAAM,OAAQ,eAAgB;GAC5C,OAAO,OAAO,MAAM,EAAE,SAAS,SAAS,KAAK,gBAAgB,CAAC;EAChE,CAAC;CACH;CAEA,OAAO;EAAE;EAAQ;EAAe;EAAU;EAAiB;EAAe;EAAO;CAAe;AAClG"}
@@ -1,54 +1,43 @@
1
- import { onBeforeUnmount, useSlots } from "vue";
2
- import { createIncrease, extractNormalVNodes } from "@veltra/utils";
1
+ import { useSlots } from "vue";
2
+ import { extractNormalVNodes } from "@veltra/utils";
3
3
  import { o } from "@cat-kit/core";
4
4
  //#region src/components/form/use-node-interceptor.ts
5
- const id = createIncrease(1);
6
5
  /**
7
6
  * 虚拟node拦截
8
- * @param options 选项
9
7
  * @returns
10
8
  */
11
- function useNodeInterceptor(options) {
12
- const { props } = options;
9
+ function useNodeInterceptor() {
13
10
  const slots = useSlots();
14
- const formId = id();
15
11
  function getSlotsNodes() {
16
- const { model } = props;
17
- const nodes = slots.default?.({
18
- model,
19
- data: model?.data
20
- });
12
+ const nodes = slots.default?.();
21
13
  if (!nodes?.length) return null;
22
14
  const flattedNodes = extractNormalVNodes(nodes);
23
15
  const results = [];
24
- const fields = [];
25
16
  let i = 0;
26
17
  while (i < flattedNodes.length) {
27
18
  const node = flattedNodes[i];
28
19
  i++;
29
20
  const { props, type } = node;
30
- const item = {
31
- isFormItem: type?.name === "FormItem",
32
- formItemProps: o(props ?? {}).pick([
33
- "label",
34
- "field",
35
- "span",
36
- "tips",
37
- "readonly"
38
- ]),
21
+ const field = props?.field;
22
+ const isFormItem = type?.name === "FormItem";
23
+ const formItemProps = o(props ?? {}).pick([
24
+ "label",
25
+ "rules",
26
+ "span",
27
+ "tips",
28
+ "readonly",
29
+ "field"
30
+ ]);
31
+ results.push({
32
+ isFormItem,
33
+ formItemProps,
39
34
  node,
40
- field: props?.field,
35
+ field,
41
36
  modelValue: props?.["model-value"] ?? props?.modelValue
42
- };
43
- item.field && fields.push(item.field);
44
- results.push(item);
37
+ });
45
38
  }
46
- model.formKeys.set(formId, fields);
47
39
  return results;
48
40
  }
49
- onBeforeUnmount(() => {
50
- props.model?.formKeys.delete(formId);
51
- });
52
41
  return { getSlotsNodes };
53
42
  }
54
43
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-node-interceptor.js","names":[],"sources":["../../../src/components/form/use-node-interceptor.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { createIncrease, extractNormalVNodes } from '@veltra/utils'\nimport { onBeforeUnmount, useSlots, type VNode } from 'vue'\n\nimport type { FormProps } from '../../types'\n\ninterface Options {\n props: FormProps\n}\n\nexport type SlotRenderItem =\n | {\n isFormItem: true\n formItemProps: Record<string, any>\n node: VNode\n field: undefined\n modelValue?: any\n }\n | {\n isFormItem: false\n formItemProps: Record<string, any>\n node: VNode\n field: string\n modelValue?: any\n }\n\nconst id = createIncrease(1)\n\n/**\n * 虚拟node拦截\n * @param options 选项\n * @returns\n */\nexport function useNodeInterceptor(options: Options): {\n getSlotsNodes: () => SlotRenderItem[] | null\n} {\n const { props } = options\n const slots = useSlots()\n\n const formId = id()\n\n function getSlotsNodes() {\n const { model } = props\n const nodes = slots.default?.({ model, data: model?.data })\n if (!nodes?.length) return null\n\n const flattedNodes = extractNormalVNodes(nodes)\n\n const results: SlotRenderItem[] = []\n\n // 渲染表单后得到的应当校验的字段\n const fields: string[] = []\n\n let i = 0\n while (i < flattedNodes.length) {\n const node = flattedNodes[i]!\n i++\n\n const { props, type } = node\n const item = {\n isFormItem: (type as any)?.name === 'FormItem',\n formItemProps: o((props ?? {}) as Record<string, any>).pick([\n 'label',\n 'field',\n 'span',\n 'tips',\n 'readonly'\n ]),\n node,\n field: props?.field,\n modelValue: props?.['model-value'] ?? props?.modelValue\n }\n\n item.field && fields.push(item.field)\n results.push(item)\n }\n\n model.formKeys.set(formId, fields)\n return results\n }\n\n onBeforeUnmount(() => {\n props.model?.formKeys.delete(formId)\n })\n\n return { getSlotsNodes }\n}\n"],"mappings":";;;;AA0BA,MAAM,KAAK,eAAe,CAAC;;;;;;AAO3B,SAAgB,mBAAmB,SAEjC;CACA,MAAM,EAAE,UAAU;CAClB,MAAM,QAAQ,SAAS;CAEvB,MAAM,SAAS,GAAG;CAElB,SAAS,gBAAgB;EACvB,MAAM,EAAE,UAAU;EAClB,MAAM,QAAQ,MAAM,UAAU;GAAE;GAAO,MAAM,OAAO;EAAK,CAAC;EAC1D,IAAI,CAAC,OAAO,QAAQ,OAAO;EAE3B,MAAM,eAAe,oBAAoB,KAAK;EAE9C,MAAM,UAA4B,CAAC;EAGnC,MAAM,SAAmB,CAAC;EAE1B,IAAI,IAAI;EACR,OAAO,IAAI,aAAa,QAAQ;GAC9B,MAAM,OAAO,aAAa;GAC1B;GAEA,MAAM,EAAE,OAAO,SAAS;GACxB,MAAM,OAAO;IACX,YAAa,MAAc,SAAS;IACpC,eAAe,EAAG,SAAS,CAAC,CAAyB,EAAE,KAAK;KAC1D;KACA;KACA;KACA;KACA;IACF,CAAC;IACD;IACA,OAAO,OAAO;IACd,YAAY,QAAQ,kBAAkB,OAAO;GAC/C;GAEA,KAAK,SAAS,OAAO,KAAK,KAAK,KAAK;GACpC,QAAQ,KAAK,IAAI;EACnB;EAEA,MAAM,SAAS,IAAI,QAAQ,MAAM;EACjC,OAAO;CACT;CAEA,sBAAsB;EACpB,MAAM,OAAO,SAAS,OAAO,MAAM;CACrC,CAAC;CAED,OAAO,EAAE,cAAc;AACzB"}
1
+ {"version":3,"file":"use-node-interceptor.js","names":[],"sources":["../../../src/components/form/use-node-interceptor.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { extractNormalVNodes } from '@veltra/utils'\nimport { useSlots, type VNode } from 'vue'\n\nexport type SlotRenderItem = {\n isFormItem: boolean\n formItemProps?: Record<string, any>\n node: VNode\n field?: string\n modelValue?: any\n}\n\n/**\n * 虚拟node拦截\n * @returns\n */\nexport function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] | null } {\n const slots = useSlots()\n\n function getSlotsNodes() {\n const nodes = slots.default?.()\n if (!nodes?.length) return null\n\n const flattedNodes = extractNormalVNodes(nodes)\n\n const results: SlotRenderItem[] = []\n\n let i = 0\n while (i < flattedNodes.length) {\n const node = flattedNodes[i]!\n i++\n\n const { props, type } = node\n const field = props?.field as string | undefined\n const isFormItem = (type as any)?.name === 'FormItem'\n const formItemProps = o((props ?? {}) as Record<string, any>).pick([\n 'label',\n 'rules',\n 'span',\n 'tips',\n 'readonly',\n 'field'\n ]) as Record<string, any>\n\n results.push({\n isFormItem,\n formItemProps,\n node,\n field,\n modelValue: props?.['model-value'] ?? props?.modelValue\n })\n }\n\n return results\n }\n\n return { getSlotsNodes }\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,qBAAuE;CACrF,MAAM,QAAQ,SAAS;CAEvB,SAAS,gBAAgB;EACvB,MAAM,QAAQ,MAAM,UAAU;EAC9B,IAAI,CAAC,OAAO,QAAQ,OAAO;EAE3B,MAAM,eAAe,oBAAoB,KAAK;EAE9C,MAAM,UAA4B,CAAC;EAEnC,IAAI,IAAI;EACR,OAAO,IAAI,aAAa,QAAQ;GAC9B,MAAM,OAAO,aAAa;GAC1B;GAEA,MAAM,EAAE,OAAO,SAAS;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAc,MAAc,SAAS;GAC3C,MAAM,gBAAgB,EAAG,SAAS,CAAC,CAAyB,EAAE,KAAK;IACjE;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GAED,QAAQ,KAAK;IACX;IACA;IACA;IACA;IACA,YAAY,QAAQ,kBAAkB,OAAO;GAC/C,CAAC;EACH;EAEA,OAAO;CACT;CAEA,OAAO,EAAE,cAAc;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-item.js","names":[],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', fieldRequired)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}:</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}:</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <slot></slot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!formProps?.readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTips\">\n {{ errorTips }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { type CSSProperties, computed } from 'vue'\n\nimport type { FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls } from './helper'\n\ndefineOptions({ name: 'FormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst { formProps } = injectFormContext()\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst className = computed(() => {\n return [cls.b, cls.m(size.value), bem.is('error', !!errorTips.value)].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\n/** 错误提示 */\nconst errorTips = computed<string | undefined>(() => {\n if (!props.field) return undefined\n return formProps?.model?.errors.get(props.field)?.[0]\n})\n\n/** 字段是否必须 */\nconst fieldRequired = computed<boolean>(() => {\n const { field } = props\n if (!field || readonly.value) return false\n const required = formProps?.model?.fields[field]?.required\n return Boolean(required)\n})\n</script>\n"],"mappings":""}
1
+ {"version":3,"file":"form-item.js","names":[],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}:</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}:</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <slot></slot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { type CSSProperties, computed, onBeforeUnmount, shallowRef, watch } from 'vue'\n\nimport type { FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'FormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst { formProps, registerField, unregisterField, shouldValidate } = injectFormContext()\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst className = computed(() => {\n return [cls.b, cls.m(size.value), bem.is('error', !!errorTip.value)].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n () => {\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":""}
@@ -1,15 +1,18 @@
1
1
  import _sfc_main from "../tip/tip.js";
2
2
  import _sfc_main$1 from "../grid/grid-item.js";
3
- import { formItemCls } from "./helper.js";
4
- import { Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, renderSlot, toDisplayString, unref, withCtx } from "vue";
3
+ import { defineField, formItemCls } from "./helper.js";
4
+ import { validateField } from "./validate.js";
5
+ import { Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, renderSlot, shallowRef, toDisplayString, unref, watch, withCtx } from "vue";
5
6
  import { useConfig, useFallbackProps } from "@veltra/compositions";
6
7
  import { bem, injectFormContext, withUnit } from "@veltra/utils";
8
+ import { o } from "@cat-kit/core";
7
9
  //#region src/components/form-item/form-item.vue?vue&type=script&setup=true&lang.tsx
8
10
  var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
9
11
  name: "FormItem",
10
12
  __name: "form-item",
11
13
  props: {
12
14
  labelWidth: {},
15
+ rules: {},
13
16
  tips: {},
14
17
  span: {},
15
18
  label: {},
@@ -24,34 +27,57 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
24
27
  setup(__props) {
25
28
  const props = __props;
26
29
  /** 表单组件上下文 */
27
- const { formProps } = injectFormContext();
30
+ const { formProps, registerField, unregisterField, shouldValidate } = injectFormContext();
28
31
  const { config } = useConfig();
29
32
  const { size, readonly } = useFallbackProps([formProps ?? {}, props], {
30
33
  size: "default",
31
34
  readonly: false
32
35
  });
36
+ const errorTip = shallowRef();
37
+ /** 递增序号,丢弃过期的异步校验结果 */
38
+ let validateSeq = 0;
39
+ /** 最近一次 validate 的 Promise,供过期调用等待最新结果 */
40
+ let latestValidatePromise = Promise.resolve(true);
33
41
  const className = computed(() => {
34
42
  return [
35
43
  formItemCls.b,
36
44
  formItemCls.m(size.value),
37
- bem.is("error", !!errorTips.value)
45
+ bem.is("error", !!errorTip.value)
38
46
  ].join(" ");
39
47
  });
40
48
  /** label样式 */
41
49
  const labelStyles = computed(() => {
42
50
  return { width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, "px") };
43
51
  });
44
- /** 错误提示 */
45
- const errorTips = computed(() => {
46
- if (!props.field) return void 0;
47
- return formProps?.model?.errors.get(props.field)?.[0];
52
+ const fieldItem = defineField({
53
+ clearValidate() {
54
+ errorTip.value = "";
55
+ },
56
+ async validate() {
57
+ if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true;
58
+ const seq = ++validateSeq;
59
+ latestValidatePromise = validateField(formProps.model, props.field, props.rules).then((tip) => {
60
+ if (seq !== validateSeq) return latestValidatePromise;
61
+ errorTip.value = tip;
62
+ return !errorTip.value;
63
+ });
64
+ return latestValidatePromise;
65
+ }
48
66
  });
49
- /** 字段是否必须 */
50
- const fieldRequired = computed(() => {
51
- const { field } = props;
52
- if (!field || readonly.value) return false;
53
- const required = formProps?.model?.fields[field]?.required;
54
- return Boolean(required);
67
+ watch(() => props.field, (field, oldField) => {
68
+ if (oldField) unregisterField?.(oldField);
69
+ if (field) registerField?.(field, fieldItem);
70
+ }, { immediate: true });
71
+ let stopWatchFieldValue;
72
+ watch([() => formProps?.model, () => props.field], ([model, field]) => {
73
+ stopWatchFieldValue?.();
74
+ if (!field || !model) return;
75
+ stopWatchFieldValue = watch(() => o(model).get(field), () => {
76
+ fieldItem.validate();
77
+ });
78
+ }, { immediate: true });
79
+ onBeforeUnmount(() => {
80
+ props.field && unregisterField?.(props.field);
55
81
  });
56
82
  return (_ctx, _cache) => {
57
83
  return openBlock(), createBlock(unref(_sfc_main$1), {
@@ -60,7 +86,7 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
60
86
  }, {
61
87
  default: withCtx(() => [props.label || _ctx.$slots.label ? (openBlock(), createElementBlock("label", {
62
88
  key: 0,
63
- class: normalizeClass([unref(formItemCls).e("label"), unref(bem).is("required", fieldRequired.value)]),
89
+ class: normalizeClass([unref(formItemCls).e("label"), unref(bem).is("required", !!__props.rules?.required)]),
64
90
  style: normalizeStyle(labelStyles.value)
65
91
  }, [__props.tips ? (openBlock(), createBlock(unref(_sfc_main), {
66
92
  key: 0,
@@ -72,17 +98,17 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
72
98
  }, 8, ["content", "class"])) : renderSlot(_ctx.$slots, "label", { key: 1 }, () => [createTextVNode(toDisplayString(__props.label) + ":", 1)])], 6)) : createCommentVNode("v-if", true), createElementVNode("section", { class: normalizeClass(unref(formItemCls).e("wrapper")) }, [
73
99
  createElementVNode("div", { class: normalizeClass(unref(formItemCls).e("content")) }, [renderSlot(_ctx.$slots, "default")], 2),
74
100
  createCommentVNode(" 只有表单控件处于非只读状态时,才显示错误提示 "),
75
- !unref(formProps)?.readonly && !unref(formProps)?.noTips ? (openBlock(), createElementBlock("section", {
101
+ !unref(readonly) && !unref(formProps)?.noTips ? (openBlock(), createElementBlock("section", {
76
102
  key: 0,
77
103
  class: normalizeClass(unref(formItemCls).e("error"))
78
104
  }, [createVNode(Transition, {
79
105
  name: "form-item-tips",
80
106
  mode: "out-in"
81
107
  }, {
82
- default: withCtx(() => [!!errorTips.value ? (openBlock(), createElementBlock("span", {
108
+ default: withCtx(() => [!!errorTip.value ? (openBlock(), createElementBlock("span", {
83
109
  key: 0,
84
110
  class: normalizeClass(unref(formItemCls).e("error-text"))
85
- }, toDisplayString(errorTips.value), 3)) : createCommentVNode("v-if", true)]),
111
+ }, toDisplayString(errorTip.value), 3)) : createCommentVNode("v-if", true)]),
86
112
  _: 1
87
113
  })], 2)) : createCommentVNode("v-if", true)
88
114
  ], 2)]),
@@ -1 +1 @@
1
- {"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', fieldRequired)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}:</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}:</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <slot></slot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!formProps?.readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTips\">\n {{ errorTips }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { type CSSProperties, computed } from 'vue'\n\nimport type { FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls } from './helper'\n\ndefineOptions({ name: 'FormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst { formProps } = injectFormContext()\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst className = computed(() => {\n return [cls.b, cls.m(size.value), bem.is('error', !!errorTips.value)].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\n/** 错误提示 */\nconst errorTips = computed<string | undefined>(() => {\n if (!props.field) return undefined\n return formProps?.model?.errors.get(props.field)?.[0]\n})\n\n/** 字段是否必须 */\nconst fieldRequired = computed<boolean>(() => {\n const { field } = props\n if (!field || readonly.value) return false\n const required = formProps?.model?.fields[field]?.required\n return Boolean(required)\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EA+CA,MAAM,QAAQ;;EASd,MAAM,EAAE,cAAc,kBAAkB;EAExC,MAAM,EAAE,WAAW,UAAU;EAE7B,MAAM,EAAE,MAAM,aAAa,iBAAiB,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG;GACpE,MAAM;GACN,UAAU;EACZ,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,OAAO;IAAC,YAAI;IAAG,YAAI,EAAE,KAAK,KAAK;IAAG,IAAI,GAAG,SAAS,CAAC,CAAC,UAAU,KAAK;GAAC,EAAE,KAAK,GAAG;EAChF,CAAC;;EAGD,MAAM,cAAc,eAA8B;GAChD,OAAO,EACL,OAAO,SAAS,MAAM,cAAc,WAAW,cAAc,OAAO,KAAK,YAAY,IAAI,EAC3F;EACF,CAAC;;EAGD,MAAM,YAAY,eAAmC;GACnD,IAAI,CAAC,MAAM,OAAO,OAAO,KAAA;GACzB,OAAO,WAAW,OAAO,OAAO,IAAI,MAAM,KAAK,IAAI;EACrD,CAAC;;EAGD,MAAM,gBAAgB,eAAwB;GAC5C,MAAM,EAAE,UAAU;GAClB,IAAI,CAAC,SAAS,SAAS,OAAO,OAAO;GACrC,MAAM,WAAW,WAAW,OAAO,OAAO,QAAQ;GAClD,OAAO,QAAQ,QAAQ;EACzB,CAAC;;uBAvFC,YA8Bc,MAAA,WAAA,GAAA;IA9BA,MAAM,QAAA;IAAO,OAAK,eAAE,UAAA,KAAS;;2BAcjC,CAZA,MAAM,SAASA,KAAAA,OAAO,SAAA,UAAA,GAD9B,mBAaQ,SAAA;;KAXL,OAAK,eAAA,CAAG,MAAA,WAAA,EAAI,EAAC,OAAA,GAAW,MAAA,GAAA,EAAI,GAAE,YAAa,cAAA,KAAa,CAAA,CAAA;KACxD,OAAK,eAAE,YAAA,KAAW;QAEN,QAAA,QAAA,UAAA,GAAb,YAIQ,MAAA,SAAA,GAAA;;KAJY,SAAS,QAAA;KAAO,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,MAAA,CAAA;;4BAGvC,CAFP,mBAEO,QAAA,EAFA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,WAAA,CAAA,EAAA,GAAA,CACjB,WAAsC,KAAA,QAAA,SAAA,CAAA,SAAA,CAAA,gBAAA,gBAAhB,QAAA,KAAK,IAAG,KAAC,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;mCAIjC,WAAsC,KAAA,QAAA,SAAA,EAAA,KAAA,EAAA,SAAA,CAAA,gBAAA,gBAAhB,QAAA,KAAK,IAAG,KAAC,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,GAInC,mBAaU,WAAA,EAbA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,SAAA,CAAA,EAAA,GAAA;KACpB,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,SAAA,CAAA,EAAA,GAAA,CAChB,WAAa,KAAA,QAAA,SAAA,CAAA,GAAA,CAAA;KAGf,mBAAA,0BAAA;MACwC,MAAA,SAAA,GAAW,YAAQ,CAAK,MAAA,SAAA,GAAW,UAAA,UAAA,GAA3E,mBAMU,WAAA;;MANA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,OAAA,CAAA;SACpB,YAIa,YAAA;MAJD,MAAK;MAAiB,MAAK;;6BAG9B,CAAA,CAAA,CAFoC,UAAA,SAAA,UAAA,GAA3C,mBAEO,QAAA;;OAFA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,YAAA,CAAA;yBACd,UAAA,KAAS,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}:</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}:</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <slot></slot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport { type CSSProperties, computed, onBeforeUnmount, shallowRef, watch } from 'vue'\n\nimport type { FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'FormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst { formProps, registerField, unregisterField, shouldValidate } = injectFormContext()\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst className = computed(() => {\n return [cls.b, cls.m(size.value), bem.is('error', !!errorTip.value)].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n () => {\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,MAAM,QAAQ;;EASd,MAAM,EAAE,WAAW,eAAe,iBAAiB,mBAAmB,kBAAkB;EAExF,MAAM,EAAE,WAAW,UAAU;EAE7B,MAAM,EAAE,MAAM,aAAa,iBAAiB,CAAC,aAAa,CAAC,GAAG,KAAK,GAAG;GACpE,MAAM;GACN,UAAU;EACZ,CAAC;EAED,MAAM,WAAW,WAAmB;;EAEpC,IAAI,cAAc;;EAElB,IAAI,wBAA0C,QAAQ,QAAQ,IAAI;EAElE,MAAM,YAAY,eAAe;GAC/B,OAAO;IAAC,YAAI;IAAG,YAAI,EAAE,KAAK,KAAK;IAAG,IAAI,GAAG,SAAS,CAAC,CAAC,SAAS,KAAK;GAAC,EAAE,KAAK,GAAG;EAC/E,CAAC;;EAGD,MAAM,cAAc,eAA8B;GAChD,OAAO,EACL,OAAO,SAAS,MAAM,cAAc,WAAW,cAAc,OAAO,KAAK,YAAY,IAAI,EAC3F;EACF,CAAC;EAED,MAAM,YAAY,YAAY;GAC5B,gBAAgB;IACd,SAAS,QAAQ;GACnB;GACA,MAAM,WAAW;IACf,IAAI,CAAC,MAAM,SAAS,CAAC,WAAW,SAAS,CAAC,MAAM,SAAS,CAAC,iBAAiB,GAAG,OAAO;IAErF,MAAM,MAAM,EAAE;IAEd,wBAAwB,cAAc,UAAU,OAAQ,MAAM,OAAQ,MAAM,KAAM,EAAE,MACjF,QAAQ;KACP,IAAI,QAAQ,aAAa,OAAO;KAEhC,SAAS,QAAQ;KACjB,OAAO,CAAC,SAAS;IACnB,CACF;IACA,OAAO;GACT;EACF,CAAC;EAED,YACQ,MAAM,QACX,OAAO,aAAa;GACnB,IAAI,UACF,kBAAkB,QAAQ;GAE5B,IAAI,OACF,gBAAgB,OAAO,SAAS;EAEpC,GACA,EAAE,WAAW,KAAK,CACpB;EAEA,IAAI;EAEJ,MACE,OAAO,WAAW,aAAa,MAAM,KAAK,IACzC,CAAC,OAAO,WAAW;GAClB,sBAAsB;GAEtB,IAAI,CAAC,SAAS,CAAC,OAAO;GAEtB,sBAAsB,YACd,EAAE,KAAK,EAAE,IAAI,KAAK,SAClB;IACJ,UAAU,SAAS;GACrB,CACF;EACF,GACA,EAAE,WAAW,KAAK,CACpB;EAEA,sBAAsB;GACpB,MAAM,SAAS,kBAAkB,MAAM,KAAK;EAC9C,CAAC;;uBA1IC,YA8Bc,MAAA,WAAA,GAAA;IA9BA,MAAM,QAAA;IAAO,OAAK,eAAE,UAAA,KAAS;;2BAcjC,CAZA,MAAM,SAASA,KAAAA,OAAO,SAAA,UAAA,GAD9B,mBAaQ,SAAA;;KAXL,OAAK,eAAA,CAAG,MAAA,WAAA,EAAI,EAAC,OAAA,GAAW,MAAA,GAAA,EAAI,GAAE,YAAA,CAAA,CAAe,QAAA,OAAO,QAAQ,CAAA,CAAA;KAC5D,OAAK,eAAE,YAAA,KAAW;QAEN,QAAA,QAAA,UAAA,GAAb,YAIQ,MAAA,SAAA,GAAA;;KAJY,SAAS,QAAA;KAAO,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,MAAA,CAAA;;4BAGvC,CAFP,mBAEO,QAAA,EAFA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,WAAA,CAAA,EAAA,GAAA,CACjB,WAAsC,KAAA,QAAA,SAAA,CAAA,SAAA,CAAA,gBAAA,gBAAhB,QAAA,KAAK,IAAG,KAAC,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,CAAA,CAAA;;mCAIjC,WAAsC,KAAA,QAAA,SAAA,EAAA,KAAA,EAAA,SAAA,CAAA,gBAAA,gBAAhB,QAAA,KAAK,IAAG,KAAC,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,GAInC,mBAaU,WAAA,EAbA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,SAAA,CAAA,EAAA,GAAA;KACpB,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,SAAA,CAAA,EAAA,GAAA,CAChB,WAAa,KAAA,QAAA,SAAA,CAAA,GAAA,CAAA;KAGf,mBAAA,0BAAA;MACwC,MAAA,QAAA,KAAQ,CAAK,MAAA,SAAA,GAAW,UAAA,UAAA,GAAhE,mBAMU,WAAA;;MANA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,OAAA,CAAA;SACpB,YAIa,YAAA;MAJD,MAAK;MAAiB,MAAK;;6BAG9B,CAAA,CAAA,CAFoC,SAAA,SAAA,UAAA,GAA3C,mBAEO,QAAA;;OAFA,OAAK,eAAE,MAAA,WAAA,EAAI,EAAC,YAAA,CAAA;yBACd,SAAA,KAAQ,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,CAAA"}
@@ -2,7 +2,10 @@ import { bem } from "@veltra/utils";
2
2
  import "@cat-kit/core";
3
3
  //#region src/components/form-item/helper.ts
4
4
  const formItemCls = bem("form-item");
5
+ function defineField(field) {
6
+ return field;
7
+ }
5
8
  //#endregion
6
- export { formItemCls };
9
+ export { defineField, formItemCls };
7
10
 
8
11
  //# sourceMappingURL=helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","names":[],"sources":["../../../src/components/form-item/helper.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { bem, type BEM, type FormComponentProps } from '@veltra/utils'\n\n/**\n * 获取表单项的属性\n * @param props 表单系列组件的属性\n * @returns\n */\nexport function getFormItemProps(\n props: FormComponentProps\n): Pick<FormComponentProps, 'label' | 'field' | 'readonly' | 'span' | 'tips'> {\n return o(props).pick(['label', 'field', 'readonly', 'span', 'tips']) as Pick<\n FormComponentProps,\n 'label' | 'field' | 'readonly' | 'span' | 'tips'\n >\n}\n\nexport const formItemCls: BEM<'form-item'> = bem('form-item')\n"],"mappings":";;;AAiBA,MAAa,cAAgC,IAAI,WAAW"}
1
+ {"version":3,"file":"helper.js","names":[],"sources":["../../../src/components/form-item/helper.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { bem, type BEM, type FormFieldItem } from '@veltra/utils'\n\nimport type { FormFieldComponentProps } from '../../types/form-field'\n\n/**\n * 获取表单项的属性\n * @param props 表单系列组件的属性\n * @returns\n */\nexport function getFormItemProps(\n props: FormFieldComponentProps\n): Pick<FormFieldComponentProps, 'label' | 'field' | 'readonly' | 'span' | 'tips'> {\n return o(props).pick(['label', 'field', 'readonly', 'span', 'tips']) as Pick<\n FormFieldComponentProps,\n 'label' | 'field' | 'readonly' | 'span' | 'tips'\n >\n}\n\nexport const formItemCls: BEM<'form-item'> = bem('form-item')\n\nexport function defineField(field: FormFieldItem) {\n return field\n}\n"],"mappings":";;;AAmBA,MAAa,cAAgC,IAAI,WAAW;AAE5D,SAAgB,YAAY,OAAsB;CAChD,OAAO;AACT"}
@@ -0,0 +1,26 @@
1
+ //#region src/components/form-item/validate.d.ts
2
+ type PresetRule = 'email' | 'phone' | 'num' | 'url' | 'idCard';
3
+ /** 字段校验规则 */
4
+ interface ValidateRule {
5
+ /** 是否必填 */
6
+ required?: boolean | string;
7
+ /** 长度单位 */
8
+ length?: number | [number, string];
9
+ /** 最小值 */
10
+ min?: number | [number, string];
11
+ /** 最大值 */
12
+ max?: number | [number, string];
13
+ /** 最小长度 */
14
+ minLen?: number | [number, string];
15
+ /** 最大长度 */
16
+ maxLen?: number | [number, string];
17
+ /** 匹配 */
18
+ match?: RegExp | [RegExp, string] | string;
19
+ /** 预设 */
20
+ preset?: PresetRule;
21
+ /** 自定义校验 */
22
+ validator?: (value: any, data: Record<string, any>) => Promise<string> | string;
23
+ }
24
+ //#endregion
25
+ export { PresetRule, ValidateRule };
26
+ //# sourceMappingURL=validate.d.ts.map