@tmagic/form 1.8.0-beta.25 → 1.8.0-beta.27

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 (131) hide show
  1. package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +47 -120
  2. package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +3 -3
  3. package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +3 -3
  4. package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +3 -3
  5. package/dist/es/containers/Col.vue_vue_type_script_setup_true_lang.js +3 -0
  6. package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +36 -51
  7. package/dist/es/containers/Fieldset.vue_vue_type_script_setup_true_lang.js +6 -0
  8. package/dist/es/containers/FlexLayout.vue_vue_type_script_setup_true_lang.js +4 -1
  9. package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +37 -15
  10. package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +109 -97
  11. package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +7 -2
  12. package/dist/es/containers/Row.vue_vue_type_script_setup_true_lang.js +3 -0
  13. package/dist/es/containers/Step.vue_vue_type_script_setup_true_lang.js +4 -1
  14. package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +7 -0
  15. package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +7 -5
  16. package/dist/es/containers/table/useTableColumns.js +5 -13
  17. package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +71 -78
  18. package/dist/es/containers/table-group-list/useAdd.js +26 -16
  19. package/dist/es/containers/table-group-list/useScrollLastItemIntoView.js +72 -0
  20. package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +1 -1
  21. package/dist/es/fields/CheckboxGroup/Index.js +5 -0
  22. 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
  23. package/dist/es/fields/CheckboxGroup/effect.js +8 -0
  24. package/dist/es/fields/Date/Index.js +5 -0
  25. 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
  26. package/dist/es/fields/Date/effect.js +10 -0
  27. package/dist/es/fields/DateTime/Index.js +5 -0
  28. 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
  29. package/dist/es/fields/DateTime/effect.js +15 -0
  30. package/dist/es/fields/Display/Index.js +5 -0
  31. 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
  32. package/dist/es/fields/Display/effect.js +8 -0
  33. package/dist/es/fields/DynamicField/Index.js +5 -0
  34. 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
  35. package/dist/es/fields/DynamicField/effect.js +40 -0
  36. package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +1 -1
  37. package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +1 -1
  38. package/dist/es/fields/NumberRange/Index.js +5 -0
  39. 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
  40. package/dist/es/fields/NumberRange/effect.js +8 -0
  41. package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +13 -6
  42. package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +1 -1
  43. package/dist/es/headless.js +13 -0
  44. package/dist/es/index.js +17 -12
  45. package/dist/es/plugin.js +57 -41
  46. package/dist/es/schema.js +6 -8
  47. package/dist/es/style.css +81 -1
  48. package/dist/es/submitForm.js +44 -194
  49. package/dist/es/utils/builtInFields.js +49 -0
  50. package/dist/es/utils/collectFields.js +407 -0
  51. package/dist/es/utils/config.js +1 -8
  52. package/dist/es/utils/fieldInnerConfig.js +42 -0
  53. package/dist/es/utils/fieldValueEffects.js +80 -0
  54. package/dist/es/utils/form.js +96 -43
  55. package/dist/es/utils/formStateProxy.js +111 -0
  56. package/dist/es/utils/registerField.js +158 -0
  57. package/dist/es/utils/submitHeadless.js +54 -0
  58. package/dist/es/utils/tableGroupList.js +85 -0
  59. package/dist/es/utils/typeMatch.js +17 -19
  60. package/dist/es/utils/validateError.js +57 -0
  61. package/dist/es/utils/validateValues.js +115 -0
  62. package/dist/style.css +81 -1
  63. package/dist/themes/magic-admin.css +83 -7
  64. package/dist/tmagic-form-headless.umd.cjs +4080 -0
  65. package/dist/tmagic-form.umd.cjs +2020 -876
  66. package/package.json +10 -4
  67. package/src/Form.vue +49 -149
  68. package/src/FormBox.vue +5 -4
  69. package/src/FormDialog.vue +5 -5
  70. package/src/FormDrawer.vue +5 -5
  71. package/src/containers/Col.vue +2 -0
  72. package/src/containers/Container.vue +86 -157
  73. package/src/containers/Fieldset.vue +5 -0
  74. package/src/containers/FlexLayout.vue +2 -0
  75. package/src/containers/GroupList.vue +41 -11
  76. package/src/containers/GroupListItem.vue +83 -80
  77. package/src/containers/Panel.vue +3 -0
  78. package/src/containers/Row.vue +2 -0
  79. package/src/containers/Step.vue +2 -0
  80. package/src/containers/Tabs.vue +13 -0
  81. package/src/containers/table/ActionsColumn.vue +1 -1
  82. package/src/containers/table/Table.vue +4 -3
  83. package/src/containers/table/usePagination.ts +1 -1
  84. package/src/containers/table/useSortable.ts +1 -1
  85. package/src/containers/table/useTableColumns.ts +9 -19
  86. package/src/containers/table-group-list/TableGroupList.vue +43 -71
  87. package/src/containers/table-group-list/useAdd.ts +40 -25
  88. package/src/containers/table-group-list/useScrollLastItemIntoView.ts +94 -0
  89. package/src/fields/{CheckboxGroup.vue → CheckboxGroup/Index.vue} +3 -8
  90. package/src/fields/CheckboxGroup/effect.ts +27 -0
  91. package/src/fields/{Date.vue → Date/Index.vue} +2 -5
  92. package/src/fields/Date/effect.ts +29 -0
  93. package/src/fields/{DateTime.vue → DateTime/Index.vue} +2 -16
  94. package/src/fields/DateTime/effect.ts +36 -0
  95. package/src/fields/{Display.vue → Display/Index.vue} +3 -7
  96. package/src/fields/Display/effect.ts +28 -0
  97. package/src/fields/{DynamicField.vue → DynamicField/Index.vue} +11 -11
  98. package/src/fields/DynamicField/effect.ts +74 -0
  99. package/src/fields/{NumberRange.vue → NumberRange/Index.vue} +2 -6
  100. package/src/fields/NumberRange/effect.ts +27 -0
  101. package/src/fields/Select.vue +13 -4
  102. package/src/headless.ts +78 -0
  103. package/src/index.ts +32 -15
  104. package/src/plugin.ts +63 -42
  105. package/src/schema.ts +6 -8
  106. package/src/submitForm.ts +81 -409
  107. package/src/theme/group-list.scss +107 -1
  108. package/src/theme/themes/magic-admin/index.scss +2 -8
  109. package/src/utils/builtInFields.ts +69 -0
  110. package/src/utils/collectFields.ts +561 -0
  111. package/src/utils/config.ts +1 -15
  112. package/src/utils/fieldInnerConfig.ts +127 -0
  113. package/src/utils/fieldValueEffects.ts +140 -0
  114. package/src/utils/form.ts +141 -75
  115. package/src/utils/formStateProxy.ts +149 -0
  116. package/src/utils/registerField.ts +308 -0
  117. package/src/utils/submitHeadless.ts +206 -0
  118. package/src/utils/tableGroupList.ts +120 -0
  119. package/src/utils/typeMatch.ts +19 -15
  120. package/src/utils/validateError.ts +92 -0
  121. package/src/utils/validateValues.ts +177 -0
  122. package/types/headless.d.ts +682 -0
  123. package/types/index.d.ts +631 -272
  124. package/dist/es/fields/CheckboxGroup.js +0 -5
  125. package/dist/es/fields/Date.js +0 -5
  126. package/dist/es/fields/DateTime.js +0 -5
  127. package/dist/es/fields/Display.js +0 -5
  128. package/dist/es/fields/DynamicField.js +0 -5
  129. package/dist/es/fields/NumberRange.js +0 -5
  130. package/dist/es/utils/silentLeafFieldTypes.js +0 -49
  131. package/src/utils/silentLeafFieldTypes.ts +0 -71
@@ -0,0 +1,407 @@
1
+ import { getFieldInnerConfig } from "./fieldInnerConfig.js";
2
+ import { getFieldMountValueEffect, isLeafFieldType } from "./fieldValueEffects.js";
3
+ import { appendProp, display, filterFunction, getItemProp, getNativeRules, isValidName, resolveItemType } from "./form.js";
4
+ import { getGroupListRowConfig, isGroupListType, isTableColumnRendered, makeTableColumnConfig, toGroupListConfig, toTableConfig } from "./tableGroupList.js";
5
+ import { toLine } from "@tmagic/utils";
6
+ //#region packages/form/src/utils/collectFields.ts
7
+ /** 已登记的 innerConfig 回调自身抛错时抛出(机制故障,不是漏登记) */
8
+ var FieldInnerConfigError = class extends Error {
9
+ code = "FIELD_INNER_CONFIG";
10
+ type;
11
+ prop;
12
+ constructor(type, prop, cause) {
13
+ const reason = cause instanceof Error ? cause.message : String(cause);
14
+ super(`[MForm] innerConfig for "${type}" at "${prop}" failed: ${reason}`);
15
+ this.name = "FieldInnerConfigError";
16
+ this.type = type;
17
+ this.prop = prop;
18
+ if (cause instanceof Error) this.cause = cause;
19
+ }
20
+ };
21
+ var isFieldInnerConfigError = (error) => error instanceof FieldInnerConfigError || typeof error === "object" && error !== null && error.code === "FIELD_INNER_CONFIG";
22
+ /** `effects` 模式下遍历全部配置,不受 display / 折叠状态影响 */
23
+ var ignoresDisplay = (ctx) => ctx.mode === "effects";
24
+ var extraContainerWalkers = /* @__PURE__ */ new Map();
25
+ var builtInContainerWalkers = /* @__PURE__ */ new Map();
26
+ var registerContainerWalker = (type, walker, builtIn = false) => {
27
+ if (typeof type !== "string" || !type || typeof walker !== "function") return;
28
+ const key = toLine(type);
29
+ if (builtIn) {
30
+ builtInContainerWalkers.set(key, walker);
31
+ return;
32
+ }
33
+ extraContainerWalkers.set(key, walker);
34
+ };
35
+ var getContainerWalker = (type) => {
36
+ const key = toLine(type);
37
+ return extraContainerWalkers.get(key) ?? builtInContainerWalkers.get(key);
38
+ };
39
+ var deleteContainerWalker = (type) => extraContainerWalkers.delete(toLine(type));
40
+ var clearContainerWalkers = () => extraContainerWalkers.clear();
41
+ var getItems = (config) => config?.items;
42
+ /**
43
+ * `resolveItemType` 的静态版本:不求值函数型 `type`,由调用方按「未知」处理。
44
+ *
45
+ * 两者必须保持一致,否则预扫描判定的 type 与实际遍历的不同,会漏执行 effect。
46
+ */
47
+ var staticItemType = (config) => {
48
+ const type = "type" in config ? config.type : "";
49
+ if (type === "form" || type === "container") return "";
50
+ return `${type || ""}`.replace(/([A-Z])/g, "-$1").toLowerCase() || (config.items ? "" : "text");
51
+ };
52
+ /**
53
+ * 静态预判一份配置子树是否可能触发值初始化写入。
54
+ *
55
+ * 只有「确定不会」时才返回 false。函数型 `type`、`itemsFunction`、业务登记的容器遍历器、
56
+ * 登记了 innerConfig 的复合字段,其运行期结构静态看不出来,一律按可能触发处理。
57
+ *
58
+ * 只用在同一份 `items` 会被逐行 / 逐标签页重复展开的地方(table、group-list、dynamic tab):
59
+ * 预判成本是 O(子项数),跳过省下的是 O(行数 × 子项数),无 effect 的多行表格能省一个量级。
60
+ * 其余位置不做预判——扫描与遍历的单节点成本相当,扫完再遍历只会更慢。
61
+ *
62
+ * 只看 `items`:table / group-list 只遍历当前形态的子项,另一形态的
63
+ * `tableItems` / `groupItems` 不参与遍历,判定范围与遍历保持一致。
64
+ */
65
+ var mayRunEffects = (config) => {
66
+ if (!config) return false;
67
+ if (typeof config.type === "function" || typeof config.itemsFunction === "function") return true;
68
+ const type = staticItemType(config);
69
+ if (type === "hidden") return false;
70
+ if (type) {
71
+ const key = toLine(type);
72
+ if (extraContainerWalkers.has(key)) return true;
73
+ if (!builtInContainerWalkers.has(key)) {
74
+ if (getFieldInnerConfig(type) || getFieldMountValueEffect(type)) return true;
75
+ if (isLeafFieldType(type)) return false;
76
+ }
77
+ }
78
+ return itemsMayRunEffects(config.items);
79
+ };
80
+ var itemsMayRunEffects = (items) => Array.isArray(items) && items.some((item) => mayRunEffects(item));
81
+ /**
82
+ * 复刻 `Container.vue` 的 `display`。
83
+ *
84
+ * 与渲染的唯一差异:`display: 'expand'`(「展开更多配置」按钮背后的字段)在此视为可见。
85
+ * 「是否已展开」纯粹是交互状态,不改变字段是否属于这份配置,无渲染校验按配置声明的范围校验。
86
+ */
87
+ var resolveDisplay = (ctx, config, nodeProps) => {
88
+ if (ignoresDisplay(ctx)) return true;
89
+ const value = display(ctx.mForm, config?.display, nodeProps);
90
+ if (value === "expand") return true;
91
+ return Boolean(value);
92
+ };
93
+ var addField = (ctx, node, itemProp, nodeProps) => {
94
+ if (ctx.mode !== "collect") return;
95
+ const rules = getNativeRules(ctx.mForm, node.config.rules, nodeProps, ctx.typeMatchValid);
96
+ if (!rules.length) return;
97
+ ctx.fields.push({
98
+ prop: itemProp,
99
+ rules,
100
+ config: node.config,
101
+ model: node.model
102
+ });
103
+ };
104
+ var walkChildren = (ctx, items, model, prop) => {
105
+ if (!Array.isArray(items)) return;
106
+ for (const item of items) {
107
+ if (!item) continue;
108
+ walkNode(ctx, {
109
+ config: item,
110
+ model,
111
+ prop
112
+ });
113
+ }
114
+ };
115
+ var getContainerScope = (node) => {
116
+ const { config, model } = node;
117
+ const name = config.name || "";
118
+ return {
119
+ config,
120
+ model,
121
+ name,
122
+ items: getItems(config),
123
+ childModel: name ? model?.[name] : model
124
+ };
125
+ };
126
+ /** containers/Tabs.vue */
127
+ var expandTab = (ctx, node, itemProp) => {
128
+ const { config, model, name, items, childModel } = getContainerScope(node);
129
+ const tabsProps = {
130
+ model,
131
+ config,
132
+ prop: itemProp
133
+ };
134
+ if (config.dynamic) {
135
+ if (!name) return;
136
+ const tabs = model?.[name] || [];
137
+ if (!tabs.length) return;
138
+ if (ctx.mode === "effects" && !itemsMayRunEffects(items)) return;
139
+ tabs.forEach((_tab, tabIndex) => {
140
+ walkChildren(ctx, items, childModel?.[tabIndex], appendProp(itemProp, tabIndex));
141
+ });
142
+ return;
143
+ }
144
+ const tabs = ignoresDisplay(ctx) ? items || [] : (items || []).filter((item) => display(ctx.mForm, item?.display, tabsProps));
145
+ for (const tab of tabs) {
146
+ const tabName = tab.name;
147
+ walkChildren(ctx, getItems(tab), tabName ? childModel?.[tabName] : childModel, tabName ? appendProp(itemProp, tabName) : itemProp);
148
+ }
149
+ };
150
+ /** containers/Row.vue → Col.vue:col 用 v-show 控制显隐,始终渲染 */
151
+ var expandRow = (ctx, node, itemProp) => {
152
+ const { items, childModel } = getContainerScope(node);
153
+ walkChildren(ctx, items, childModel, itemProp);
154
+ };
155
+ /** containers/Fieldset.vue */
156
+ var expandFieldset = (ctx, node, itemProp) => {
157
+ const { config, items, childModel } = getContainerScope(node);
158
+ if (!childModel) return;
159
+ const { checkbox } = config;
160
+ const checkboxName = typeof checkbox === "object" && typeof checkbox.name === "string" ? checkbox.name : "value";
161
+ const checkboxTrueValue = typeof checkbox === "object" && typeof checkbox.trueValue !== "undefined" ? checkbox.trueValue : 1;
162
+ if (!ignoresDisplay(ctx) && config.expand && childModel?.[checkboxName] !== checkboxTrueValue) return;
163
+ walkChildren(ctx, items, childModel, itemProp);
164
+ };
165
+ /** containers/Panel.vue:折叠仅 display:none,子项照常渲染 */
166
+ var expandPanel = (ctx, node, itemProp) => {
167
+ const { items, childModel } = getContainerScope(node);
168
+ if (!items?.length) return;
169
+ walkChildren(ctx, items, childModel, itemProp);
170
+ };
171
+ /** containers/Step.vue:非当前步仅 v-show 隐藏,子项照常渲染;prop 基准被重置为 step.name */
172
+ var expandStep = (ctx, node, _itemProp) => {
173
+ const { model, items } = getContainerScope(node);
174
+ for (const step of items || []) {
175
+ const stepName = step?.name;
176
+ walkChildren(ctx, getItems(step), stepName ? model?.[stepName] : model, `${stepName}`);
177
+ }
178
+ };
179
+ /**
180
+ * 遍历 table / group-list。
181
+ *
182
+ * `TableGroupList.vue` 按原始 `config.type` 决定初始展示形态(table 或 groupList),
183
+ * 两种形态的子项结构不同,这里分别复刻(形态间的配置派生复用 `utils/tableGroupList`,
184
+ * 与渲染式实现同源):
185
+ * - table:逐列(跳过 hidden 列与 display 为假的列)× 逐行渲染 Container,行 prop 为 `${prop}.${index}`;
186
+ * - groupList:逐行渲染一个 row 容器,包含 `items` 全部字段。
187
+ *
188
+ * 与渲染的差异:不做分页截断(table)、不跳过折叠行(groupList)。二者都是列表的视图状态,
189
+ * 与「这些行的值是否合法」无关,无渲染校验覆盖全部行。
190
+ */
191
+ var expandTableGroupList = (ctx, node, itemProp) => {
192
+ const { config, model } = node;
193
+ const name = config.name || "";
194
+ const rows = model?.[name];
195
+ if (!Array.isArray(rows) || !rows.length) return;
196
+ if (isGroupListType(config.type)) {
197
+ const groupListConfig = toGroupListConfig(config);
198
+ if (ctx.mode === "effects" && !itemsMayRunEffects(groupListConfig.items)) return;
199
+ rows.forEach((row, index) => {
200
+ walkNode(ctx, {
201
+ config: getGroupListRowConfig(groupListConfig, index, ctx.mForm?.keyProp),
202
+ model: row,
203
+ prop: appendProp(itemProp, index)
204
+ });
205
+ });
206
+ return;
207
+ }
208
+ const tableItems = toTableConfig(config).items;
209
+ if (!Array.isArray(tableItems)) return;
210
+ if (ctx.mode === "effects" && !itemsMayRunEffects(tableItems)) return;
211
+ const tableProps = {
212
+ model,
213
+ config,
214
+ prop: itemProp
215
+ };
216
+ const evalDisplay = (display$1) => display(ctx.mForm, display$1, tableProps);
217
+ const isRendered = (column) => ignoresDisplay(ctx) || isTableColumnRendered(column, evalDisplay);
218
+ rows.forEach((row, index) => {
219
+ for (const column of tableItems) {
220
+ if (!column || !isRendered(column)) continue;
221
+ walkNode(ctx, {
222
+ config: makeTableColumnConfig(column, row),
223
+ model: row,
224
+ prop: appendProp(itemProp, index)
225
+ });
226
+ }
227
+ });
228
+ };
229
+ /**
230
+ * 执行某个叶子字段登记的值初始化写入。
231
+ *
232
+ * 跑在表单初始化路径上,单个字段的 effect 抛错不应该让整张表单渲染不出来,因此只记录并继续。
233
+ */
234
+ var runMountValueEffect = (ctx, type, node, itemProp) => {
235
+ const effect = getFieldMountValueEffect(type);
236
+ if (!effect) return;
237
+ try {
238
+ effect({
239
+ config: node.config,
240
+ model: node.model,
241
+ prop: itemProp,
242
+ mForm: ctx.mForm,
243
+ values: ctx.values
244
+ });
245
+ } catch (e) {
246
+ console.error(`[MForm] mount value effect for "${type}" at "${itemProp}" failed:`, e);
247
+ }
248
+ };
249
+ /**
250
+ * 遍历已登记 innerConfig 的复合字段。
251
+ *
252
+ * `collect` 模式下回调抛错视为机制故障,包装成 `FieldInnerConfigError` 抛出,由校验流程暴露。
253
+ * `effects` 模式跑在表单初始化路径上,抛出会让整张表单渲染不出来,因此只记录并跳过该子树。
254
+ */
255
+ var walkInnerConfig = (ctx, type, node, itemProp) => {
256
+ const resolve = getFieldInnerConfig(type);
257
+ if (!resolve) return false;
258
+ let result;
259
+ try {
260
+ result = resolve({
261
+ config: node.config,
262
+ model: node.model,
263
+ prop: itemProp,
264
+ parentProp: node.prop,
265
+ mForm: ctx.mForm
266
+ });
267
+ } catch (e) {
268
+ if (ctx.mode === "collect") throw new FieldInnerConfigError(type, itemProp, e);
269
+ console.error(new FieldInnerConfigError(type, itemProp, e));
270
+ return true;
271
+ }
272
+ if (!result) return true;
273
+ const innerModel = result.model ?? node.model;
274
+ const innerProp = result.prop ?? itemProp;
275
+ walkChildren(ctx, Array.isArray(result.config) ? result.config : [result.config], innerModel, innerProp);
276
+ return true;
277
+ };
278
+ /**
279
+ * 遍历一个 Container 节点,分支顺序与 `Container.vue` 的模板保持一致。
280
+ *
281
+ * 对比模式(`isCompare`)在此被忽略:它只改变同一个 prop 渲染几份 FormItem,
282
+ * 规则与取值完全相同,对校验结果没有影响。
283
+ */
284
+ var walkNode = (ctx, node) => {
285
+ const { config, prop } = node;
286
+ const model = node.model ?? {};
287
+ const nodeProps = {
288
+ model,
289
+ config,
290
+ prop
291
+ };
292
+ const name = config.name || "";
293
+ const items = getItems(config);
294
+ const type = resolveItemType(ctx.mForm, config, nodeProps);
295
+ const itemProp = getItemProp(prop, name);
296
+ const text = filterFunction(ctx.mForm, config.text, nodeProps);
297
+ if (type === "hidden") {
298
+ addField(ctx, {
299
+ ...node,
300
+ model
301
+ }, itemProp, nodeProps);
302
+ return;
303
+ }
304
+ const display = resolveDisplay(ctx, config, nodeProps);
305
+ if (items && !text && type && display) {
306
+ dispatchByType(ctx, type, {
307
+ ...node,
308
+ model
309
+ }, itemProp);
310
+ return;
311
+ }
312
+ if (type && display) {
313
+ addField(ctx, {
314
+ ...node,
315
+ model
316
+ }, itemProp, nodeProps);
317
+ dispatchByType(ctx, type, {
318
+ ...node,
319
+ model
320
+ }, itemProp);
321
+ return;
322
+ }
323
+ if (items && display) {
324
+ const childModel = isValidName(name) ? model[name] : model;
325
+ if (!childModel) return;
326
+ walkChildren(ctx, items, childModel, itemProp);
327
+ }
328
+ };
329
+ /**
330
+ * 按 type 分派:已登记 walk 的容器 → 按容器模板遍历;
331
+ * `effects` 模式下先执行本字段的 effect(可与 innerConfig 并存,如 `code-select` 先归一化再下钻);
332
+ * 登记了 innerConfig 的复合字段 → 遍历其内部配置;叶子字段无子树。
333
+ *
334
+ * 未登记的 type:配置里若有 `items` 则按普通容器下钻;否则视为没有嵌套表单项。
335
+ * 自身 rules 已在 FormItem 分支收集,有 rules 就会校验。
336
+ */
337
+ var dispatchByType = (ctx, type, node, itemProp) => {
338
+ const walkContainer = getContainerWalker(type);
339
+ if (walkContainer) {
340
+ walkContainer(ctx, node, itemProp);
341
+ return;
342
+ }
343
+ if (ctx.mode === "effects") runMountValueEffect(ctx, type, node, itemProp);
344
+ if (walkInnerConfig(ctx, type, node, itemProp)) return;
345
+ if (isLeafFieldType(type)) return;
346
+ const items = getItems(node.config);
347
+ if (items?.length) {
348
+ const { childModel } = getContainerScope(node);
349
+ walkChildren(ctx, items, childModel, itemProp);
350
+ }
351
+ };
352
+ /**
353
+ * 纯逻辑收集「一份 config + 一份 values」中所有参与校验的字段。
354
+ *
355
+ * 遍历规则与 `Container.vue` 及各容器组件的模板一一对应,因此产出的 prop / rules
356
+ * 与渲染式校验(挂载 MForm 后调用 `validate()`)等价,无需任何 DOM 或组件实例。
357
+ *
358
+ * 本函数只读不写。字段的值初始化副作用由 `applyMountValueEffects` 负责,
359
+ * 调用方需在初始化表单值之后、收集字段之前执行一次(`validateValues` 已经这么做)。
360
+ *
361
+ * @param mForm - 表单状态;无渲染时由 `createHeadlessFormState` 构造
362
+ * @param config - 表单配置
363
+ * @param values - 表单值
364
+ * @param [typeMatchValid] - 是否自动注入 typeMatch 规则;为 true 时,未显式声明
365
+ * `typeMatch` 的字段会补上 `{ typeMatch: true }`,按字段 type 校验值形态
366
+ * @returns 带规则的字段列表
367
+ */
368
+ var collectValidatableFields = (mForm, config, values, typeMatchValid) => {
369
+ const ctx = {
370
+ mForm,
371
+ typeMatchValid,
372
+ fields: [],
373
+ values,
374
+ mode: "collect"
375
+ };
376
+ if (Array.isArray(config)) walkChildren(ctx, config, values, "");
377
+ return ctx.fields;
378
+ };
379
+ /**
380
+ * 执行「一份 config + 一份 values」中所有叶子字段登记的值初始化写入,就地改写 `values`。
381
+ *
382
+ * 渲染与无渲染两条链路共用的唯一执行点:字段组件自身不再在 setup 里改写 model,
383
+ * 由持有完整表单值的一方(`Form.vue` / `validateValues`)在表单值初始化完成、
384
+ * 且已挂到 `mForm.values` 之后调用一次。这样 effect 与动态 `type` 回调读到的
385
+ * `formValue` 就是最新值,而不是上一轮的旧值。
386
+ *
387
+ * 与 `collectValidatableFields` 的差异:不看 `display`(`display: false` 的字段也会规整)。
388
+ * `type: 'hidden'` 会在该节点停止遍历,内部字段不执行 effect。
389
+ *
390
+ * `prop` 与 `values` 都以传入的 `values` 为根,因此也可以用于 tab / table 新增行这类
391
+ * 「先构造一份子树的值,再挂到表单上」的场景:传入子树配置与该行的值即可。
392
+ *
393
+ * @param mForm - 表单状态;无渲染时由 `createHeadlessFormState` 构造
394
+ * @param config - 表单配置
395
+ * @param values - 表单值(会被就地修改)
396
+ */
397
+ var applyMountValueEffects = (mForm, config, values) => {
398
+ if (!Array.isArray(config)) return;
399
+ walkChildren({
400
+ mForm,
401
+ fields: [],
402
+ values,
403
+ mode: "effects"
404
+ }, config, values, "");
405
+ };
406
+ //#endregion
407
+ export { FieldInnerConfigError, applyMountValueEffects, clearContainerWalkers, collectValidatableFields, deleteContainerWalker, expandFieldset, expandPanel, expandRow, expandStep, expandTab, expandTableGroupList, getContainerWalker, isFieldInnerConfigError, registerContainerWalker };
@@ -7,12 +7,5 @@ var setConfig = (option) => {
7
7
  isGlobalFlat.value = option.flat ?? false;
8
8
  };
9
9
  var getConfig = (key) => $MAGIC_FORM[key];
10
- var fieldRegistry = /* @__PURE__ */ new Map();
11
- var registerField = (tagName, component) => {
12
- if (fieldRegistry.has(tagName)) return;
13
- fieldRegistry.set(tagName, component);
14
- };
15
- var getField = (tagName) => fieldRegistry.get(tagName);
16
- var deleteField = (tagName) => fieldRegistry.delete(tagName);
17
10
  //#endregion
18
- export { deleteField, getConfig, getField, isGlobalFlat, registerField, setConfig };
11
+ export { getConfig, isGlobalFlat, setConfig };
@@ -0,0 +1,42 @@
1
+ import { toLine } from "@tmagic/utils";
2
+ //#region packages/form/src/utils/fieldInnerConfig.ts
3
+ /** 内置内部配置(由 `registerBuiltInFields` 写入;`clearFields` 不会清掉) */
4
+ var builtInInnerConfigs = /* @__PURE__ */ new Map();
5
+ /** 业务侧登记的内部配置 */
6
+ var extraInnerConfigs = /* @__PURE__ */ new Map();
7
+ /**
8
+ * 登记复合字段的内部配置:无渲染校验遇到该 type 时,用返回值继续遍历内部字段。
9
+ *
10
+ * `type` 会按 Container 的规则归一化为中划线形式(`codeSelect` 与 `code-select` 等价)。
11
+ * 重复登记以最后一次为准,便于业务侧覆盖内置实现。
12
+ * `builtIn` 登记不受 `deleteFieldInnerConfig` / `clearFieldInnerConfigs` 影响。
13
+ *
14
+ * @param type - 字段 type
15
+ * @param resolve - innerConfig 回调
16
+ * @param [builtIn=false] - 是否写入内置表
17
+ */
18
+ var registerFieldInnerConfig = (type, resolve, builtIn = false) => {
19
+ if (typeof type !== "string" || !type || typeof resolve !== "function") return;
20
+ (builtIn ? builtInInnerConfigs : extraInnerConfigs).set(toLine(type), resolve);
21
+ };
22
+ /**
23
+ * 获取指定 type 的 innerConfig 回调(业务侧优先于内置)。
24
+ *
25
+ * @param type - 字段 type
26
+ * @returns innerConfig 回调;未登记则为 `undefined`
27
+ */
28
+ var getFieldInnerConfig = (type) => {
29
+ const key = toLine(type);
30
+ return extraInnerConfigs.get(key) ?? builtInInnerConfigs.get(key);
31
+ };
32
+ /**
33
+ * 删除业务侧登记的 innerConfig(不影响内置)。
34
+ *
35
+ * @param type - 字段 type
36
+ * @returns 是否删除成功
37
+ */
38
+ var deleteFieldInnerConfig = (type) => extraInnerConfigs.delete(toLine(type));
39
+ /** 清空业务侧登记的 innerConfig(不影响内置;主要用于单测)。 */
40
+ var clearFieldInnerConfigs = () => extraInnerConfigs.clear();
41
+ //#endregion
42
+ export { clearFieldInnerConfigs, deleteFieldInnerConfig, getFieldInnerConfig, registerFieldInnerConfig };
@@ -0,0 +1,80 @@
1
+ import { toLine } from "@tmagic/utils";
2
+ //#region packages/form/src/utils/fieldValueEffects.ts
3
+ /**
4
+ * @fileoverview 叶子字段登记表:哪些 type 是叶子,以及该字段对表单值的初始化写入。
5
+ *
6
+ * 无渲染校验据此判定该 type 没有属于父表单的嵌套字段。
7
+ * 业务自定义字段通过 `registerField` / `registerFields` 登记。
8
+ * 内置叶子字段由 `MagicForm.install` 的 `registerBuiltInFields` 写入。
9
+ * 未登记且没有 innerConfig 的 type:有 `items` 会下钻子项,自身有 `rules` 会校验自身。
10
+ *
11
+ * 这里只是登记表。各字段的 effect 写在对应组件同目录的 `fields/<Field>/effect.ts`,
12
+ * 执行收口在 `collectFields` 的 `applyMountValueEffects`:渲染(`Form.vue`)
13
+ * 与无渲染(`validateValues`)都在表单值初始化完成后调用它一次,
14
+ * 字段组件自身不再在 setup 里改写 model。
15
+ *
16
+ * @module fieldValueEffects
17
+ */
18
+ /** 内置叶子字段(由 `MagicForm.install` 写入;clearFields 不会清掉) */
19
+ var builtInLeafFieldTypes = /* @__PURE__ */ new Set();
20
+ var builtInMountValueEffects = /* @__PURE__ */ new Map();
21
+ /** 业务侧登记的叶子字段与其挂载副作用 */
22
+ var extraLeafFieldTypes = /* @__PURE__ */ new Set();
23
+ var extraMountValueEffects = /* @__PURE__ */ new Map();
24
+ /**
25
+ * 登记一个叶子字段 type。由 `registerField` 调用,业务侧请走 `registerField`。
26
+ *
27
+ * 传了 `effect` 会覆盖该 type 已有的 extra effect(含覆盖内置);不传则清掉 extra effect,
28
+ * 内置 effect 仍生效。
29
+ *
30
+ * @param type - 字段 type
31
+ * @param [effect] - 挂载时改写 model 的副作用
32
+ * @param [builtIn=false] - 是否写入内置表
33
+ */
34
+ var registerLeafFieldType = (type, effect, builtIn = false) => {
35
+ if (typeof type !== "string" || !type) return;
36
+ const key = toLine(type);
37
+ const types = builtIn ? builtInLeafFieldTypes : extraLeafFieldTypes;
38
+ const effects = builtIn ? builtInMountValueEffects : extraMountValueEffects;
39
+ types.add(key);
40
+ if (typeof effect === "function") effects.set(key, effect);
41
+ else if (!builtIn) effects.delete(key);
42
+ };
43
+ /**
44
+ * 是否为叶子字段 type(内置 ∪ 已登记)。
45
+ *
46
+ * @param type - 字段 type
47
+ * @returns 是否为叶子
48
+ */
49
+ var isLeafFieldType = (type) => {
50
+ const key = toLine(type);
51
+ return extraLeafFieldTypes.has(key) || builtInLeafFieldTypes.has(key);
52
+ };
53
+ /**
54
+ * 获取指定 type 挂载时改写 model 的副作用(已登记的优先于内置)。
55
+ *
56
+ * @param type - 字段 type
57
+ * @returns 副作用函数;未登记则为 `undefined`
58
+ */
59
+ var getFieldMountValueEffect = (type) => {
60
+ const key = toLine(type);
61
+ return extraMountValueEffects.get(key) ?? builtInMountValueEffects.get(key);
62
+ };
63
+ /**
64
+ * 删除业务侧登记的叶子字段(不影响内置;主要用于单测)。
65
+ *
66
+ * @param type - 字段 type
67
+ * @returns 是否删除成功
68
+ */
69
+ var deleteLeafFieldType = (type) => {
70
+ const key = toLine(type);
71
+ extraMountValueEffects.delete(key);
72
+ return extraLeafFieldTypes.delete(key);
73
+ };
74
+ /** 清空业务侧登记的叶子字段(不影响内置;主要用于单测)。 */
75
+ var clearLeafFieldTypes = () => {
76
+ extraLeafFieldTypes.clear();
77
+ extraMountValueEffects.clear();
78
+ };
79
+ //#endregion
80
+ export { clearLeafFieldTypes, deleteLeafFieldType, getFieldMountValueEffect, isLeafFieldType, registerLeafFieldType };