@tmagic/form 1.8.0-manmanyu.8 → 1.8.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 (149) hide show
  1. package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +88 -107
  2. package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +21 -5
  3. package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +21 -5
  4. package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +46 -12
  5. package/dist/es/containers/Col.vue_vue_type_script_setup_true_lang.js +6 -2
  6. package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +44 -47
  7. package/dist/es/containers/Fieldset.vue_vue_type_script_setup_true_lang.js +7 -1
  8. package/dist/es/containers/FlexLayout.vue_vue_type_script_setup_true_lang.js +5 -2
  9. package/dist/es/containers/FormLabel.vue_vue_type_script_setup_true_lang.js +1 -1
  10. package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +59 -22
  11. package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +118 -100
  12. package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +8 -3
  13. package/dist/es/containers/Row.vue_vue_type_script_setup_true_lang.js +4 -1
  14. package/dist/es/containers/Step.vue_vue_type_script_setup_true_lang.js +5 -2
  15. package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +8 -1
  16. package/dist/es/containers/table/ActionsColumn.vue_vue_type_script_setup_true_lang.js +1 -1
  17. package/dist/es/containers/table/SortColumn.vue_vue_type_script_setup_true_lang.js +1 -1
  18. package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +14 -10
  19. package/dist/es/containers/table/useTableColumns.js +10 -15
  20. package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +72 -79
  21. package/dist/es/containers/table-group-list/useAdd.js +26 -16
  22. package/dist/es/containers/table-group-list/useScrollLastItemIntoView.js +72 -0
  23. package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +2 -2
  24. package/dist/es/fields/Checkbox.vue_vue_type_script_setup_true_lang.js +1 -1
  25. package/dist/es/fields/CheckboxGroup/Index.js +5 -0
  26. package/dist/es/fields/{CheckboxGroup.vue_vue_type_script_setup_true_lang.js → CheckboxGroup/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
  27. package/dist/es/fields/CheckboxGroup/effect.js +8 -0
  28. package/dist/es/fields/ColorPicker.vue_vue_type_script_setup_true_lang.js +1 -1
  29. package/dist/es/fields/Date/Index.js +5 -0
  30. package/dist/es/fields/{Date.vue_vue_type_script_setup_true_lang.js → Date/Index.vue_vue_type_script_setup_true_lang.js} +5 -7
  31. package/dist/es/fields/Date/effect.js +10 -0
  32. package/dist/es/fields/DateTime/Index.js +5 -0
  33. package/dist/es/fields/{DateTime.vue_vue_type_script_setup_true_lang.js → DateTime/Index.vue_vue_type_script_setup_true_lang.js} +5 -9
  34. package/dist/es/fields/DateTime/effect.js +15 -0
  35. package/dist/es/fields/Daterange.vue_vue_type_script_setup_true_lang.js +2 -2
  36. package/dist/es/fields/Display/Index.js +5 -0
  37. package/dist/es/fields/{Display.vue_vue_type_script_setup_true_lang.js → Display/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
  38. package/dist/es/fields/Display/effect.js +8 -0
  39. package/dist/es/fields/DynamicField/Index.js +5 -0
  40. package/dist/es/fields/{DynamicField.vue_vue_type_script_setup_true_lang.js → DynamicField/Index.vue_vue_type_script_setup_true_lang.js} +11 -15
  41. package/dist/es/fields/DynamicField/effect.js +40 -0
  42. package/dist/es/fields/Hidden.vue_vue_type_script_setup_true_lang.js +1 -1
  43. package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +4 -4
  44. package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +4 -2
  45. package/dist/es/fields/NumberRange/Index.js +5 -0
  46. package/dist/es/fields/{NumberRange.vue_vue_type_script_setup_true_lang.js → NumberRange/Index.vue_vue_type_script_setup_true_lang.js} +5 -6
  47. package/dist/es/fields/NumberRange/effect.js +8 -0
  48. package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +5 -2
  49. package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +14 -6
  50. package/dist/es/fields/Switch.vue_vue_type_script_setup_true_lang.js +1 -1
  51. package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +13 -10
  52. package/dist/es/fields/Textarea.vue_vue_type_script_setup_true_lang.js +1 -1
  53. package/dist/es/fields/Time.vue_vue_type_script_setup_true_lang.js +1 -1
  54. package/dist/es/fields/Timerange.vue_vue_type_script_setup_true_lang.js +3 -3
  55. package/dist/es/headless.js +13 -0
  56. package/dist/es/index.js +19 -12
  57. package/dist/es/plugin.js +57 -39
  58. package/dist/es/schema.js +11 -2
  59. package/dist/es/style.css +123 -3
  60. package/dist/es/submitForm.js +270 -72
  61. package/dist/es/utils/builtInFields.js +49 -0
  62. package/dist/es/utils/collectFields.js +407 -0
  63. package/dist/es/utils/config.js +4 -8
  64. package/dist/es/utils/fieldInnerConfig.js +42 -0
  65. package/dist/es/utils/fieldValueEffects.js +80 -0
  66. package/dist/es/utils/form.js +154 -19
  67. package/dist/es/utils/formStateProxy.js +111 -0
  68. package/dist/es/utils/registerField.js +158 -0
  69. package/dist/es/utils/submitHeadless.js +54 -0
  70. package/dist/es/utils/tableGroupList.js +104 -0
  71. package/dist/es/utils/typeMatch.js +532 -0
  72. package/dist/es/utils/validateError.js +57 -0
  73. package/dist/es/utils/validateValues.js +115 -0
  74. package/dist/style.css +123 -3
  75. package/dist/themes/magic-admin.css +576 -14
  76. package/dist/tmagic-form-headless.umd.cjs +4515 -0
  77. package/dist/tmagic-form.umd.cjs +3444 -983
  78. package/package.json +16 -10
  79. package/src/Form.vue +113 -129
  80. package/src/FormBox.vue +16 -4
  81. package/src/FormDialog.vue +16 -5
  82. package/src/FormDrawer.vue +41 -13
  83. package/src/containers/Col.vue +3 -0
  84. package/src/containers/Container.vue +40 -45
  85. package/src/containers/Fieldset.vue +5 -0
  86. package/src/containers/FlexLayout.vue +2 -0
  87. package/src/containers/GroupList.vue +64 -13
  88. package/src/containers/GroupListItem.vue +88 -81
  89. package/src/containers/Panel.vue +3 -0
  90. package/src/containers/Row.vue +2 -0
  91. package/src/containers/Step.vue +2 -0
  92. package/src/containers/Tabs.vue +13 -0
  93. package/src/containers/table/ActionsColumn.vue +1 -1
  94. package/src/containers/table/Table.vue +5 -3
  95. package/src/containers/table/usePagination.ts +1 -1
  96. package/src/containers/table/useSortable.ts +1 -1
  97. package/src/containers/table/useTableColumns.ts +15 -22
  98. package/src/containers/table-group-list/TableGroupList.vue +46 -72
  99. package/src/containers/table-group-list/useAdd.ts +40 -25
  100. package/src/containers/table-group-list/useScrollLastItemIntoView.ts +94 -0
  101. package/src/fields/{CheckboxGroup.vue → CheckboxGroup/Index.vue} +3 -8
  102. package/src/fields/CheckboxGroup/effect.ts +27 -0
  103. package/src/fields/{Date.vue → Date/Index.vue} +2 -5
  104. package/src/fields/Date/effect.ts +29 -0
  105. package/src/fields/{DateTime.vue → DateTime/Index.vue} +2 -16
  106. package/src/fields/DateTime/effect.ts +36 -0
  107. package/src/fields/{Display.vue → Display/Index.vue} +3 -7
  108. package/src/fields/Display/effect.ts +28 -0
  109. package/src/fields/{DynamicField.vue → DynamicField/Index.vue} +11 -11
  110. package/src/fields/DynamicField/effect.ts +74 -0
  111. package/src/fields/Link.vue +3 -4
  112. package/src/fields/Number.vue +2 -1
  113. package/src/fields/{NumberRange.vue → NumberRange/Index.vue} +2 -6
  114. package/src/fields/NumberRange/effect.ts +27 -0
  115. package/src/fields/RadioGroup.vue +13 -1
  116. package/src/fields/Select.vue +13 -4
  117. package/src/headless.ts +78 -0
  118. package/src/index.ts +46 -9
  119. package/src/plugin.ts +68 -37
  120. package/src/schema.ts +13 -2
  121. package/src/submitForm.ts +410 -131
  122. package/src/theme/fieldset.scss +1 -0
  123. package/src/theme/form-box.scss +5 -0
  124. package/src/theme/form-dialog.scss +1 -1
  125. package/src/theme/group-list.scss +143 -2
  126. package/src/theme/table.scss +11 -1
  127. package/src/theme/themes/magic-admin/index.scss +27 -16
  128. package/src/utils/builtInFields.ts +69 -0
  129. package/src/utils/collectFields.ts +561 -0
  130. package/src/utils/config.ts +7 -15
  131. package/src/utils/fieldInnerConfig.ts +127 -0
  132. package/src/utils/fieldValueEffects.ts +140 -0
  133. package/src/utils/form.ts +230 -30
  134. package/src/utils/formStateProxy.ts +149 -0
  135. package/src/utils/registerField.ts +308 -0
  136. package/src/utils/submitHeadless.ts +206 -0
  137. package/src/utils/tableGroupList.ts +147 -0
  138. package/src/utils/typeMatch.ts +948 -0
  139. package/src/utils/validateError.ts +92 -0
  140. package/src/utils/validateValues.ts +177 -0
  141. package/types/headless.d.ts +682 -0
  142. package/types/index.d.ts +900 -180
  143. package/dist/es/_virtual/_rolldown/runtime.js +0 -27
  144. package/dist/es/fields/CheckboxGroup.js +0 -5
  145. package/dist/es/fields/Date.js +0 -5
  146. package/dist/es/fields/DateTime.js +0 -5
  147. package/dist/es/fields/Display.js +0 -5
  148. package/dist/es/fields/DynamicField.js +0 -5
  149. package/dist/es/fields/NumberRange.js +0 -5
@@ -0,0 +1,92 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making TMagicEditor available.
3
+ *
4
+ * Copyright (C) 2025 Tencent. All rights reserved.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import type { FormConfig, ValidateError } from '../schema';
20
+
21
+ /**
22
+ * 通过 name 从 config 中查找对应的 text
23
+ *
24
+ * @param name - 字段名,支持点分隔的路径格式,如 'a.b.c'
25
+ * @param config - 表单配置数组
26
+ * @returns 找到的 text 值,如果未找到则返回 undefined
27
+ */
28
+ export const getTextByName = (name: string, config: FormConfig = []): string | undefined => {
29
+ if (!name || !Array.isArray(config)) return undefined;
30
+
31
+ const nameParts = name.split('.');
32
+
33
+ const findInConfig = (configs: FormConfig, parts: string[]): string | undefined => {
34
+ if (parts.length === 0) return undefined;
35
+
36
+ const [currentPart, ...remainingParts] = parts;
37
+
38
+ for (const item of configs) {
39
+ if (item.name === currentPart) {
40
+ if (remainingParts.length === 0) {
41
+ return typeof item.text === 'string' ? item.text : undefined;
42
+ }
43
+
44
+ if ('items' in item && Array.isArray(item.items)) {
45
+ const result = findInConfig(item.items, remainingParts);
46
+ if (result !== undefined) return result;
47
+ }
48
+ }
49
+
50
+ if ('items' in item && Array.isArray(item.items)) {
51
+ const result = findInConfig(item.items, parts);
52
+ if (result !== undefined) return result;
53
+ }
54
+ }
55
+
56
+ return undefined;
57
+ };
58
+
59
+ return findInConfig(config, nameParts);
60
+ };
61
+
62
+ /**
63
+ * 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
64
+ *
65
+ * `useFieldTextInError` 为 `true`(默认)时用字段的 text 文案作为错误前缀,找不到则回退为字段 name。
66
+ *
67
+ * 由渲染式校验(Form.vue 的 `submitForm` / `validate`)与无渲染校验(`validateValues`)共用,
68
+ * 保证两条校验链路产出的错误文案格式完全一致。
69
+ *
70
+ * @param invalidFields - async-validator 产出的字段错误表
71
+ * @param [options] - 文案选项
72
+ * @param [options.config] - 用于查找 text 的表单配置
73
+ * @param [options.useFieldTextInError=true] - 错误前缀是否使用字段 text
74
+ * @returns 拼接后的错误文案;无错误时为空串
75
+ */
76
+ export const formatValidateError = (
77
+ invalidFields: Record<string, any>,
78
+ { config = [], useFieldTextInError = true }: { config?: FormConfig; useFieldTextInError?: boolean } = {},
79
+ ): string => {
80
+ const error: string[] = [];
81
+
82
+ Object.entries(invalidFields).forEach(([prop, validateError]) => {
83
+ (validateError as ValidateError[]).forEach(({ field, message }) => {
84
+ const name = field || prop;
85
+ const text = (useFieldTextInError ? getTextByName(name, config) : undefined) || name;
86
+
87
+ error.push(`${text} -> ${message}`);
88
+ });
89
+ });
90
+
91
+ return error.join('<br>');
92
+ };
@@ -0,0 +1,177 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making TMagicEditor available.
3
+ *
4
+ * Copyright (C) 2025 Tencent. All rights reserved.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import { reactive } from 'vue';
20
+ import Schema from 'async-validator';
21
+
22
+ import type { FormConfig, FormContext, FormState, FormValue } from '../schema';
23
+
24
+ import { applyMountValueEffects, type CollectedField, collectValidatableFields } from './collectFields';
25
+ import { createFormStateBase, createFormStateProxy, initValue } from './form';
26
+ import { formatValidateError } from './validateError';
27
+
28
+ // #region HeadlessFormStateOptions
29
+ /** 构造无渲染 formState 所需的参数(与 MForm 的同名 props 对齐) */
30
+ export interface HeadlessFormStateOptions {
31
+ config: FormConfig;
32
+ initValues?: FormValue;
33
+ parentValues?: FormValue;
34
+ keyProp?: string;
35
+ popperClass?: string;
36
+ /** 宿主业务上下文,与 MForm 的同名 prop 语义一致 */
37
+ context?: FormContext;
38
+ }
39
+ // #endregion HeadlessFormStateOptions
40
+
41
+ /**
42
+ * 构造一个不依赖组件实例的 `formState`。
43
+ *
44
+ * 字段结构与 `Form.vue` 中 provide 给子树的 `mForm` 保持一致,使配置里的
45
+ * `display` / `rules.validator` / `filter` 等回调拿到的上下文与渲染式校验相同。
46
+ *
47
+ * 差异:没有真实的表单实例,因此 `$emit` 为空实现、`fields` 注册表始终为空
48
+ * (该注册表在整个仓库中只写不读,不参与校验)。
49
+ */
50
+ export const createHeadlessFormState = (options: HeadlessFormStateOptions): FormState => {
51
+ const context = options.context ?? {};
52
+ const state: FormState = {
53
+ keyProp: options.keyProp ?? '__key',
54
+ popperClass: options.popperClass ?? '',
55
+ config: options.config,
56
+ initValues: options.initValues ?? {},
57
+ isCompare: false,
58
+ lastValues: {},
59
+ parentValues: options.parentValues ?? {},
60
+ values: {},
61
+ lastValuesProcessed: {},
62
+ $emit: () => undefined,
63
+ ...createFormStateBase(),
64
+ };
65
+
66
+ return createFormStateProxy(reactive(state), () => context);
67
+ };
68
+
69
+ /**
70
+ * 按 prop 路径从表单值中取字段值,路径中断时返回 `undefined`。
71
+ *
72
+ * 不复用 `@tmagic/utils` 的 `getValueByKeyPath`:后者在路径中断时抛错,
73
+ * 而 Element Plus 的 FormItem 取值是宽松的(拿不到就是 `undefined`),此处与之对齐。
74
+ */
75
+ const getFieldValue = (prop: string, values: FormValue): any => {
76
+ if (!prop) return undefined;
77
+
78
+ return prop.split('.').reduce<any>((acc, key) => {
79
+ if (acc === null || typeof acc !== 'object') return undefined;
80
+ return acc[key];
81
+ }, values);
82
+ };
83
+
84
+ /**
85
+ * 校验单个字段,返回 async-validator 的 `fields` 错误映射(通过则返回 `null`)。
86
+ *
87
+ * 与 Element Plus 的 FormItem 逐字段校验保持一致:
88
+ * 每个字段独立构造一个 `Schema({ [prop]: rules })`,以字段自身的值为 source,
89
+ * 并开启 `firstFields` 只取每个字段的首条错误;`trigger` 是 FormItem 用来筛选规则的
90
+ * 元信息,交给 async-validator 前必须剔除,否则会改变规则的匹配方式。
91
+ */
92
+ const validateField = async (field: CollectedField, values: FormValue): Promise<Record<string, any> | null> => {
93
+ const rules = field.rules.map(({ trigger, ...rule }: any) => rule);
94
+ if (!rules.length) return null;
95
+
96
+ const value = getFieldValue(field.prop, values);
97
+
98
+ try {
99
+ await new Schema({ [field.prop]: rules } as any).validate({ [field.prop]: value }, { firstFields: true });
100
+ return null;
101
+ } catch (err: any) {
102
+ // async-validator reject 的形态为 { errors, fields }
103
+ if (err?.fields) return err.fields;
104
+ return { [field.prop]: [{ field: field.prop, message: err?.message ?? `${err}` }] };
105
+ }
106
+ };
107
+
108
+ // #region ValidateValuesOptions
109
+ /** `validateValues` 参数 */
110
+ export interface ValidateValuesOptions extends HeadlessFormStateOptions {
111
+ /** 是否开启类型匹配校验 */
112
+ typeMatchValid?: boolean;
113
+ /**
114
+ * 校验失败时错误提示前缀是否使用字段的 text 文案。默认 `true`。
115
+ */
116
+ useFieldTextInError?: boolean;
117
+ }
118
+ // #endregion ValidateValuesOptions
119
+
120
+ // #region ValidateValuesResult
121
+ /** `validateValues` 结果 */
122
+ export interface ValidateValuesResult {
123
+ /** 经 `initValue` 初始化并执行字段值初始化写入后的表单值 */
124
+ values: FormValue;
125
+ /** 汇总后的错误文案(多条以 `<br>` 拼接),校验通过为空字符串 */
126
+ error: string;
127
+ /** 原始错误映射,形如 `{ [prop]: [{ field, message }] }` */
128
+ invalidFields: Record<string, any>;
129
+ }
130
+ // #endregion ValidateValuesResult
131
+
132
+ /**
133
+ * 不挂载任何组件,纯逻辑地对「一份 config + 一份值」执行一次完整校验。
134
+ *
135
+ * 流程与渲染式校验一一对应,但不需要 DOM,也不会实例化任何字段组件:
136
+ *
137
+ * 1. 构造 headless `formState` 并挂上 `context` 读穿;
138
+ * 2. `initValue` 初始化表单值(默认值、嵌套结构、`onInitValue` 等);
139
+ * 3. `applyMountValueEffects` 执行字段登记的值初始化写入(与渲染式共用同一份登记表);
140
+ * 4. 遍历 config 树收集所有带规则的字段(等价于渲染出的 FormItem 集合);
141
+ * 5. 逐字段交给 async-validator 执行,汇总错误文案。
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * const { error, values } = await validateValues({
146
+ * config: [...],
147
+ * initValues: { name: '' },
148
+ * typeMatchValid: true,
149
+ * });
150
+ * ```
151
+ */
152
+ export const validateValues = async (options: ValidateValuesOptions): Promise<ValidateValuesResult> => {
153
+ const { config, initValues = {}, typeMatchValid, useFieldTextInError = true } = options;
154
+
155
+ const formState = createHeadlessFormState(options);
156
+
157
+ const values = await initValue(formState, { initValues, config });
158
+ formState.values = values;
159
+ // 与 Form.vue 一致:值挂到 formState 之后再执行字段的值初始化写入
160
+ applyMountValueEffects(formState, config, values);
161
+
162
+ const fields = collectValidatableFields(formState, config, values, Boolean(typeMatchValid));
163
+
164
+ const invalidFields: Record<string, any> = {};
165
+ for (const field of fields) {
166
+ const fieldErrors = await validateField(field, values);
167
+ if (fieldErrors) {
168
+ Object.assign(invalidFields, fieldErrors);
169
+ }
170
+ }
171
+
172
+ return {
173
+ values,
174
+ invalidFields,
175
+ error: formatValidateError(invalidFields, { config, useFieldTextInError }),
176
+ };
177
+ };