@veltra/desktop 1.1.35 → 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 (198) hide show
  1. package/dist/components/action/action-group.vue.d.ts +2 -2
  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 +5 -5
  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/dialog/use-maximum.js +7 -1
  36. package/dist/components/dialog/use-maximum.js.map +1 -1
  37. package/dist/components/empty/empty.vue.d.ts +1 -1
  38. package/dist/components/expression-editor/expression-editor.js +1 -0
  39. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  40. package/dist/components/file-picker/file-picker.js +1 -0
  41. package/dist/components/file-picker/file-picker.js.map +1 -1
  42. package/dist/components/form/form.js.map +1 -1
  43. package/dist/components/form/form.vue.d.ts +11 -19
  44. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +24 -26
  45. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  46. package/dist/components/form/index.d.ts +0 -3
  47. package/dist/components/form/use-form-fields.js +88 -0
  48. package/dist/components/form/use-form-fields.js.map +1 -0
  49. package/dist/components/form/use-node-interceptor.js +19 -30
  50. package/dist/components/form/use-node-interceptor.js.map +1 -1
  51. package/dist/components/form-item/form-item.js.map +1 -1
  52. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js +44 -18
  53. package/dist/components/form-item/form-item.vue_vue_type_script_setup_true_lang.js.map +1 -1
  54. package/dist/components/form-item/helper.js +4 -1
  55. package/dist/components/form-item/helper.js.map +1 -1
  56. package/dist/components/form-item/validate.d.ts +26 -0
  57. package/dist/components/form-item/validate.js +97 -0
  58. package/dist/components/form-item/validate.js.map +1 -0
  59. package/dist/components/group-input/group-input.js +1 -0
  60. package/dist/components/group-input/group-input.js.map +1 -1
  61. package/dist/components/index.d.ts +0 -3
  62. package/dist/components/index.js +0 -7
  63. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js +1 -0
  64. package/dist/components/input/input.vue_vue_type_script_setup_true_lang.js.map +1 -1
  65. package/dist/components/loading/style2.css +90 -159
  66. package/dist/components/multi-select/multi-select.js +1 -0
  67. package/dist/components/multi-select/multi-select.js.map +1 -1
  68. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  69. package/dist/components/multi-tree-select/multi-tree-select.js +1 -0
  70. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  71. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +4 -4
  72. package/dist/components/number-input/number-input.js +1 -0
  73. package/dist/components/number-input/number-input.js.map +1 -1
  74. package/dist/components/number-range-input/number-range-input.js +1 -0
  75. package/dist/components/number-range-input/number-range-input.js.map +1 -1
  76. package/dist/components/palette/palette.js +1 -0
  77. package/dist/components/palette/palette.js.map +1 -1
  78. package/dist/components/password-input/password-input.js +1 -0
  79. package/dist/components/password-input/password-input.js.map +1 -1
  80. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  81. package/dist/components/radio/radio.js +1 -0
  82. package/dist/components/radio/radio.js.map +1 -1
  83. package/dist/components/radio-group/radio-group.js +1 -0
  84. package/dist/components/radio-group/radio-group.js.map +1 -1
  85. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  86. package/dist/components/rich-text-editor/rich-text-editor.js +3 -2
  87. package/dist/components/rich-text-editor/rich-text-editor.js.map +1 -1
  88. package/dist/components/rich-text-editor/toolbar.js +10 -7
  89. package/dist/components/rich-text-editor/toolbar.js.map +1 -1
  90. package/dist/components/select/select.js +6 -3
  91. package/dist/components/select/select.js.map +1 -1
  92. package/dist/components/select/select.vue.d.ts +2 -2
  93. package/dist/components/slider/slider.js +15 -2
  94. package/dist/components/slider/slider.js.map +1 -1
  95. package/dist/components/steps/steps.vue.d.ts +1 -1
  96. package/dist/components/switch/switch.js +1 -0
  97. package/dist/components/switch/switch.js.map +1 -1
  98. package/dist/components/table/style2.css +4 -7
  99. package/dist/components/textarea/textarea.js +1 -0
  100. package/dist/components/textarea/textarea.js.map +1 -1
  101. package/dist/components/tip/tip.vue.d.ts +1 -1
  102. package/dist/components/tree-select/tree-select.js +1 -0
  103. package/dist/components/tree-select/tree-select.js.map +1 -1
  104. package/dist/components/tree-select/tree-select.vue.d.ts +4 -4
  105. package/dist/components/watermark/watermark.vue.d.ts +1 -1
  106. package/dist/index.d.ts +5 -6
  107. package/dist/index.js +1 -4
  108. package/dist/types/auto-complete.d.ts +3 -2
  109. package/dist/types/batch-edit.d.ts +4 -5
  110. package/dist/types/cascade.d.ts +3 -2
  111. package/dist/types/checkbox-group.d.ts +2 -2
  112. package/dist/types/checkbox.d.ts +4 -3
  113. package/dist/types/code-editor.d.ts +3 -2
  114. package/dist/types/date-picker.d.ts +3 -2
  115. package/dist/types/date-range-picker.d.ts +3 -2
  116. package/dist/types/expression-editor.d.ts +3 -2
  117. package/dist/types/file-picker.d.ts +3 -2
  118. package/dist/types/form-field.d.ts +12 -0
  119. package/dist/types/form-item.d.ts +2 -2
  120. package/dist/types/form.d.ts +9 -48
  121. package/dist/types/group-input.d.ts +3 -2
  122. package/dist/types/index.d.ts +4 -2
  123. package/dist/types/input.d.ts +3 -2
  124. package/dist/types/loading.d.ts +1 -1
  125. package/dist/types/multi-select.d.ts +3 -2
  126. package/dist/types/multi-tree-select.d.ts +3 -2
  127. package/dist/types/palette.d.ts +3 -2
  128. package/dist/types/radio-group.d.ts +3 -2
  129. package/dist/types/radio.d.ts +3 -2
  130. package/dist/types/rich-text-editor.d.ts +3 -2
  131. package/dist/types/select.d.ts +3 -2
  132. package/dist/types/slider.d.ts +3 -2
  133. package/dist/types/switch.d.ts +3 -2
  134. package/dist/types/text-editor.d.ts +3 -2
  135. package/dist/types/textarea.d.ts +3 -2
  136. package/dist/types/tree-select.d.ts +3 -2
  137. package/package.json +6 -6
  138. package/src/components/batch-edit/batch-edit-form.vue +20 -13
  139. package/src/components/batch-edit/batch-edit.vue +7 -8
  140. package/src/components/batch-edit/di.ts +1 -2
  141. package/src/components/batch-edit/use-edit.ts +58 -36
  142. package/src/components/date-range-picker/date-range-picker.vue +2 -0
  143. package/src/components/dialog/use-maximum.ts +8 -1
  144. package/src/components/form/form.vue +22 -38
  145. package/src/components/form/index.ts +0 -6
  146. package/src/components/form/use-form-fields.ts +126 -0
  147. package/src/components/form/use-node-interceptor.ts +27 -56
  148. package/src/components/form-item/form-item.vue +69 -18
  149. package/src/components/form-item/helper.ts +10 -4
  150. package/src/components/form-item/validate.ts +155 -0
  151. package/src/components/loading/style.scss +126 -355
  152. package/src/components/rich-text-editor/rich-text-editor.vue +28 -25
  153. package/src/components/rich-text-editor/toolbar.vue +23 -19
  154. package/src/components/select/select.vue +6 -4
  155. package/src/components/slider/slider.vue +12 -2
  156. package/src/components/table/style.scss +4 -14
  157. package/src/types/auto-complete.ts +4 -2
  158. package/src/types/batch-edit.ts +4 -5
  159. package/src/types/cascade.ts +4 -2
  160. package/src/types/checkbox-group.ts +2 -2
  161. package/src/types/checkbox.ts +5 -3
  162. package/src/types/code-editor.ts +4 -2
  163. package/src/types/date-picker.ts +4 -2
  164. package/src/types/date-range-picker.ts +4 -2
  165. package/src/types/expression-editor.ts +4 -2
  166. package/src/types/file-picker.ts +4 -2
  167. package/src/types/form-field.ts +11 -0
  168. package/src/types/form-item.ts +2 -2
  169. package/src/types/form.ts +9 -82
  170. package/src/types/group-input.ts +4 -2
  171. package/src/types/index.ts +1 -0
  172. package/src/types/input.ts +4 -2
  173. package/src/types/loading.ts +1 -1
  174. package/src/types/multi-select.ts +4 -2
  175. package/src/types/multi-tree-select.ts +5 -2
  176. package/src/types/palette.ts +4 -2
  177. package/src/types/radio-group.ts +4 -2
  178. package/src/types/radio.ts +4 -2
  179. package/src/types/rich-text-editor.ts +4 -2
  180. package/src/types/select.ts +4 -2
  181. package/src/types/slider.ts +4 -2
  182. package/src/types/switch.ts +4 -2
  183. package/src/types/text-editor.ts +4 -2
  184. package/src/types/textarea.ts +4 -2
  185. package/src/types/tree-select.ts +5 -2
  186. package/dist/components/form/dynamic-form-model.d.ts +0 -73
  187. package/dist/components/form/dynamic-form-model.js +0 -184
  188. package/dist/components/form/dynamic-form-model.js.map +0 -1
  189. package/dist/components/form/form-model.d.ts +0 -73
  190. package/dist/components/form/form-model.js +0 -179
  191. package/dist/components/form/form-model.js.map +0 -1
  192. package/dist/components/form/helper.d.ts +0 -16
  193. package/dist/components/form/helper.js +0 -20
  194. package/dist/components/form/helper.js.map +0 -1
  195. package/src/components/form/__test__/form-model.test.ts +0 -190
  196. package/src/components/form/dynamic-form-model.ts +0 -258
  197. package/src/components/form/form-model.ts +0 -255
  198. package/src/components/form/helper.ts +0 -23
@@ -1 +1 @@
1
- {"version":3,"file":"expression-editor.js","names":[],"sources":["../../../src/components/expression-editor/expression-editor.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <div\n ref=\"container\"\n :class=\"cls.e('container')\"\n :contenteditable=\"!readonly && !disabled\"\n :data-empty=\"isEmpty || undefined\"\n :data-placeholder=\"props.placeholder\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n ></div>\n\n <div v-if=\"showPlaceholder\" :class=\"cls.e('placeholder')\">\n {{ props.placeholder }}\n </div>\n\n <VariablePicker\n ref=\"pickerRef\"\n :visible=\"pickerVisible\"\n :trigger-dom=\"pickerTriggerDom\"\n :variables=\"props.variables\"\n :filter=\"pickerFilter\"\n :selectable-levels=\"props.selectableLevels ?? 'leaf'\"\n @select=\"onPickerSelect\"\n @dismiss=\"onPickerDismiss\"\n @update:visible=\"onPickerVisibleChange\"\n />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch\n} from 'vue'\n\nimport type { ExpressionEditorProps, VariableItem } from '../../types'\nimport VariablePicker from './components/variable-picker.vue'\nimport { createEditor, type EditorAPI } from './core/editor'\nimport { createMention, type MentionAPI } from './core/mention'\nimport { parse, type Doc } from './core/model'\nimport { createVariableMap } from './di'\n\ndefineOptions({ name: 'ExpressionEditor' })\n\nconst props = withDefaults(defineProps<ExpressionEditorProps>(), {\n placeholder: '请输入表达式,输入 @ 可插入变量',\n selectableLevels: 'leaf'\n})\n\nconst emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()\n\nconst cls = bem('expression-editor')\nconst { formProps } = injectFormContext()\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst containerRef = useTemplateRef<HTMLDivElement>('container')\nconst pickerRef = useTemplateRef<{ handleKeydown: (e: KeyboardEvent) => boolean }>('pickerRef')\n\nconst className = computed(() => [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', disabled.value),\n bem.is('readonly', readonly.value)\n])\n\nconst variableMap = computed(() => createVariableMap(props.variables))\n\nconst editorRef = shallowRef<EditorAPI | null>(null)\nconst mention: MentionAPI = createMention()\n\nconst currentDoc = shallowRef<Doc>([])\nconst isEmpty = computed(() => currentDoc.value.length === 0)\nconst showPlaceholder = computed(() => isEmpty.value && !disabled.value)\n\nconst pickerMode = shallowRef<'mention' | 'reselect' | null>(null)\nconst pickerTriggerDom = shallowRef<HTMLElement | undefined>(undefined)\nconst reselectingSegIdx = shallowRef<number | null>(null)\nconst pickerFilter = shallowRef('')\n\nconst pickerVisible = computed(() => pickerMode.value !== null)\n\nfunction syncMention() {\n const editor = editorRef.value\n if (!editor) return\n if (pickerMode.value === 'reselect') return\n const caret = editor.getCaretOffset()\n const state = mention.update(currentDoc.value, caret)\n if (state) {\n pickerMode.value = 'mention'\n pickerFilter.value = state.filter\n pickerTriggerDom.value = containerRef.value ?? undefined\n } else if (pickerMode.value === 'mention') {\n closePicker()\n }\n}\n\nfunction closePicker() {\n pickerMode.value = null\n reselectingSegIdx.value = null\n pickerFilter.value = ''\n}\n\nonMounted(() => {\n if (!containerRef.value) return\n const initialDoc = parse(props.modelValue ?? '', variableMap.value)\n currentDoc.value = initialDoc\n\n const editor = createEditor({\n container: containerRef.value,\n cls,\n initialDoc,\n getVariableMap: () => variableMap.value,\n onChange: (doc) => {\n currentDoc.value = doc\n const value = serializeFromDoc(doc)\n if (value !== (props.modelValue ?? '')) emit('update:modelValue', value)\n syncMention()\n },\n onSelectionChange: () => {\n syncMention()\n },\n onChipReselect: ({ chipEl, segIndex }) => {\n if (disabled.value || readonly.value) return\n mention.commit()\n pickerMode.value = 'reselect'\n pickerFilter.value = ''\n pickerTriggerDom.value = chipEl\n reselectingSegIdx.value = segIndex\n },\n onChipRemove: ({ segIndex }) => {\n if (disabled.value || readonly.value) return\n editor.removeVarAt(segIndex)\n }\n })\n editorRef.value = editor\n})\n\nonBeforeUnmount(() => {\n editorRef.value?.dispose()\n})\n\nfunction serializeFromDoc(doc: Doc): string {\n let s = ''\n for (const seg of doc) s += seg.kind === 'text' ? seg.value : `{${seg.value}}`\n return s\n}\n\nwatch(\n () => props.modelValue,\n (v) => {\n const editor = editorRef.value\n if (!editor) return\n if (v === editor.getValue()) return\n editor.setValue(v ?? '')\n }\n)\n\nwatch(variableMap, () => {\n // variables 变化:重渲染以刷新 chip label / type\n const editor = editorRef.value\n if (!editor) return\n editor.setValue(editor.getValue())\n})\n\nfunction onKeydown(e: KeyboardEvent) {\n if (disabled.value || readonly.value) return\n // mention / reselect 激活时,优先把箭头键 / Enter / Esc 转给 picker\n if (pickerMode.value !== null) {\n const handledByPicker = pickerRef.value?.handleKeydown(e)\n if (handledByPicker) {\n e.preventDefault()\n return\n }\n }\n\n if (pickerMode.value === 'mention') {\n // 这些键导致 mention 退出(保留 @filter 文本,光标继续移动)\n if (e.key === ' ' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n mention.dismiss()\n closePicker()\n }\n }\n}\n\nfunction onBlur() {\n if (pickerMode.value === 'mention') {\n mention.dismiss()\n // 下一帧再关闭,避免和 picker 内 click 冲突\n void nextTick(() => closePicker())\n }\n}\n\nfunction onPickerSelect(item: VariableItem) {\n const editor = editorRef.value\n if (!editor) return\n\n if (pickerMode.value === 'mention') {\n const state = mention.getState()\n if (!state) return\n const anchor = state.anchorOffset\n const caret = editor.getCaretOffset() ?? anchor + state.filter.length + 1\n editor.replaceRangeWithVar(anchor, caret, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n mention.commit()\n } else if (pickerMode.value === 'reselect') {\n const idx = reselectingSegIdx.value\n if (idx !== null) {\n editor.replaceVarAt(idx, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n }\n }\n closePicker()\n}\n\nfunction onPickerDismiss() {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n}\n\nfunction onPickerVisibleChange(v: boolean) {\n if (!v && pickerMode.value !== null) {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n }\n}\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"expression-editor.js","names":[],"sources":["../../../src/components/expression-editor/expression-editor.vue"],"sourcesContent":["<template>\n <div :class=\"className\">\n <div\n ref=\"container\"\n :class=\"cls.e('container')\"\n :contenteditable=\"!readonly && !disabled\"\n :data-empty=\"isEmpty || undefined\"\n :data-placeholder=\"props.placeholder\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n ></div>\n\n <div v-if=\"showPlaceholder\" :class=\"cls.e('placeholder')\">\n {{ props.placeholder }}\n </div>\n\n <VariablePicker\n ref=\"pickerRef\"\n :visible=\"pickerVisible\"\n :trigger-dom=\"pickerTriggerDom\"\n :variables=\"props.variables\"\n :filter=\"pickerFilter\"\n :selectable-levels=\"props.selectableLevels ?? 'leaf'\"\n @select=\"onPickerSelect\"\n @dismiss=\"onPickerDismiss\"\n @update:visible=\"onPickerVisibleChange\"\n />\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFormFallbackProps } from '@veltra/compositions'\nimport { bem } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n shallowRef,\n useTemplateRef,\n watch\n} from 'vue'\n\nimport type { ExpressionEditorProps, VariableItem } from '../../types'\nimport VariablePicker from './components/variable-picker.vue'\nimport { createEditor, type EditorAPI } from './core/editor'\nimport { createMention, type MentionAPI } from './core/mention'\nimport { parse, type Doc } from './core/model'\nimport { createVariableMap } from './di'\n\ndefineOptions({ name: 'ExpressionEditor' })\n\nconst props = withDefaults(defineProps<ExpressionEditorProps>(), {\n placeholder: '请输入表达式,输入 @ 可插入变量',\n selectableLevels: 'leaf'\n})\n\nconst emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()\n\nconst cls = bem('expression-editor')\nconst { formProps } = injectFormContext()\nconst { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])\n\nconst containerRef = useTemplateRef<HTMLDivElement>('container')\nconst pickerRef = useTemplateRef<{ handleKeydown: (e: KeyboardEvent) => boolean }>('pickerRef')\n\nconst className = computed(() => [\n cls.b,\n cls.m(size.value),\n bem.is('disabled', disabled.value),\n bem.is('readonly', readonly.value)\n])\n\nconst variableMap = computed(() => createVariableMap(props.variables))\n\nconst editorRef = shallowRef<EditorAPI | null>(null)\nconst mention: MentionAPI = createMention()\n\nconst currentDoc = shallowRef<Doc>([])\nconst isEmpty = computed(() => currentDoc.value.length === 0)\nconst showPlaceholder = computed(() => isEmpty.value && !disabled.value)\n\nconst pickerMode = shallowRef<'mention' | 'reselect' | null>(null)\nconst pickerTriggerDom = shallowRef<HTMLElement | undefined>(undefined)\nconst reselectingSegIdx = shallowRef<number | null>(null)\nconst pickerFilter = shallowRef('')\n\nconst pickerVisible = computed(() => pickerMode.value !== null)\n\nfunction syncMention() {\n const editor = editorRef.value\n if (!editor) return\n if (pickerMode.value === 'reselect') return\n const caret = editor.getCaretOffset()\n const state = mention.update(currentDoc.value, caret)\n if (state) {\n pickerMode.value = 'mention'\n pickerFilter.value = state.filter\n pickerTriggerDom.value = containerRef.value ?? undefined\n } else if (pickerMode.value === 'mention') {\n closePicker()\n }\n}\n\nfunction closePicker() {\n pickerMode.value = null\n reselectingSegIdx.value = null\n pickerFilter.value = ''\n}\n\nonMounted(() => {\n if (!containerRef.value) return\n const initialDoc = parse(props.modelValue ?? '', variableMap.value)\n currentDoc.value = initialDoc\n\n const editor = createEditor({\n container: containerRef.value,\n cls,\n initialDoc,\n getVariableMap: () => variableMap.value,\n onChange: (doc) => {\n currentDoc.value = doc\n const value = serializeFromDoc(doc)\n if (value !== (props.modelValue ?? '')) emit('update:modelValue', value)\n syncMention()\n },\n onSelectionChange: () => {\n syncMention()\n },\n onChipReselect: ({ chipEl, segIndex }) => {\n if (disabled.value || readonly.value) return\n mention.commit()\n pickerMode.value = 'reselect'\n pickerFilter.value = ''\n pickerTriggerDom.value = chipEl\n reselectingSegIdx.value = segIndex\n },\n onChipRemove: ({ segIndex }) => {\n if (disabled.value || readonly.value) return\n editor.removeVarAt(segIndex)\n }\n })\n editorRef.value = editor\n})\n\nonBeforeUnmount(() => {\n editorRef.value?.dispose()\n})\n\nfunction serializeFromDoc(doc: Doc): string {\n let s = ''\n for (const seg of doc) s += seg.kind === 'text' ? seg.value : `{${seg.value}}`\n return s\n}\n\nwatch(\n () => props.modelValue,\n (v) => {\n const editor = editorRef.value\n if (!editor) return\n if (v === editor.getValue()) return\n editor.setValue(v ?? '')\n }\n)\n\nwatch(variableMap, () => {\n // variables 变化:重渲染以刷新 chip label / type\n const editor = editorRef.value\n if (!editor) return\n editor.setValue(editor.getValue())\n})\n\nfunction onKeydown(e: KeyboardEvent) {\n if (disabled.value || readonly.value) return\n // mention / reselect 激活时,优先把箭头键 / Enter / Esc 转给 picker\n if (pickerMode.value !== null) {\n const handledByPicker = pickerRef.value?.handleKeydown(e)\n if (handledByPicker) {\n e.preventDefault()\n return\n }\n }\n\n if (pickerMode.value === 'mention') {\n // 这些键导致 mention 退出(保留 @filter 文本,光标继续移动)\n if (e.key === ' ' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n mention.dismiss()\n closePicker()\n }\n }\n}\n\nfunction onBlur() {\n if (pickerMode.value === 'mention') {\n mention.dismiss()\n // 下一帧再关闭,避免和 picker 内 click 冲突\n void nextTick(() => closePicker())\n }\n}\n\nfunction onPickerSelect(item: VariableItem) {\n const editor = editorRef.value\n if (!editor) return\n\n if (pickerMode.value === 'mention') {\n const state = mention.getState()\n if (!state) return\n const anchor = state.anchorOffset\n const caret = editor.getCaretOffset() ?? anchor + state.filter.length + 1\n editor.replaceRangeWithVar(anchor, caret, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n mention.commit()\n } else if (pickerMode.value === 'reselect') {\n const idx = reselectingSegIdx.value\n if (idx !== null) {\n editor.replaceVarAt(idx, {\n value: item.value,\n label: item.label,\n ...(item.type ? { type: item.type } : {})\n })\n }\n }\n closePicker()\n}\n\nfunction onPickerDismiss() {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n}\n\nfunction onPickerVisibleChange(v: boolean) {\n if (!v && pickerMode.value !== null) {\n if (pickerMode.value === 'mention') mention.dismiss()\n closePicker()\n }\n}\n</script>\n"],"mappings":"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"}
@@ -13,6 +13,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
13
13
  type: Boolean,
14
14
  default: false
15
15
  },
16
+ rules: {},
16
17
  tips: {},
17
18
  span: {},
18
19
  label: {},
@@ -1 +1 @@
1
- {"version":3,"file":"file-picker.js","names":[],"sources":["../../../src/components/file-picker/file-picker.vue"],"sourcesContent":["<template>\n <component\n :is=\"tag\"\n :class=\"cls.b\"\n @click.stop=\"handleClickPicker\"\n @dragover.prevent=\"handleDragover\"\n @dragleave=\"handleDragLeave\"\n @drop.prevent=\"handleDrop\"\n >\n <input\n :multiple=\"multiple\"\n :class=\"cls.e('native')\"\n type=\"file\"\n :accept=\"accept\"\n hidden\n placeholder=\"选择文件\"\n ref=\"fileRef\"\n @change=\"handleChange\"\n capture=\"environment\"\n />\n <slot :is-dragover=\"isDragover\" />\n </component>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { shallowRef, ref } from 'vue'\n\nimport type { UploaderProps, UploaderEmits } from '../../types'\nimport { matchAccept } from './helper'\n\ndefineOptions({ name: 'FilePicker' })\n\nconst props = withDefaults(defineProps<UploaderProps>(), { tag: 'div', multiple: false })\n\nconst emit = defineEmits<UploaderEmits>()\n\nconst cls = bem('file-picker')\n\nconst fileRef = shallowRef<HTMLInputElement>()\nconst isDragover = ref(false)\n\nconst processFiles = (files: File[]) => {\n const filteredFiles = files.filter((file) => matchAccept(file, props.accept))\n emit('pick', filteredFiles)\n}\n\nconst handleChange = (e: Event) => {\n const target = e.target as HTMLInputElement\n const files = Array.from(target.files || [])\n processFiles(files)\n target.value = ''\n}\n\nconst handleDragover = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = true\n}\n\nconst handleDragLeave = (e: DragEvent) => {\n if (props.disabled) return\n const currentTarget = e.currentTarget as HTMLElement\n const relatedTarget = e.relatedTarget as HTMLElement | null\n\n if (currentTarget.contains(relatedTarget)) return\n isDragover.value = false\n}\n\nconst handleDrop = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = false\n const files = Array.from(e.dataTransfer?.files || [])\n processFiles(files)\n}\n\nfunction handleClickPicker() {\n if (props.disabled) return\n fileRef.value?.click()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EAiCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,aAAa;EAE7B,MAAM,UAAU,WAA6B;EAC7C,MAAM,aAAa,IAAI,KAAK;EAE5B,MAAM,gBAAgB,UAAkB;GAEtC,KAAK,QADiB,MAAM,QAAQ,SAAS,YAAY,MAAM,MAAM,MAAM,CAC9D,CAAa;EAC5B;EAEA,MAAM,gBAAgB,MAAa;GACjC,MAAM,SAAS,EAAE;GAEjB,aADc,MAAM,KAAK,OAAO,SAAS,CAAC,CAC7B,CAAK;GAClB,OAAO,QAAQ;EACjB;EAEA,MAAM,kBAAkB,MAAiB;GACvC,IAAI,MAAM,UAAU;GACpB,WAAW,QAAQ;EACrB;EAEA,MAAM,mBAAmB,MAAiB;GACxC,IAAI,MAAM,UAAU;GACpB,MAAM,gBAAgB,EAAE;GACxB,MAAM,gBAAgB,EAAE;GAExB,IAAI,cAAc,SAAS,aAAa,GAAG;GAC3C,WAAW,QAAQ;EACrB;EAEA,MAAM,cAAc,MAAiB;GACnC,IAAI,MAAM,UAAU;GACpB,WAAW,QAAQ;GAEnB,aADc,MAAM,KAAK,EAAE,cAAc,SAAS,CAAC,CACtC,CAAK;EACpB;EAEA,SAAS,oBAAoB;GAC3B,IAAI,MAAM,UAAU;GACpB,QAAQ,OAAO,MAAM;EACvB;;uBA7EE,YAoBY,wBAnBL,QAAA,GAAG,GAAA;IACP,OAAK,eAAE,MAAA,GAAA,EAAI,CAAC;IACZ,SAAK,cAAO,mBAAiB,CAAA,MAAA,CAAA;IAC7B,YAAQ,cAAU,gBAAc,CAAA,SAAA,CAAA;IAChC,aAAW;IACX,QAAI,cAAU,YAAU,CAAA,SAAA,CAAA;;2BAYvB,CAVF,mBAUE,SAAA;KATC,UAAU,QAAA;KACV,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,QAAA,CAAA;KACb,MAAK;KACJ,QAAQ,QAAA;KACT,QAAA;KACA,aAAY;cACR;KAAJ,KAAI;KACH,UAAQ;KACT,SAAQ;8BAEV,WAAkC,KAAA,QAAA,WAAA,EAA3B,YAAa,WAAA,MAAU,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"file-picker.js","names":[],"sources":["../../../src/components/file-picker/file-picker.vue"],"sourcesContent":["<template>\n <component\n :is=\"tag\"\n :class=\"cls.b\"\n @click.stop=\"handleClickPicker\"\n @dragover.prevent=\"handleDragover\"\n @dragleave=\"handleDragLeave\"\n @drop.prevent=\"handleDrop\"\n >\n <input\n :multiple=\"multiple\"\n :class=\"cls.e('native')\"\n type=\"file\"\n :accept=\"accept\"\n hidden\n placeholder=\"选择文件\"\n ref=\"fileRef\"\n @change=\"handleChange\"\n capture=\"environment\"\n />\n <slot :is-dragover=\"isDragover\" />\n </component>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { shallowRef, ref } from 'vue'\n\nimport type { UploaderProps, UploaderEmits } from '../../types'\nimport { matchAccept } from './helper'\n\ndefineOptions({ name: 'FilePicker' })\n\nconst props = withDefaults(defineProps<UploaderProps>(), { tag: 'div', multiple: false })\n\nconst emit = defineEmits<UploaderEmits>()\n\nconst cls = bem('file-picker')\n\nconst fileRef = shallowRef<HTMLInputElement>()\nconst isDragover = ref(false)\n\nconst processFiles = (files: File[]) => {\n const filteredFiles = files.filter((file) => matchAccept(file, props.accept))\n emit('pick', filteredFiles)\n}\n\nconst handleChange = (e: Event) => {\n const target = e.target as HTMLInputElement\n const files = Array.from(target.files || [])\n processFiles(files)\n target.value = ''\n}\n\nconst handleDragover = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = true\n}\n\nconst handleDragLeave = (e: DragEvent) => {\n if (props.disabled) return\n const currentTarget = e.currentTarget as HTMLElement\n const relatedTarget = e.relatedTarget as HTMLElement | null\n\n if (currentTarget.contains(relatedTarget)) return\n isDragover.value = false\n}\n\nconst handleDrop = (e: DragEvent) => {\n if (props.disabled) return\n isDragover.value = false\n const files = Array.from(e.dataTransfer?.files || [])\n processFiles(files)\n}\n\nfunction handleClickPicker() {\n if (props.disabled) return\n fileRef.value?.click()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;EAiCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,aAAa;EAE7B,MAAM,UAAU,WAA6B;EAC7C,MAAM,aAAa,IAAI,KAAK;EAE5B,MAAM,gBAAgB,UAAkB;GAEtC,KAAK,QADiB,MAAM,QAAQ,SAAS,YAAY,MAAM,MAAM,MAAM,CAC9D,CAAa;EAC5B;EAEA,MAAM,gBAAgB,MAAa;GACjC,MAAM,SAAS,EAAE;GAEjB,aADc,MAAM,KAAK,OAAO,SAAS,CAAC,CAC7B,CAAK;GAClB,OAAO,QAAQ;EACjB;EAEA,MAAM,kBAAkB,MAAiB;GACvC,IAAI,MAAM,UAAU;GACpB,WAAW,QAAQ;EACrB;EAEA,MAAM,mBAAmB,MAAiB;GACxC,IAAI,MAAM,UAAU;GACpB,MAAM,gBAAgB,EAAE;GACxB,MAAM,gBAAgB,EAAE;GAExB,IAAI,cAAc,SAAS,aAAa,GAAG;GAC3C,WAAW,QAAQ;EACrB;EAEA,MAAM,cAAc,MAAiB;GACnC,IAAI,MAAM,UAAU;GACpB,WAAW,QAAQ;GAEnB,aADc,MAAM,KAAK,EAAE,cAAc,SAAS,CAAC,CACtC,CAAK;EACpB;EAEA,SAAS,oBAAoB;GAC3B,IAAI,MAAM,UAAU;GACpB,QAAQ,OAAO,MAAM;EACvB;;uBA7EE,YAoBY,wBAnBL,QAAA,GAAG,GAAA;IACP,OAAK,eAAE,MAAA,GAAA,EAAI,CAAC;IACZ,SAAK,cAAO,mBAAiB,CAAA,MAAA,CAAA;IAC7B,YAAQ,cAAU,gBAAc,CAAA,SAAA,CAAA;IAChC,aAAW;IACX,QAAI,cAAU,YAAU,CAAA,SAAA,CAAA;;2BAYvB,CAVF,mBAUE,SAAA;KATC,UAAU,QAAA;KACV,OAAK,eAAE,MAAA,GAAA,EAAI,EAAC,QAAA,CAAA;KACb,MAAK;KACJ,QAAQ,QAAA;KACT,QAAA;KACA,aAAY;cACR;KAAJ,KAAI;KACH,UAAQ;KACT,SAAQ;8BAEV,WAAkC,KAAA,QAAA,WAAA,EAA3B,YAAa,WAAA,MAAU,CAAA,CAAA,CAAA"}
@@ -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)]),