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

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 (130) 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 +2 -2
  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 +75 -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 +4044 -0
  65. package/dist/tmagic-form.umd.cjs +1975 -874
  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/headless.ts +78 -0
  102. package/src/index.ts +32 -15
  103. package/src/plugin.ts +63 -42
  104. package/src/schema.ts +6 -8
  105. package/src/submitForm.ts +81 -409
  106. package/src/theme/group-list.scss +107 -1
  107. package/src/theme/themes/magic-admin/index.scss +2 -8
  108. package/src/utils/builtInFields.ts +69 -0
  109. package/src/utils/collectFields.ts +561 -0
  110. package/src/utils/config.ts +1 -15
  111. package/src/utils/fieldInnerConfig.ts +127 -0
  112. package/src/utils/fieldValueEffects.ts +140 -0
  113. package/src/utils/form.ts +141 -75
  114. package/src/utils/formStateProxy.ts +104 -0
  115. package/src/utils/registerField.ts +308 -0
  116. package/src/utils/submitHeadless.ts +206 -0
  117. package/src/utils/tableGroupList.ts +120 -0
  118. package/src/utils/typeMatch.ts +19 -15
  119. package/src/utils/validateError.ts +92 -0
  120. package/src/utils/validateValues.ts +177 -0
  121. package/types/headless.d.ts +682 -0
  122. package/types/index.d.ts +631 -272
  123. package/dist/es/fields/CheckboxGroup.js +0 -5
  124. package/dist/es/fields/Date.js +0 -5
  125. package/dist/es/fields/DateTime.js +0 -5
  126. package/dist/es/fields/Display.js +0 -5
  127. package/dist/es/fields/DynamicField.js +0 -5
  128. package/dist/es/fields/NumberRange.js +0 -5
  129. package/dist/es/utils/silentLeafFieldTypes.js +0 -49
  130. package/src/utils/silentLeafFieldTypes.ts +0 -71
@@ -1,16 +1,18 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("dayjs"), require("dayjs/plugin/utc"), require("@tmagic/design"), require("@tmagic/utils"), require("dayjs/plugin/customParseFormat"), require("@element-plus/icons-vue"), require("@popperjs/core")) : typeof define === "function" && define.amd ? define([
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("async-validator"), require("@tmagic/utils"), require("dayjs"), require("dayjs/plugin/utc.js"), require("@tmagic/design/headless"), require("dayjs/plugin/customParseFormat.js"), require("@tmagic/design"), require("@element-plus/icons-vue"), require("@popperjs/core")) : typeof define === "function" && define.amd ? define([
3
3
  "exports",
4
4
  "vue",
5
+ "async-validator",
6
+ "@tmagic/utils",
5
7
  "dayjs",
6
- "dayjs/plugin/utc",
8
+ "dayjs/plugin/utc.js",
9
+ "@tmagic/design/headless",
10
+ "dayjs/plugin/customParseFormat.js",
7
11
  "@tmagic/design",
8
- "@tmagic/utils",
9
- "dayjs/plugin/customParseFormat",
10
12
  "@element-plus/icons-vue",
11
13
  "@popperjs/core"
12
- ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.TMagicForm = {}, global.Vue, global.dayjs, global.dayjs_plugin_utc, global._tmagic_design, global._tmagic_utils, global.dayjs_plugin_customParseFormat, global._element_plus_icons_vue, global._popperjs_core));
13
- })(this, function(exports, vue, dayjs, dayjs_plugin_utc, _tmagic_design, _tmagic_utils, dayjs_plugin_customParseFormat, _element_plus_icons_vue, _popperjs_core) {
14
+ ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.TMagicForm = {}, global.Vue, global.async_validator, global._tmagic_utils, global.dayjs, global.dayjs_plugin_utc_js, global._tmagic_design_headless, global.dayjs_plugin_customParseFormat_js, global._tmagic_design, global._element_plus_icons_vue, global._popperjs_core));
15
+ })(this, function(exports, vue, async_validator, _tmagic_utils, dayjs, dayjs_plugin_utc_js, _tmagic_design_headless, dayjs_plugin_customParseFormat_js, _tmagic_design, _element_plus_icons_vue, _popperjs_core) {
14
16
  Object.defineProperties(exports, {
15
17
  __esModule: { value: true },
16
18
  [Symbol.toStringTag]: { value: "Module" }
@@ -37,23 +39,22 @@
37
39
  enumerable: true
38
40
  }) : target, mod));
39
41
  //#endregion
42
+ async_validator = __toESM(async_validator, 1);
40
43
  dayjs = __toESM(dayjs, 1);
41
- dayjs_plugin_utc = __toESM(dayjs_plugin_utc, 1);
42
- dayjs_plugin_customParseFormat = __toESM(dayjs_plugin_customParseFormat, 1);
44
+ dayjs_plugin_utc_js = __toESM(dayjs_plugin_utc_js, 1);
45
+ dayjs_plugin_customParseFormat_js = __toESM(dayjs_plugin_customParseFormat_js, 1);
43
46
  //#region packages/form/src/schema.ts
44
47
  var FORM_DIFF_CONFIG_KEY = Symbol("mFormDiffConfig");
45
48
  var FORM_TYPE_MATCH_VALID_KEY = Symbol("mFormTypeMatchValid");
46
49
  /**
47
- * 静默模式标记,由 `submitForm` / `validateForm` 以隐藏方式挂载临时表单时 provide(debug 弹层模式不提供)。
50
+ * 宿主业务上下文,由 `MForm`(或更外层的 Editor)通过 `provide` 下发。
48
51
  *
49
- * Container 会基于该标记,并对照 `getSilentLeafFieldTypes()`(默认 `LEAF_FIELD_TYPES`)
50
- * 跳过叶子字段的渲染,只保留 FormItem:字段校验由 FormItem 基于 model 值完成,与叶子 UI 组件实例无关。
51
- * 重型字段组件(如 vs-code)也可 inject 该标记跳过自身渲染,省去无谓的实例化开销。
52
+ * 嵌套表单(Link 子表单、FormBox、临时校验表单等)会自动继承最近祖先的 context,
53
+ * 无需层层透传。
52
54
  *
53
- * 注意:仅「挂载无值副作用」(不在 onMounted/watch immediate 中 emit change、不依赖 DOM 计算值)的
54
- * 字段组件可以安全跳过,接入前需逐一审计;业务扩展请使用 `registerSilentLeafFieldTypes`。
55
+ * 配置回调统一通过 `mForm.xxx` 读取,由 formState 的读穿 Proxy 落到这里。
55
56
  */
56
- var FORM_SILENT_MODE_KEY = Symbol("mFormSilentMode");
57
+ var FORM_CONTEXT_KEY = Symbol("mFormContext");
57
58
  //#endregion
58
59
  //#region node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/_freeGlobal.js
59
60
  /** Detect free variable `global` from Node.js. */
@@ -2731,27 +2732,191 @@
2731
2732
  return baseIsEqual(value, other);
2732
2733
  }
2733
2734
  //#endregion
2735
+ //#region node_modules/.pnpm/lodash-es@4.18.1/node_modules/lodash-es/isEqualWith.js
2736
+ /**
2737
+ * This method is like `_.isEqual` except that it accepts `customizer` which
2738
+ * is invoked to compare values. If `customizer` returns `undefined`, comparisons
2739
+ * are handled by the method instead. The `customizer` is invoked with up to
2740
+ * six arguments: (objValue, othValue [, index|key, object, other, stack]).
2741
+ *
2742
+ * @static
2743
+ * @memberOf _
2744
+ * @since 4.0.0
2745
+ * @category Lang
2746
+ * @param {*} value The value to compare.
2747
+ * @param {*} other The other value to compare.
2748
+ * @param {Function} [customizer] The function to customize comparisons.
2749
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
2750
+ * @example
2751
+ *
2752
+ * function isGreeting(value) {
2753
+ * return /^h(?:i|ello)$/.test(value);
2754
+ * }
2755
+ *
2756
+ * function customizer(objValue, othValue) {
2757
+ * if (isGreeting(objValue) && isGreeting(othValue)) {
2758
+ * return true;
2759
+ * }
2760
+ * }
2761
+ *
2762
+ * var array = ['hello', 'goodbye'];
2763
+ * var other = ['hi', 'goodbye'];
2764
+ *
2765
+ * _.isEqualWith(array, other, customizer);
2766
+ * // => true
2767
+ */
2768
+ function isEqualWith(value, other, customizer) {
2769
+ customizer = typeof customizer == "function" ? customizer : void 0;
2770
+ var result = customizer ? customizer(value, other) : void 0;
2771
+ return result === void 0 ? baseIsEqual(value, other, void 0, customizer) : !!result;
2772
+ }
2773
+ //#endregion
2774
+ //#region packages/form/src/utils/fieldInnerConfig.ts
2775
+ /** 内置内部配置(由 `registerBuiltInFields` 写入;`clearFields` 不会清掉) */
2776
+ var builtInInnerConfigs = /* @__PURE__ */ new Map();
2777
+ /** 业务侧登记的内部配置 */
2778
+ var extraInnerConfigs = /* @__PURE__ */ new Map();
2779
+ /**
2780
+ * 登记复合字段的内部配置:无渲染校验遇到该 type 时,用返回值继续遍历内部字段。
2781
+ *
2782
+ * `type` 会按 Container 的规则归一化为中划线形式(`codeSelect` 与 `code-select` 等价)。
2783
+ * 重复登记以最后一次为准,便于业务侧覆盖内置实现。
2784
+ * `builtIn` 登记不受 `deleteFieldInnerConfig` / `clearFieldInnerConfigs` 影响。
2785
+ *
2786
+ * @param type - 字段 type
2787
+ * @param resolve - innerConfig 回调
2788
+ * @param [builtIn=false] - 是否写入内置表
2789
+ */
2790
+ var registerFieldInnerConfig = (type, resolve, builtIn = false) => {
2791
+ if (typeof type !== "string" || !type || typeof resolve !== "function") return;
2792
+ (builtIn ? builtInInnerConfigs : extraInnerConfigs).set((0, _tmagic_utils.toLine)(type), resolve);
2793
+ };
2794
+ /**
2795
+ * 获取指定 type 的 innerConfig 回调(业务侧优先于内置)。
2796
+ *
2797
+ * @param type - 字段 type
2798
+ * @returns innerConfig 回调;未登记则为 `undefined`
2799
+ */
2800
+ var getFieldInnerConfig = (type) => {
2801
+ const key = (0, _tmagic_utils.toLine)(type);
2802
+ return extraInnerConfigs.get(key) ?? builtInInnerConfigs.get(key);
2803
+ };
2804
+ /**
2805
+ * 删除业务侧登记的 innerConfig(不影响内置)。
2806
+ *
2807
+ * @param type - 字段 type
2808
+ * @returns 是否删除成功
2809
+ */
2810
+ var deleteFieldInnerConfig = (type) => extraInnerConfigs.delete((0, _tmagic_utils.toLine)(type));
2811
+ /** 清空业务侧登记的 innerConfig(不影响内置;主要用于单测)。 */
2812
+ var clearFieldInnerConfigs = () => extraInnerConfigs.clear();
2813
+ //#endregion
2814
+ //#region packages/form/src/utils/fieldValueEffects.ts
2815
+ /**
2816
+ * @fileoverview 叶子字段登记表:哪些 type 是叶子,以及该字段对表单值的初始化写入。
2817
+ *
2818
+ * 无渲染校验据此判定该 type 没有属于父表单的嵌套字段。
2819
+ * 业务自定义字段通过 `registerField` / `registerFields` 登记。
2820
+ * 内置叶子字段由 `MagicForm.install` 的 `registerBuiltInFields` 写入。
2821
+ * 未登记且没有 innerConfig 的 type:有 `items` 会下钻子项,自身有 `rules` 会校验自身。
2822
+ *
2823
+ * 这里只是登记表。各字段的 effect 写在对应组件同目录的 `fields/<Field>/effect.ts`,
2824
+ * 执行收口在 `collectFields` 的 `applyMountValueEffects`:渲染(`Form.vue`)
2825
+ * 与无渲染(`validateValues`)都在表单值初始化完成后调用它一次,
2826
+ * 字段组件自身不再在 setup 里改写 model。
2827
+ *
2828
+ * @module fieldValueEffects
2829
+ */
2830
+ /** 内置叶子字段(由 `MagicForm.install` 写入;clearFields 不会清掉) */
2831
+ var builtInLeafFieldTypes = /* @__PURE__ */ new Set();
2832
+ var builtInMountValueEffects = /* @__PURE__ */ new Map();
2833
+ /** 业务侧登记的叶子字段与其挂载副作用 */
2834
+ var extraLeafFieldTypes = /* @__PURE__ */ new Set();
2835
+ var extraMountValueEffects = /* @__PURE__ */ new Map();
2836
+ /**
2837
+ * 登记一个叶子字段 type。由 `registerField` 调用,业务侧请走 `registerField`。
2838
+ *
2839
+ * 传了 `effect` 会覆盖该 type 已有的 extra effect(含覆盖内置);不传则清掉 extra effect,
2840
+ * 内置 effect 仍生效。
2841
+ *
2842
+ * @param type - 字段 type
2843
+ * @param [effect] - 挂载时改写 model 的副作用
2844
+ * @param [builtIn=false] - 是否写入内置表
2845
+ */
2846
+ var registerLeafFieldType = (type, effect, builtIn = false) => {
2847
+ if (typeof type !== "string" || !type) return;
2848
+ const key = (0, _tmagic_utils.toLine)(type);
2849
+ const types = builtIn ? builtInLeafFieldTypes : extraLeafFieldTypes;
2850
+ const effects = builtIn ? builtInMountValueEffects : extraMountValueEffects;
2851
+ types.add(key);
2852
+ if (typeof effect === "function") effects.set(key, effect);
2853
+ else if (!builtIn) effects.delete(key);
2854
+ };
2855
+ /**
2856
+ * 是否为叶子字段 type(内置 ∪ 已登记)。
2857
+ *
2858
+ * @param type - 字段 type
2859
+ * @returns 是否为叶子
2860
+ */
2861
+ var isLeafFieldType = (type) => {
2862
+ const key = (0, _tmagic_utils.toLine)(type);
2863
+ return extraLeafFieldTypes.has(key) || builtInLeafFieldTypes.has(key);
2864
+ };
2865
+ /**
2866
+ * 获取指定 type 挂载时改写 model 的副作用(已登记的优先于内置)。
2867
+ *
2868
+ * @param type - 字段 type
2869
+ * @returns 副作用函数;未登记则为 `undefined`
2870
+ */
2871
+ var getFieldMountValueEffect = (type) => {
2872
+ const key = (0, _tmagic_utils.toLine)(type);
2873
+ return extraMountValueEffects.get(key) ?? builtInMountValueEffects.get(key);
2874
+ };
2875
+ /**
2876
+ * 删除业务侧登记的叶子字段(不影响内置;主要用于单测)。
2877
+ *
2878
+ * @param type - 字段 type
2879
+ * @returns 是否删除成功
2880
+ */
2881
+ var deleteLeafFieldType = (type) => {
2882
+ const key = (0, _tmagic_utils.toLine)(type);
2883
+ extraMountValueEffects.delete(key);
2884
+ return extraLeafFieldTypes.delete(key);
2885
+ };
2886
+ /** 清空业务侧登记的叶子字段(不影响内置;主要用于单测)。 */
2887
+ var clearLeafFieldTypes = () => {
2888
+ extraLeafFieldTypes.clear();
2889
+ extraMountValueEffects.clear();
2890
+ };
2891
+ //#endregion
2892
+ //#region packages/form/src/utils/config.ts
2893
+ var $MAGIC_FORM = {};
2894
+ var isGlobalFlat = (0, vue.ref)(false);
2895
+ var setConfig = (option) => {
2896
+ $MAGIC_FORM = option;
2897
+ isGlobalFlat.value = option.flat ?? false;
2898
+ };
2899
+ var getConfig = (key) => $MAGIC_FORM[key];
2900
+ //#endregion
2734
2901
  //#region packages/form/src/utils/typeMatch.ts
2735
- dayjs.default.extend(dayjs_plugin_customParseFormat.default);
2736
- var typeMatchRuleRegistry = /* @__PURE__ */ new Map();
2902
+ dayjs.default.extend(dayjs_plugin_customParseFormat_js.default);
2903
+ var extraTypeMatchRules = /* @__PURE__ */ new Map();
2904
+ var builtInTypeMatchRules = /* @__PURE__ */ new Map();
2737
2905
  var isPromise = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
2738
- /** 注册或覆盖某个字段 type 的 typeMatch 校验规则 */
2739
- var registerTypeMatchRule = (type, validator) => {
2740
- typeMatchRuleRegistry.set((0, _tmagic_utils.toLine)(type), validator);
2741
- };
2742
- /** 批量注册 typeMatch 校验规则 */
2743
- var registerTypeMatchRules = (rules) => {
2744
- Object.entries(rules).forEach(([type, validator]) => {
2745
- registerTypeMatchRule(type, validator);
2746
- });
2906
+ /** 注册或覆盖某个字段 type 的 typeMatch 校验规则。`builtIn` 登记不受 `clearTypeMatchRules` / `deleteTypeMatchRule` 影响。 */
2907
+ var registerTypeMatchRule = (type, validator, builtIn = false) => {
2908
+ (builtIn ? builtInTypeMatchRules : extraTypeMatchRules).set((0, _tmagic_utils.toLine)(type), validator);
2747
2909
  };
2748
- /** 获取某个字段 type 的自定义 typeMatch 校验规则 */
2749
- var getTypeMatchRule = (type) => typeMatchRuleRegistry.get((0, _tmagic_utils.toLine)(type));
2750
- /** 删除某个字段 type 的自定义 typeMatch 校验规则 */
2751
- var deleteTypeMatchRule = (type) => typeMatchRuleRegistry.delete((0, _tmagic_utils.toLine)(type));
2752
- /** 清空所有自定义 typeMatch 校验规则 */
2910
+ /** 获取某个字段 type 的 typeMatch 校验规则(业务侧优先于内置) */
2911
+ var getTypeMatchRule = (type) => {
2912
+ const key = (0, _tmagic_utils.toLine)(type);
2913
+ return extraTypeMatchRules.get(key) ?? builtInTypeMatchRules.get(key);
2914
+ };
2915
+ /** 删除业务侧 typeMatch 校验规则(不影响内置) */
2916
+ var deleteTypeMatchRule = (type) => extraTypeMatchRules.delete((0, _tmagic_utils.toLine)(type));
2917
+ /** 清空业务侧 typeMatch 校验规则(不影响内置) */
2753
2918
  var clearTypeMatchRules = () => {
2754
- typeMatchRuleRegistry.clear();
2919
+ extraTypeMatchRules.clear();
2755
2920
  };
2756
2921
  /** 本地解析配置函数,避免与 form.ts 循环依赖 */
2757
2922
  var resolveConfig = (mForm, config, props) => {
@@ -2884,7 +3049,7 @@
2884
3049
  */
2885
3050
  var defaultMessage = (message, fallback, suggestion) => {
2886
3051
  if (message) return message;
2887
- return (0, _tmagic_design.appendValidateSuggestion)(fallback, suggestion);
3052
+ return (0, _tmagic_design_headless.appendValidateSuggestion)(fallback, suggestion);
2888
3053
  };
2889
3054
  var isEmptyValue = (value) => value === void 0 || value === null || value === "";
2890
3055
  var isEmptyArray = (value) => Array.isArray(value) && value.length === 0;
@@ -3215,8 +3380,81 @@
3215
3380
  };
3216
3381
  };
3217
3382
  //#endregion
3383
+ //#region packages/form/src/utils/formStateProxy.ts
3384
+ /** 无上下文时共用的空对象,避免每次读取都分配新对象 */
3385
+ var EMPTY_CONTEXT = Object.freeze({});
3386
+ /**
3387
+ * 按优先级分层合并上下文,**靠后的层优先**。
3388
+ *
3389
+ * 用读穿 Proxy 而非 `{ ...a, ...b }`:展开会立即执行 accessor,
3390
+ * 而宿主允许用 `{ get stage() { ... } }` 这类读时求值的描述符。
3391
+ */
3392
+ var mergeFormContexts = (...layers) => {
3393
+ const stack = layers.filter((layer) => Boolean(layer));
3394
+ if (stack.length === 0) return EMPTY_CONTEXT;
3395
+ if (stack.length === 1) return stack[0];
3396
+ const ordered = stack.slice().reverse();
3397
+ const owner = (k) => ordered.find((layer) => Reflect.has(layer, k));
3398
+ return new Proxy({}, {
3399
+ get: (_t, k) => owner(k)?.[k],
3400
+ has: (_t, k) => Boolean(owner(k)),
3401
+ ownKeys: () => [...new Set(ordered.flatMap((layer) => Reflect.ownKeys(layer)))],
3402
+ getOwnPropertyDescriptor: (_t, k) => {
3403
+ for (const layer of ordered) {
3404
+ const descriptor = Object.getOwnPropertyDescriptor(layer, k);
3405
+ if (descriptor) return {
3406
+ ...descriptor,
3407
+ configurable: true
3408
+ };
3409
+ }
3410
+ }
3411
+ });
3412
+ };
3413
+ /**
3414
+ * 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
3415
+ *
3416
+ * - `get` 用属性访问而非 `Reflect.get(t, k, receiver)`,避免破坏 Vue reactive 的 `__v_raw`;
3417
+ * - symbol 键一律只走 core,四个 trap 保持一致。context 是业务数据袋,不承载 symbol 键,
3418
+ * 把 Vue / 工具链的内部 symbol 隔离在 core 上才能让 `toRaw` / `isReactive` 判定正确;
3419
+ * - `ownKeys` + `getOwnPropertyDescriptor` 保证 `Object.entries(formState)` 能枚举到扩展字段
3420
+ * (admin-web-next 的 `pickPanelFormStateExtendFields` 依赖此语义);
3421
+ * - `set` 写入 coreState,第三方 `formState.xxx = v` 仍生效且优先于 context。
3422
+ *
3423
+ * 独立成文件,避免与 `form.ts` ↔ `typeMatch.ts` 形成循环依赖。
3424
+ */
3425
+ var createFormStateProxy = (coreState, getContext) => {
3426
+ const resolve = () => (typeof getContext === "function" ? getContext() : (0, vue.unref)(getContext)) || EMPTY_CONTEXT;
3427
+ return new Proxy(coreState, {
3428
+ get(t, k) {
3429
+ if (typeof k === "symbol") return Reflect.get(t, k);
3430
+ const v = t[k];
3431
+ if (v !== void 0 || Reflect.has(t, k)) return v;
3432
+ return resolve()[k];
3433
+ },
3434
+ set(t, k, value) {
3435
+ t[k] = value;
3436
+ return true;
3437
+ },
3438
+ has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && k in resolve(),
3439
+ ownKeys: (t) => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== "symbol")])],
3440
+ getOwnPropertyDescriptor: (t, k) => {
3441
+ const own = Reflect.getOwnPropertyDescriptor(t, k);
3442
+ if (own) return own;
3443
+ if (typeof k === "symbol") return void 0;
3444
+ const ctx = resolve();
3445
+ if (!(k in ctx)) return void 0;
3446
+ return {
3447
+ configurable: true,
3448
+ enumerable: true,
3449
+ writable: true,
3450
+ value: ctx[k]
3451
+ };
3452
+ }
3453
+ });
3454
+ };
3455
+ //#endregion
3218
3456
  //#region packages/form/src/utils/form.ts
3219
- var isTDesignAdapter = () => (0, _tmagic_design.getDesignConfig)("adapterType") === "tdesign-vue-next";
3457
+ var isTDesignAdapter = () => (0, _tmagic_design_headless.getDesignConfig)("adapterType") === "tdesign-vue-next";
3220
3458
  /**
3221
3459
  * 将 async-validator(Element Plus)风格的 validator 适配到当前 UI 库。
3222
3460
  * TDesign 调用签名为 `(val) => boolean | CustomValidateObj | Promise`,无 callback。
@@ -3326,6 +3564,79 @@
3326
3564
  if (type === "daterange" && !names) return [];
3327
3565
  return "";
3328
3566
  };
3567
+ /**
3568
+ * formState 中与表单 props 无关的公共部分:字段注册表、消息组件、post 请求。
3569
+ *
3570
+ * 由渲染式表单(`Form.vue`)与无渲染校验(`createHeadlessFormState`)共用,
3571
+ * 保证两者提供给字段配置(`onChange` / `validator` / 异步 options 等)的能力一致。
3572
+ */
3573
+ var fallbackMessage = {
3574
+ error: (msg) => console.error(msg),
3575
+ success: (msg) => console.log(msg),
3576
+ warning: (msg) => console.warn(msg),
3577
+ info: (msg) => console.info(msg),
3578
+ closeAll: () => void 0
3579
+ };
3580
+ var fallbackMessageBox = {
3581
+ alert: (msg) => console.log(msg),
3582
+ confirm: (msg) => console.log(msg),
3583
+ close: (msg) => console.log(msg)
3584
+ };
3585
+ var createFormStateBase = (ui) => {
3586
+ const fields = /* @__PURE__ */ new Map();
3587
+ const requestFuc = getConfig("request");
3588
+ return {
3589
+ fields,
3590
+ setField: (prop, field) => fields.set(prop, field),
3591
+ getField: (prop) => fields.get(prop),
3592
+ deleteField: (prop) => fields.delete(prop),
3593
+ $messageBox: ui?.$messageBox ?? fallbackMessageBox,
3594
+ $message: ui?.$message ?? fallbackMessage,
3595
+ post: (options) => {
3596
+ if (requestFuc) return requestFuc({
3597
+ method: "POST",
3598
+ ...options
3599
+ });
3600
+ }
3601
+ };
3602
+ };
3603
+ /**
3604
+ * 配置项的 `name` 是否可用于从 model 中下钻取值。
3605
+ *
3606
+ * 供 `Container.vue` 与无渲染校验(`collectValidatableFields`)共用,两者对「该不该下钻」
3607
+ * 的判断必须一致。
3608
+ */
3609
+ var isValidName = (name) => {
3610
+ const valueType = typeof name;
3611
+ if (valueType !== "string" && valueType !== "symbol" && valueType !== "number") return false;
3612
+ if (name === "") return false;
3613
+ if (valueType === "number") return name >= 0;
3614
+ return true;
3615
+ };
3616
+ /**
3617
+ * 在 `prop` 后追加一段路径。
3618
+ *
3619
+ * 用于容器把 `prop` 透传给子级时再拼上索引 / key(如 group-list 的行、table 的行)。
3620
+ */
3621
+ var appendProp = (prop = "", key) => `${prop}${prop ? "." : ""}${key}`;
3622
+ /**
3623
+ * 由父级 `prop` 与配置项 `name` 拼出该配置项的完整 `prop`(即 FormItem 的 prop)。
3624
+ *
3625
+ * 调用方传入的 name 已按 `config.name || ''` 归一化,空 name 表示该配置项不占据值路径上的一层,
3626
+ * 此时沿用父级 `prop`。
3627
+ */
3628
+ var getItemProp = (prop = "", name = "") => name === "" ? prop ?? "" : appendProp(prop, name);
3629
+ /**
3630
+ * 归一化配置项的 `type`:求值动态 type、剥离容器语义的 type、驼峰转中划线、补默认值。
3631
+ *
3632
+ * 供 `Container.vue` 与无渲染校验共用,保证两者分派到的 type 完全一致。
3633
+ */
3634
+ var resolveItemType = (mForm, config, props) => {
3635
+ let type = "type" in (config || {}) ? config.type : "";
3636
+ type = type && filterFunction(mForm, type, props);
3637
+ if (type === "form" || type === "container") return "";
3638
+ return type?.replace(/([A-Z])/g, "-$1").toLowerCase() || (config?.items ? "" : "text");
3639
+ };
3329
3640
  var filterFunction = (mForm, config, props) => {
3330
3641
  if (typeof config === "function") return config(mForm, {
3331
3642
  values: (0, vue.readonly)(mForm?.initValues || {}),
@@ -3345,18 +3656,18 @@
3345
3656
  if (config === false) return false;
3346
3657
  return true;
3347
3658
  };
3348
- var getRules = function(mForm, r = [], props, typeMatchValid) {
3659
+ var buildRules = function(mForm, r = [], props, typeMatchValid, adapt = (validator) => validator) {
3349
3660
  let rules = cloneDeep(r);
3350
3661
  if (typeof rules === "object" && !Array.isArray(rules)) rules = [rules];
3351
- if (typeMatchValid?.value && !rules.some((r) => typeof r.typeMatch !== "undefined")) rules.push({ typeMatch: true });
3662
+ if ((0, vue.unref)(typeMatchValid) && !rules.some((r) => typeof r.typeMatch !== "undefined")) rules.push({ typeMatch: true });
3352
3663
  return rules.map((item) => {
3353
3664
  if (item.typeMatch) {
3354
- item.validator = adaptFormValidator(createTypeMatchValidator(mForm, props, item));
3665
+ item.validator = adapt(createTypeMatchValidator(mForm, props, item));
3355
3666
  return item;
3356
3667
  }
3357
3668
  if (typeof item.validator === "function") {
3358
3669
  const fnc = item.validator;
3359
- item.validator = adaptFormValidator((rule, value, callback, source, options) => fnc({
3670
+ item.validator = adapt((rule, value, callback, source, options) => fnc({
3360
3671
  rule,
3361
3672
  value: props.config.names ? props.model : value,
3362
3673
  callback,
@@ -3377,6 +3688,18 @@
3377
3688
  return true;
3378
3689
  });
3379
3690
  };
3691
+ var getRules = function(mForm, r = [], props, typeMatchValid) {
3692
+ return buildRules(mForm, r, props, typeMatchValid, adaptFormValidator);
3693
+ };
3694
+ /**
3695
+ * 与 `getRules` 相同,但 validator 保持 async-validator(Element Plus)原生签名,不做 UI 库适配。
3696
+ *
3697
+ * 供无渲染校验(`validateValues`)使用:它直接把规则交给 async-validator 执行,
3698
+ * 不经过 TDesign 的 `validator(val)` 调用约定,因此不能套 `adaptFormValidator`。
3699
+ */
3700
+ var getNativeRules = function(mForm, r = [], props, typeMatchValid) {
3701
+ return buildRules(mForm, r, props, typeMatchValid);
3702
+ };
3380
3703
  var initValue = async (mForm, { initValues, config }) => {
3381
3704
  if (!Array.isArray(config)) throw new Error("config应该为数组");
3382
3705
  const initValuesCopy = cloneDeep(initValues);
@@ -3393,7 +3716,7 @@
3393
3716
  let time;
3394
3717
  if (["x", "timestamp"].includes(format)) time = (0, dayjs.default)(Number.isNaN(Number(v)) ? v : Number(v)).valueOf();
3395
3718
  else if (typeof v === "string" && v.includes("Z") || v instanceof Date) {
3396
- dayjs.default.extend(dayjs_plugin_utc.default);
3719
+ dayjs.default.extend(dayjs_plugin_utc_js.default);
3397
3720
  time = (0, dayjs.default)(v).utcOffset(8).format(format);
3398
3721
  } else time = (0, dayjs.default)(v).format(format);
3399
3722
  if (time !== "Invalid Date") return time;
@@ -3417,47 +3740,712 @@
3417
3740
  if (order === "ascending") data.sort((a, b) => a[prop] - b[prop]);
3418
3741
  else if (order === "descending") data.sort((a, b) => b[prop] - a[prop]);
3419
3742
  };
3743
+ var createObjectProp = (prop, key, name) => {
3744
+ if (prop === "") return key;
3745
+ const itemPath = `${prop}`.split(".");
3746
+ if (name) {
3747
+ if (`${itemPath[itemPath.length - 1]}` === `${name}`) return `${[...itemPath.slice(0, -1), key].join(".")}`;
3748
+ }
3749
+ return `${[...itemPath, key].join(".")}`;
3750
+ };
3751
+ //#endregion
3752
+ //#region packages/form/src/utils/tableGroupList.ts
3420
3753
  /**
3421
- * 将 extendState 返回的扩展字段合并进 formState。
3754
+ * table / group-list 是同一份配置的两种展示形态,可以互相切换(`TableGroupList.vue`)。
3422
3755
  *
3423
- * - data 描述符(普通字段)通过 `formState[key] = value` 写入,走 reactive proxy 的 set,
3424
- * 触发依赖通知;
3425
- * - accessor 描述符(`{ get stage() { return ... } }`)按原样 defineProperty,调用方
3426
- * 可控制读时求值;强制 `configurable: true` 以便下一次合并可再 define。
3756
+ * 本模块收口两种形态之间的配置派生,以及各形态下「子项配置怎么算出来」的规则,
3757
+ * 供渲染(`TableGroupList.vue` / `GroupListItem.vue` / `useTableColumns.ts`)与无渲染校验
3758
+ * (`collectValidatableFields`)共用,避免两条链路各写一份而产生偏差。
3759
+ */
3760
+ /** group-list 形态的 type(兼容驼峰与中划线两种写法) */
3761
+ var isGroupListType = (type) => type === "groupList" || type === "group-list";
3762
+ /** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
3763
+ var calcLabelWidth = (label) => {
3764
+ if (!label) return "0px";
3765
+ const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
3766
+ const chLength = label.length - zhLength;
3767
+ return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
3768
+ };
3769
+ /** 由 group-list 形态的配置派生出 table 形态所需的配置;本身已是 table 形态则原样返回 */
3770
+ var toTableConfig = (config) => {
3771
+ if (!isGroupListType(config.type)) return config;
3772
+ const source = config;
3773
+ return {
3774
+ ...config,
3775
+ type: "table",
3776
+ groupItems: source.items,
3777
+ items: source.tableItems || source.items.map((item) => ({
3778
+ ...item,
3779
+ label: item.label || item.text,
3780
+ text: null
3781
+ }))
3782
+ };
3783
+ };
3784
+ /** 由 table 形态的配置派生出 group-list 形态所需的配置;本身已是 group-list 形态则原样返回 */
3785
+ var toGroupListConfig = (config) => {
3786
+ if (isGroupListType(config.type)) return config;
3787
+ const source = config;
3788
+ return {
3789
+ ...config,
3790
+ type: "groupList",
3791
+ tableItems: source.items,
3792
+ items: source.groupItems || source.items.map((item) => {
3793
+ const text = item.text || item.label;
3794
+ return {
3795
+ ...item,
3796
+ text,
3797
+ labelWidth: calcLabelWidth(text),
3798
+ span: item.span || 12
3799
+ };
3800
+ })
3801
+ };
3802
+ };
3803
+ /**
3804
+ * group-list 每一行渲染的 row 容器配置(`GroupListItem.vue` 渲染的 `Container` 的 config)。
3427
3805
  *
3428
- * 注意:extendState 只能向 formState「新增」字段,不允许覆盖其已有 key。
3429
- * 调用方可通过 `reservedKeys` 传入合并前已存在的内置 key 快照(keyProp / popperClass /
3430
- * config / initValues / isCompare / lastValues / parentValues / values / $emit / fields /
3431
- * post 等),命中这些 key 时统一跳过并告警。
3806
+ * `keyProp` 由调用方从 `mForm.keyProp` 取,默认 `__key`。
3807
+ */
3808
+ var getGroupListRowConfig = (config, index, keyProp) => {
3809
+ const key = keyProp || "__key";
3810
+ return {
3811
+ type: "row",
3812
+ span: config.span || 24,
3813
+ items: config.items,
3814
+ labelWidth: config.labelWidth,
3815
+ labelPosition: config.labelPosition,
3816
+ [key]: `${config[key]}${String(index)}`
3817
+ };
3818
+ };
3819
+ /**
3820
+ * 列是否会被渲染成一列(`hidden` 列不渲染,`display` 为假的列也不渲染)。
3432
3821
  *
3433
- * 兜底:未传 `reservedKeys` 时,仍会拦截 props 派生的只读 getter 字段(无 setter),
3434
- * 否则以普通字段形式赋值会让 proxy 的 set trap 抛出
3435
- * `TypeError: 'set' on proxy: trap returned falsish`。
3822
+ * `display` 的求值上下文是表格自身的 props,由调用方通过 `evalDisplay` 注入。
3436
3823
  */
3437
- var applyExtendState = (formState, state, reservedKeys) => {
3438
- if (!state) return;
3439
- for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) {
3440
- if (reservedKeys?.has(key)) continue;
3441
- if (!("value" in descriptor)) {
3442
- descriptor.configurable = true;
3443
- Object.defineProperty(formState, key, descriptor);
3444
- continue;
3824
+ var isTableColumnRendered = (column, evalDisplay) => column.type !== "hidden" && Boolean(evalDisplay(column.display));
3825
+ /**
3826
+ * 单元格内 `Container` 实际收到的列配置:按行求值 `itemsFunction`,并去掉已在列层面消费过的 `display`。
3827
+ */
3828
+ var makeTableColumnConfig = (column, row) => {
3829
+ const newConfig = cloneDeep(column);
3830
+ if (typeof column.itemsFunction === "function") newConfig.items = column.itemsFunction(row);
3831
+ delete newConfig.display;
3832
+ return newConfig;
3833
+ };
3834
+ //#endregion
3835
+ //#region packages/form/src/utils/collectFields.ts
3836
+ /** 已登记的 innerConfig 回调自身抛错时抛出(机制故障,不是漏登记) */
3837
+ var FieldInnerConfigError = class extends Error {
3838
+ code = "FIELD_INNER_CONFIG";
3839
+ type;
3840
+ prop;
3841
+ constructor(type, prop, cause) {
3842
+ const reason = cause instanceof Error ? cause.message : String(cause);
3843
+ super(`[MForm] innerConfig for "${type}" at "${prop}" failed: ${reason}`);
3844
+ this.name = "FieldInnerConfigError";
3845
+ this.type = type;
3846
+ this.prop = prop;
3847
+ if (cause instanceof Error) this.cause = cause;
3848
+ }
3849
+ };
3850
+ var isFieldInnerConfigError = (error) => error instanceof FieldInnerConfigError || typeof error === "object" && error !== null && error.code === "FIELD_INNER_CONFIG";
3851
+ /** `effects` 模式下遍历全部配置,不受 display / 折叠状态影响 */
3852
+ var ignoresDisplay = (ctx) => ctx.mode === "effects";
3853
+ var extraContainerWalkers = /* @__PURE__ */ new Map();
3854
+ var builtInContainerWalkers = /* @__PURE__ */ new Map();
3855
+ var registerContainerWalker = (type, walker, builtIn = false) => {
3856
+ if (typeof type !== "string" || !type || typeof walker !== "function") return;
3857
+ const key = (0, _tmagic_utils.toLine)(type);
3858
+ if (builtIn) {
3859
+ builtInContainerWalkers.set(key, walker);
3860
+ return;
3861
+ }
3862
+ extraContainerWalkers.set(key, walker);
3863
+ };
3864
+ var getContainerWalker = (type) => {
3865
+ const key = (0, _tmagic_utils.toLine)(type);
3866
+ return extraContainerWalkers.get(key) ?? builtInContainerWalkers.get(key);
3867
+ };
3868
+ var deleteContainerWalker = (type) => extraContainerWalkers.delete((0, _tmagic_utils.toLine)(type));
3869
+ var clearContainerWalkers = () => extraContainerWalkers.clear();
3870
+ var getItems = (config) => config?.items;
3871
+ /**
3872
+ * `resolveItemType` 的静态版本:不求值函数型 `type`,由调用方按「未知」处理。
3873
+ *
3874
+ * 两者必须保持一致,否则预扫描判定的 type 与实际遍历的不同,会漏执行 effect。
3875
+ */
3876
+ var staticItemType = (config) => {
3877
+ const type = "type" in config ? config.type : "";
3878
+ if (type === "form" || type === "container") return "";
3879
+ return `${type || ""}`.replace(/([A-Z])/g, "-$1").toLowerCase() || (config.items ? "" : "text");
3880
+ };
3881
+ /**
3882
+ * 静态预判一份配置子树是否可能触发值初始化写入。
3883
+ *
3884
+ * 只有「确定不会」时才返回 false。函数型 `type`、`itemsFunction`、业务登记的容器遍历器、
3885
+ * 登记了 innerConfig 的复合字段,其运行期结构静态看不出来,一律按可能触发处理。
3886
+ *
3887
+ * 只用在同一份 `items` 会被逐行 / 逐标签页重复展开的地方(table、group-list、dynamic tab):
3888
+ * 预判成本是 O(子项数),跳过省下的是 O(行数 × 子项数),无 effect 的多行表格能省一个量级。
3889
+ * 其余位置不做预判——扫描与遍历的单节点成本相当,扫完再遍历只会更慢。
3890
+ *
3891
+ * 只看 `items`:table / group-list 只遍历当前形态的子项,另一形态的
3892
+ * `tableItems` / `groupItems` 不参与遍历,判定范围与遍历保持一致。
3893
+ */
3894
+ var mayRunEffects = (config) => {
3895
+ if (!config) return false;
3896
+ if (typeof config.type === "function" || typeof config.itemsFunction === "function") return true;
3897
+ const type = staticItemType(config);
3898
+ if (type === "hidden") return false;
3899
+ if (type) {
3900
+ const key = (0, _tmagic_utils.toLine)(type);
3901
+ if (extraContainerWalkers.has(key)) return true;
3902
+ if (!builtInContainerWalkers.has(key)) {
3903
+ if (getFieldInnerConfig(type) || getFieldMountValueEffect(type)) return true;
3904
+ if (isLeafFieldType(type)) return false;
3445
3905
  }
3446
- const targetDescriptor = Object.getOwnPropertyDescriptor(formState, key);
3447
- if (targetDescriptor && !("value" in targetDescriptor) && typeof targetDescriptor.set !== "function") {
3448
- console.warn(`[MForm] extendState: "${key}" is a read-only field derived from props and cannot be assigned a plain value. Return it as a getter accessor if you really need to override it.`);
3449
- continue;
3906
+ }
3907
+ return itemsMayRunEffects(config.items);
3908
+ };
3909
+ var itemsMayRunEffects = (items) => Array.isArray(items) && items.some((item) => mayRunEffects(item));
3910
+ /**
3911
+ * 复刻 `Container.vue` 的 `display`。
3912
+ *
3913
+ * 与渲染的唯一差异:`display: 'expand'`(「展开更多配置」按钮背后的字段)在此视为可见。
3914
+ * 「是否已展开」纯粹是交互状态,不改变字段是否属于这份配置,无渲染校验按配置声明的范围校验。
3915
+ */
3916
+ var resolveDisplay = (ctx, config, nodeProps) => {
3917
+ if (ignoresDisplay(ctx)) return true;
3918
+ const value = display(ctx.mForm, config?.display, nodeProps);
3919
+ if (value === "expand") return true;
3920
+ return Boolean(value);
3921
+ };
3922
+ var addField = (ctx, node, itemProp, nodeProps) => {
3923
+ if (ctx.mode !== "collect") return;
3924
+ const rules = getNativeRules(ctx.mForm, node.config.rules, nodeProps, ctx.typeMatchValid);
3925
+ if (!rules.length) return;
3926
+ ctx.fields.push({
3927
+ prop: itemProp,
3928
+ rules,
3929
+ config: node.config,
3930
+ model: node.model
3931
+ });
3932
+ };
3933
+ var walkChildren = (ctx, items, model, prop) => {
3934
+ if (!Array.isArray(items)) return;
3935
+ for (const item of items) {
3936
+ if (!item) continue;
3937
+ walkNode(ctx, {
3938
+ config: item,
3939
+ model,
3940
+ prop
3941
+ });
3942
+ }
3943
+ };
3944
+ var getContainerScope = (node) => {
3945
+ const { config, model } = node;
3946
+ const name = config.name || "";
3947
+ return {
3948
+ config,
3949
+ model,
3950
+ name,
3951
+ items: getItems(config),
3952
+ childModel: name ? model?.[name] : model
3953
+ };
3954
+ };
3955
+ /** containers/Tabs.vue */
3956
+ var expandTab = (ctx, node, itemProp) => {
3957
+ const { config, model, name, items, childModel } = getContainerScope(node);
3958
+ const tabsProps = {
3959
+ model,
3960
+ config,
3961
+ prop: itemProp
3962
+ };
3963
+ if (config.dynamic) {
3964
+ if (!name) return;
3965
+ const tabs = model?.[name] || [];
3966
+ if (!tabs.length) return;
3967
+ if (ctx.mode === "effects" && !itemsMayRunEffects(items)) return;
3968
+ tabs.forEach((_tab, tabIndex) => {
3969
+ walkChildren(ctx, items, childModel?.[tabIndex], appendProp(itemProp, tabIndex));
3970
+ });
3971
+ return;
3972
+ }
3973
+ const tabs = ignoresDisplay(ctx) ? items || [] : (items || []).filter((item) => display(ctx.mForm, item?.display, tabsProps));
3974
+ for (const tab of tabs) {
3975
+ const tabName = tab.name;
3976
+ walkChildren(ctx, getItems(tab), tabName ? childModel?.[tabName] : childModel, tabName ? appendProp(itemProp, tabName) : itemProp);
3977
+ }
3978
+ };
3979
+ /** containers/Row.vue → Col.vue:col 用 v-show 控制显隐,始终渲染 */
3980
+ var expandRow = (ctx, node, itemProp) => {
3981
+ const { items, childModel } = getContainerScope(node);
3982
+ walkChildren(ctx, items, childModel, itemProp);
3983
+ };
3984
+ /** containers/Fieldset.vue */
3985
+ var expandFieldset = (ctx, node, itemProp) => {
3986
+ const { config, items, childModel } = getContainerScope(node);
3987
+ if (!childModel) return;
3988
+ const { checkbox } = config;
3989
+ const checkboxName = typeof checkbox === "object" && typeof checkbox.name === "string" ? checkbox.name : "value";
3990
+ const checkboxTrueValue = typeof checkbox === "object" && typeof checkbox.trueValue !== "undefined" ? checkbox.trueValue : 1;
3991
+ if (!ignoresDisplay(ctx) && config.expand && childModel?.[checkboxName] !== checkboxTrueValue) return;
3992
+ walkChildren(ctx, items, childModel, itemProp);
3993
+ };
3994
+ /** containers/Panel.vue:折叠仅 display:none,子项照常渲染 */
3995
+ var expandPanel = (ctx, node, itemProp) => {
3996
+ const { items, childModel } = getContainerScope(node);
3997
+ if (!items?.length) return;
3998
+ walkChildren(ctx, items, childModel, itemProp);
3999
+ };
4000
+ /** containers/Step.vue:非当前步仅 v-show 隐藏,子项照常渲染;prop 基准被重置为 step.name */
4001
+ var expandStep = (ctx, node, _itemProp) => {
4002
+ const { model, items } = getContainerScope(node);
4003
+ for (const step of items || []) {
4004
+ const stepName = step?.name;
4005
+ walkChildren(ctx, getItems(step), stepName ? model?.[stepName] : model, `${stepName}`);
4006
+ }
4007
+ };
4008
+ /**
4009
+ * 遍历 table / group-list。
4010
+ *
4011
+ * `TableGroupList.vue` 按原始 `config.type` 决定初始展示形态(table 或 groupList),
4012
+ * 两种形态的子项结构不同,这里分别复刻(形态间的配置派生复用 `utils/tableGroupList`,
4013
+ * 与渲染式实现同源):
4014
+ * - table:逐列(跳过 hidden 列与 display 为假的列)× 逐行渲染 Container,行 prop 为 `${prop}.${index}`;
4015
+ * - groupList:逐行渲染一个 row 容器,包含 `items` 全部字段。
4016
+ *
4017
+ * 与渲染的差异:不做分页截断(table)、不跳过折叠行(groupList)。二者都是列表的视图状态,
4018
+ * 与「这些行的值是否合法」无关,无渲染校验覆盖全部行。
4019
+ */
4020
+ var expandTableGroupList = (ctx, node, itemProp) => {
4021
+ const { config, model } = node;
4022
+ const name = config.name || "";
4023
+ const rows = model?.[name];
4024
+ if (!Array.isArray(rows) || !rows.length) return;
4025
+ if (isGroupListType(config.type)) {
4026
+ const groupListConfig = toGroupListConfig(config);
4027
+ if (ctx.mode === "effects" && !itemsMayRunEffects(groupListConfig.items)) return;
4028
+ rows.forEach((row, index) => {
4029
+ walkNode(ctx, {
4030
+ config: getGroupListRowConfig(groupListConfig, index, ctx.mForm?.keyProp),
4031
+ model: row,
4032
+ prop: appendProp(itemProp, index)
4033
+ });
4034
+ });
4035
+ return;
4036
+ }
4037
+ const tableItems = toTableConfig(config).items;
4038
+ if (!Array.isArray(tableItems)) return;
4039
+ if (ctx.mode === "effects" && !itemsMayRunEffects(tableItems)) return;
4040
+ const tableProps = {
4041
+ model,
4042
+ config,
4043
+ prop: itemProp
4044
+ };
4045
+ const evalDisplay = (display$4) => display(ctx.mForm, display$4, tableProps);
4046
+ const isRendered = (column) => ignoresDisplay(ctx) || isTableColumnRendered(column, evalDisplay);
4047
+ rows.forEach((row, index) => {
4048
+ for (const column of tableItems) {
4049
+ if (!column || !isRendered(column)) continue;
4050
+ walkNode(ctx, {
4051
+ config: makeTableColumnConfig(column, row),
4052
+ model: row,
4053
+ prop: appendProp(itemProp, index)
4054
+ });
3450
4055
  }
3451
- formState[key] = state[key];
4056
+ });
4057
+ };
4058
+ /**
4059
+ * 执行某个叶子字段登记的值初始化写入。
4060
+ *
4061
+ * 跑在表单初始化路径上,单个字段的 effect 抛错不应该让整张表单渲染不出来,因此只记录并继续。
4062
+ */
4063
+ var runMountValueEffect = (ctx, type, node, itemProp) => {
4064
+ const effect = getFieldMountValueEffect(type);
4065
+ if (!effect) return;
4066
+ try {
4067
+ effect({
4068
+ config: node.config,
4069
+ model: node.model,
4070
+ prop: itemProp,
4071
+ mForm: ctx.mForm,
4072
+ values: ctx.values
4073
+ });
4074
+ } catch (e) {
4075
+ console.error(`[MForm] mount value effect for "${type}" at "${itemProp}" failed:`, e);
3452
4076
  }
3453
4077
  };
3454
- var createObjectProp = (prop, key, name) => {
3455
- if (prop === "") return key;
3456
- const itemPath = `${prop}`.split(".");
3457
- if (name) {
3458
- if (`${itemPath[itemPath.length - 1]}` === `${name}`) return `${[...itemPath.slice(0, -1), key].join(".")}`;
4078
+ /**
4079
+ * 遍历已登记 innerConfig 的复合字段。
4080
+ *
4081
+ * `collect` 模式下回调抛错视为机制故障,包装成 `FieldInnerConfigError` 抛出,由校验流程暴露。
4082
+ * `effects` 模式跑在表单初始化路径上,抛出会让整张表单渲染不出来,因此只记录并跳过该子树。
4083
+ */
4084
+ var walkInnerConfig = (ctx, type, node, itemProp) => {
4085
+ const resolve = getFieldInnerConfig(type);
4086
+ if (!resolve) return false;
4087
+ let result;
4088
+ try {
4089
+ result = resolve({
4090
+ config: node.config,
4091
+ model: node.model,
4092
+ prop: itemProp,
4093
+ parentProp: node.prop,
4094
+ mForm: ctx.mForm
4095
+ });
4096
+ } catch (e) {
4097
+ if (ctx.mode === "collect") throw new FieldInnerConfigError(type, itemProp, e);
4098
+ console.error(new FieldInnerConfigError(type, itemProp, e));
4099
+ return true;
3459
4100
  }
3460
- return `${[...itemPath, key].join(".")}`;
4101
+ if (!result) return true;
4102
+ const innerModel = result.model ?? node.model;
4103
+ const innerProp = result.prop ?? itemProp;
4104
+ walkChildren(ctx, Array.isArray(result.config) ? result.config : [result.config], innerModel, innerProp);
4105
+ return true;
4106
+ };
4107
+ /**
4108
+ * 遍历一个 Container 节点,分支顺序与 `Container.vue` 的模板保持一致。
4109
+ *
4110
+ * 对比模式(`isCompare`)在此被忽略:它只改变同一个 prop 渲染几份 FormItem,
4111
+ * 规则与取值完全相同,对校验结果没有影响。
4112
+ */
4113
+ var walkNode = (ctx, node) => {
4114
+ const { config, prop } = node;
4115
+ const model = node.model ?? {};
4116
+ const nodeProps = {
4117
+ model,
4118
+ config,
4119
+ prop
4120
+ };
4121
+ const name = config.name || "";
4122
+ const items = getItems(config);
4123
+ const type = resolveItemType(ctx.mForm, config, nodeProps);
4124
+ const itemProp = getItemProp(prop, name);
4125
+ const text = filterFunction(ctx.mForm, config.text, nodeProps);
4126
+ if (type === "hidden") {
4127
+ addField(ctx, {
4128
+ ...node,
4129
+ model
4130
+ }, itemProp, nodeProps);
4131
+ return;
4132
+ }
4133
+ const display = resolveDisplay(ctx, config, nodeProps);
4134
+ if (items && !text && type && display) {
4135
+ dispatchByType(ctx, type, {
4136
+ ...node,
4137
+ model
4138
+ }, itemProp);
4139
+ return;
4140
+ }
4141
+ if (type && display) {
4142
+ addField(ctx, {
4143
+ ...node,
4144
+ model
4145
+ }, itemProp, nodeProps);
4146
+ dispatchByType(ctx, type, {
4147
+ ...node,
4148
+ model
4149
+ }, itemProp);
4150
+ return;
4151
+ }
4152
+ if (items && display) {
4153
+ const childModel = isValidName(name) ? model[name] : model;
4154
+ if (!childModel) return;
4155
+ walkChildren(ctx, items, childModel, itemProp);
4156
+ }
4157
+ };
4158
+ /**
4159
+ * 按 type 分派:已登记 walk 的容器 → 按容器模板遍历;
4160
+ * `effects` 模式下先执行本字段的 effect(可与 innerConfig 并存,如 `code-select` 先归一化再下钻);
4161
+ * 登记了 innerConfig 的复合字段 → 遍历其内部配置;叶子字段无子树。
4162
+ *
4163
+ * 未登记的 type:配置里若有 `items` 则按普通容器下钻;否则视为没有嵌套表单项。
4164
+ * 自身 rules 已在 FormItem 分支收集,有 rules 就会校验。
4165
+ */
4166
+ var dispatchByType = (ctx, type, node, itemProp) => {
4167
+ const walkContainer = getContainerWalker(type);
4168
+ if (walkContainer) {
4169
+ walkContainer(ctx, node, itemProp);
4170
+ return;
4171
+ }
4172
+ if (ctx.mode === "effects") runMountValueEffect(ctx, type, node, itemProp);
4173
+ if (walkInnerConfig(ctx, type, node, itemProp)) return;
4174
+ if (isLeafFieldType(type)) return;
4175
+ const items = getItems(node.config);
4176
+ if (items?.length) {
4177
+ const { childModel } = getContainerScope(node);
4178
+ walkChildren(ctx, items, childModel, itemProp);
4179
+ }
4180
+ };
4181
+ /**
4182
+ * 纯逻辑收集「一份 config + 一份 values」中所有参与校验的字段。
4183
+ *
4184
+ * 遍历规则与 `Container.vue` 及各容器组件的模板一一对应,因此产出的 prop / rules
4185
+ * 与渲染式校验(挂载 MForm 后调用 `validate()`)等价,无需任何 DOM 或组件实例。
4186
+ *
4187
+ * 本函数只读不写。字段的值初始化副作用由 `applyMountValueEffects` 负责,
4188
+ * 调用方需在初始化表单值之后、收集字段之前执行一次(`validateValues` 已经这么做)。
4189
+ *
4190
+ * @param mForm - 表单状态;无渲染时由 `createHeadlessFormState` 构造
4191
+ * @param config - 表单配置
4192
+ * @param values - 表单值
4193
+ * @param [typeMatchValid] - 是否自动注入 typeMatch 规则;为 true 时,未显式声明
4194
+ * `typeMatch` 的字段会补上 `{ typeMatch: true }`,按字段 type 校验值形态
4195
+ * @returns 带规则的字段列表
4196
+ */
4197
+ var collectValidatableFields = (mForm, config, values, typeMatchValid) => {
4198
+ const ctx = {
4199
+ mForm,
4200
+ typeMatchValid,
4201
+ fields: [],
4202
+ values,
4203
+ mode: "collect"
4204
+ };
4205
+ if (Array.isArray(config)) walkChildren(ctx, config, values, "");
4206
+ return ctx.fields;
4207
+ };
4208
+ /**
4209
+ * 执行「一份 config + 一份 values」中所有叶子字段登记的值初始化写入,就地改写 `values`。
4210
+ *
4211
+ * 渲染与无渲染两条链路共用的唯一执行点:字段组件自身不再在 setup 里改写 model,
4212
+ * 由持有完整表单值的一方(`Form.vue` / `validateValues`)在表单值初始化完成、
4213
+ * 且已挂到 `mForm.values` 之后调用一次。这样 effect 与动态 `type` 回调读到的
4214
+ * `formValue` 就是最新值,而不是上一轮的旧值。
4215
+ *
4216
+ * 与 `collectValidatableFields` 的差异:不看 `display`(`display: false` 的字段也会规整)。
4217
+ * `type: 'hidden'` 会在该节点停止遍历,内部字段不执行 effect。
4218
+ *
4219
+ * `prop` 与 `values` 都以传入的 `values` 为根,因此也可以用于 tab / table 新增行这类
4220
+ * 「先构造一份子树的值,再挂到表单上」的场景:传入子树配置与该行的值即可。
4221
+ *
4222
+ * @param mForm - 表单状态;无渲染时由 `createHeadlessFormState` 构造
4223
+ * @param config - 表单配置
4224
+ * @param values - 表单值(会被就地修改)
4225
+ */
4226
+ var applyMountValueEffects = (mForm, config, values) => {
4227
+ if (!Array.isArray(config)) return;
4228
+ walkChildren({
4229
+ mForm,
4230
+ fields: [],
4231
+ values,
4232
+ mode: "effects"
4233
+ }, config, values, "");
4234
+ };
4235
+ //#endregion
4236
+ //#region packages/form/src/utils/validateError.ts
4237
+ /**
4238
+ * 通过 name 从 config 中查找对应的 text
4239
+ *
4240
+ * @param name - 字段名,支持点分隔的路径格式,如 'a.b.c'
4241
+ * @param config - 表单配置数组
4242
+ * @returns 找到的 text 值,如果未找到则返回 undefined
4243
+ */
4244
+ var getTextByName = (name, config = []) => {
4245
+ if (!name || !Array.isArray(config)) return void 0;
4246
+ const nameParts = name.split(".");
4247
+ const findInConfig = (configs, parts) => {
4248
+ if (parts.length === 0) return void 0;
4249
+ const [currentPart, ...remainingParts] = parts;
4250
+ for (const item of configs) {
4251
+ if (item.name === currentPart) {
4252
+ if (remainingParts.length === 0) return typeof item.text === "string" ? item.text : void 0;
4253
+ if ("items" in item && Array.isArray(item.items)) {
4254
+ const result = findInConfig(item.items, remainingParts);
4255
+ if (result !== void 0) return result;
4256
+ }
4257
+ }
4258
+ if ("items" in item && Array.isArray(item.items)) {
4259
+ const result = findInConfig(item.items, parts);
4260
+ if (result !== void 0) return result;
4261
+ }
4262
+ }
4263
+ };
4264
+ return findInConfig(config, nameParts);
4265
+ };
4266
+ /**
4267
+ * 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
4268
+ *
4269
+ * `useFieldTextInError` 为 `true`(默认)时用字段的 text 文案作为错误前缀,找不到则回退为字段 name。
4270
+ *
4271
+ * 由渲染式校验(Form.vue 的 `submitForm` / `validate`)与无渲染校验(`validateValues`)共用,
4272
+ * 保证两条校验链路产出的错误文案格式完全一致。
4273
+ *
4274
+ * @param invalidFields - async-validator 产出的字段错误表
4275
+ * @param [options] - 文案选项
4276
+ * @param [options.config] - 用于查找 text 的表单配置
4277
+ * @param [options.useFieldTextInError=true] - 错误前缀是否使用字段 text
4278
+ * @returns 拼接后的错误文案;无错误时为空串
4279
+ */
4280
+ var formatValidateError = (invalidFields, { config = [], useFieldTextInError = true } = {}) => {
4281
+ const error = [];
4282
+ Object.entries(invalidFields).forEach(([prop, validateError]) => {
4283
+ validateError.forEach(({ field, message }) => {
4284
+ const name = field || prop;
4285
+ const text = (useFieldTextInError ? getTextByName(name, config) : void 0) || name;
4286
+ error.push(`${text} -> ${message}`);
4287
+ });
4288
+ });
4289
+ return error.join("<br>");
4290
+ };
4291
+ //#endregion
4292
+ //#region packages/form/src/utils/validateValues.ts
4293
+ /**
4294
+ * 构造一个不依赖组件实例的 `formState`。
4295
+ *
4296
+ * 字段结构与 `Form.vue` 中 provide 给子树的 `mForm` 保持一致,使配置里的
4297
+ * `display` / `rules.validator` / `filter` 等回调拿到的上下文与渲染式校验相同。
4298
+ *
4299
+ * 差异:没有真实的表单实例,因此 `$emit` 为空实现、`fields` 注册表始终为空
4300
+ * (该注册表在整个仓库中只写不读,不参与校验)。
4301
+ */
4302
+ var createHeadlessFormState = (options) => {
4303
+ const context = options.context ?? {};
4304
+ const state = {
4305
+ keyProp: options.keyProp ?? "__key",
4306
+ popperClass: options.popperClass ?? "",
4307
+ config: options.config,
4308
+ initValues: options.initValues ?? {},
4309
+ isCompare: false,
4310
+ lastValues: {},
4311
+ parentValues: options.parentValues ?? {},
4312
+ values: {},
4313
+ lastValuesProcessed: {},
4314
+ $emit: () => void 0,
4315
+ ...createFormStateBase()
4316
+ };
4317
+ return createFormStateProxy((0, vue.reactive)(state), () => context);
4318
+ };
4319
+ /**
4320
+ * 按 prop 路径从表单值中取字段值,路径中断时返回 `undefined`。
4321
+ *
4322
+ * 不复用 `@tmagic/utils` 的 `getValueByKeyPath`:后者在路径中断时抛错,
4323
+ * 而 Element Plus 的 FormItem 取值是宽松的(拿不到就是 `undefined`),此处与之对齐。
4324
+ */
4325
+ var getFieldValue = (prop, values) => {
4326
+ if (!prop) return void 0;
4327
+ return prop.split(".").reduce((acc, key) => {
4328
+ if (acc === null || typeof acc !== "object") return void 0;
4329
+ return acc[key];
4330
+ }, values);
4331
+ };
4332
+ /**
4333
+ * 校验单个字段,返回 async-validator 的 `fields` 错误映射(通过则返回 `null`)。
4334
+ *
4335
+ * 与 Element Plus 的 FormItem 逐字段校验保持一致:
4336
+ * 每个字段独立构造一个 `Schema({ [prop]: rules })`,以字段自身的值为 source,
4337
+ * 并开启 `firstFields` 只取每个字段的首条错误;`trigger` 是 FormItem 用来筛选规则的
4338
+ * 元信息,交给 async-validator 前必须剔除,否则会改变规则的匹配方式。
4339
+ */
4340
+ var validateField = async (field, values) => {
4341
+ const rules = field.rules.map(({ trigger, ...rule }) => rule);
4342
+ if (!rules.length) return null;
4343
+ const value = getFieldValue(field.prop, values);
4344
+ try {
4345
+ await new async_validator.default({ [field.prop]: rules }).validate({ [field.prop]: value }, { firstFields: true });
4346
+ return null;
4347
+ } catch (err) {
4348
+ if (err?.fields) return err.fields;
4349
+ return { [field.prop]: [{
4350
+ field: field.prop,
4351
+ message: err?.message ?? `${err}`
4352
+ }] };
4353
+ }
4354
+ };
4355
+ /**
4356
+ * 不挂载任何组件,纯逻辑地对「一份 config + 一份值」执行一次完整校验。
4357
+ *
4358
+ * 流程与渲染式校验一一对应,但不需要 DOM,也不会实例化任何字段组件:
4359
+ *
4360
+ * 1. 构造 headless `formState` 并挂上 `context` 读穿;
4361
+ * 2. `initValue` 初始化表单值(默认值、嵌套结构、`onInitValue` 等);
4362
+ * 3. `applyMountValueEffects` 执行字段登记的值初始化写入(与渲染式共用同一份登记表);
4363
+ * 4. 遍历 config 树收集所有带规则的字段(等价于渲染出的 FormItem 集合);
4364
+ * 5. 逐字段交给 async-validator 执行,汇总错误文案。
4365
+ *
4366
+ * @example
4367
+ * ```ts
4368
+ * const { error, values } = await validateValues({
4369
+ * config: [...],
4370
+ * initValues: { name: '' },
4371
+ * typeMatchValid: true,
4372
+ * });
4373
+ * ```
4374
+ */
4375
+ var validateValues = async (options) => {
4376
+ const { config, initValues = {}, typeMatchValid, useFieldTextInError = true } = options;
4377
+ const formState = createHeadlessFormState(options);
4378
+ const values = await initValue(formState, {
4379
+ initValues,
4380
+ config
4381
+ });
4382
+ formState.values = values;
4383
+ applyMountValueEffects(formState, config, values);
4384
+ const fields = collectValidatableFields(formState, config, values, Boolean(typeMatchValid));
4385
+ const invalidFields = {};
4386
+ for (const field of fields) {
4387
+ const fieldErrors = await validateField(field, values);
4388
+ if (fieldErrors) Object.assign(invalidFields, fieldErrors);
4389
+ }
4390
+ return {
4391
+ values,
4392
+ invalidFields,
4393
+ error: formatValidateError(invalidFields, {
4394
+ config,
4395
+ useFieldTextInError
4396
+ })
4397
+ };
4398
+ };
4399
+ //#endregion
4400
+ //#region packages/form/src/utils/submitHeadless.ts
4401
+ var throwIfAborted = (signal, fnName) => {
4402
+ if (signal?.aborted) throw signal.reason ?? /* @__PURE__ */ new Error(`${fnName} aborted`);
4403
+ };
4404
+ var throwIfDialog = (dialog, fnName) => {
4405
+ if (dialog) throw new Error(`[MForm] ${fnName}({ dialog: true }) is not available from @tmagic/form/headless. Import from @tmagic/form instead.`);
4406
+ };
4407
+ /**
4408
+ * `submitForm` 与 `validateForm` 共用的无渲染校验流程:中断检查 → `validateValues`。
4409
+ */
4410
+ var validateWithoutRender = async (fnName, options) => {
4411
+ const { signal, config, initValues, parentValues, keyProp, typeMatchValid, useFieldTextInError, context } = options;
4412
+ throwIfAborted(signal, fnName);
4413
+ const result = await validateValues({
4414
+ config,
4415
+ initValues,
4416
+ parentValues,
4417
+ keyProp,
4418
+ popperClass: options.popperClass,
4419
+ typeMatchValid,
4420
+ useFieldTextInError,
4421
+ context
4422
+ });
4423
+ throwIfAborted(signal, fnName);
4424
+ return result;
4425
+ };
4426
+ /**
4427
+ * 以命令式方式对一份「表单配置 + 值」做一次校验并取回表单值(无渲染)。
4428
+ *
4429
+ * `@tmagic/form/headless` 不支持 `dialog: true`。
4430
+ */
4431
+ var submitForm$1 = async (options) => {
4432
+ throwIfDialog(options.dialog, "submitForm");
4433
+ const validated = await validateWithoutRender("submitForm", options);
4434
+ if (validated.error) throw new Error(validated.error);
4435
+ const values = options.native ? validated.values : cloneDeep(validated.values);
4436
+ return options.returnChangeRecords ? {
4437
+ values,
4438
+ changeRecords: []
4439
+ } : values;
4440
+ };
4441
+ /**
4442
+ * 以命令式方式对一份「表单配置 + 值」做一次静默校验(无渲染)。
4443
+ *
4444
+ * `@tmagic/form/headless` 不支持 `dialog: true`。
4445
+ */
4446
+ var validateForm$1 = async (options) => {
4447
+ throwIfDialog(options.dialog, "validateForm");
4448
+ return (await validateWithoutRender("validateForm", options)).error;
3461
4449
  };
3462
4450
  //#endregion
3463
4451
  //#region packages/form/src/utils/useAddField.ts
@@ -3492,67 +4480,156 @@
3492
4480
  }
3493
4481
  });
3494
4482
  //#endregion
3495
- //#region packages/form/src/utils/config.ts
3496
- var $MAGIC_FORM = {};
3497
- var isGlobalFlat = (0, vue.ref)(false);
3498
- var setConfig = (option) => {
3499
- $MAGIC_FORM = option;
3500
- isGlobalFlat.value = option.flat ?? false;
4483
+ //#region packages/form/src/utils/registerField.ts
4484
+ var extraComponents = /* @__PURE__ */ new Map();
4485
+ var builtInComponents = /* @__PURE__ */ new Map();
4486
+ var applyVueComponent = (type, component, app, builtIn, kind) => {
4487
+ const key = (0, _tmagic_utils.toLine)(type);
4488
+ if (builtIn) builtInComponents.set(key, component);
4489
+ else extraComponents.set(key, component);
4490
+ app?.component(`m-${kind}-${key}`, component);
3501
4491
  };
3502
- var getConfig = (key) => $MAGIC_FORM[key];
3503
- var fieldRegistry = /* @__PURE__ */ new Map();
3504
- var registerField = (tagName, component) => {
3505
- if (fieldRegistry.has(tagName)) return;
3506
- fieldRegistry.set(tagName, component);
3507
- };
3508
- var getField = (tagName) => fieldRegistry.get(tagName);
3509
- var deleteField = (tagName) => fieldRegistry.delete(tagName);
3510
- //#endregion
3511
- //#region packages/form/src/utils/silentLeafFieldTypes.ts
3512
4492
  /**
3513
- * 可在静默校验模式下跳过渲染的内置叶子字段(默认集合)。
4493
+ * 把多份字段登记表按 type 浅合并。后一份只覆盖自己带了的 key,未出现的 key 保留前一份。
3514
4494
  *
3515
- * 这些字段只渲染一个输入控件,内部没有嵌套 FormItem,且不会依赖组件挂载修改 model;
3516
- * 字段值由初始化逻辑处理(包括 defaultValue),因此跳过不影响校验结果。
4495
+ * 安装插件时用:Node 侧只登记 `headless`,浏览器再补 `component` / `container`。
3517
4496
  *
3518
- * 业务侧可通过 `registerSilentLeafFieldTypes` 追加已审计的安全自定义字段。
4497
+ * @param tables - 待合并的登记表,`undefined` 会被跳过
4498
+ * @returns 合并后的登记表
3519
4499
  */
3520
- var LEAF_FIELD_TYPES = /* @__PURE__ */ new Set([
3521
- "text",
3522
- "textarea",
3523
- "number",
3524
- "time",
3525
- "daterange",
3526
- "timerange",
3527
- "checkbox",
3528
- "radio-group",
3529
- "switch",
3530
- "select",
3531
- "cascader",
3532
- "color-picker",
3533
- "link"
3534
- ]);
3535
- /** 业务侧追加的静默叶子字段(不含内置默认集合) */
3536
- var extraSilentLeafFieldTypes = /* @__PURE__ */ new Set();
4500
+ var mergeFieldOptions = (...tables) => {
4501
+ const result = {};
4502
+ for (const table of tables) {
4503
+ if (!table) continue;
4504
+ for (const [type, options] of Object.entries(table)) result[type] = {
4505
+ ...result[type],
4506
+ ...pickDefinedFieldOptions(options)
4507
+ };
4508
+ }
4509
+ return result;
4510
+ };
4511
+ var FIELD_OPTION_KEYS = [
4512
+ "component",
4513
+ "container",
4514
+ "effect",
4515
+ "walk",
4516
+ "innerConfig",
4517
+ "typeMatch"
4518
+ ];
4519
+ var pickDefinedFieldOptions = (options) => {
4520
+ if (!options) return {};
4521
+ const next = {};
4522
+ for (const key of FIELD_OPTION_KEYS) if (options[key] !== void 0) next[key] = options[key];
4523
+ return next;
4524
+ };
4525
+ var extraFieldOptions = /* @__PURE__ */ new Map();
4526
+ var builtInFieldOptions = /* @__PURE__ */ new Map();
4527
+ var removeFormComponent = (type) => {
4528
+ extraComponents.delete((0, _tmagic_utils.toLine)(type));
4529
+ };
4530
+ var clearFormComponents = () => {
4531
+ extraComponents.clear();
4532
+ };
3537
4533
  /**
3538
- * 注册可在静默校验中跳过渲染的叶子字段 type(追加到默认 `LEAF_FIELD_TYPES` 之上)。
4534
+ * 按字段 type 取已登记的渲染组件(`codeSelect` 与 `code-select` 等价)。
3539
4535
  *
3540
- * 仅应纳入「无嵌套 FormItem、无挂载值副作用」的字段;复合 / 动态子表单不可加入。
4536
+ * @param type - 字段 type
4537
+ * @returns 已登记的 Vue 组件;未登记则为 `undefined`
3541
4538
  */
3542
- var registerSilentLeafFieldTypes = (types) => {
3543
- for (const type of types) {
3544
- if (typeof type !== "string" || !type) continue;
3545
- extraSilentLeafFieldTypes.add((0, _tmagic_utils.toLine)(type));
4539
+ var getField = (type) => {
4540
+ const key = (0, _tmagic_utils.toLine)(type);
4541
+ return extraComponents.get(key) ?? builtInComponents.get(key);
4542
+ };
4543
+ var registerFieldImpl = (type, options, app, builtIn) => {
4544
+ if (typeof type !== "string" || !type) return;
4545
+ const key = (0, _tmagic_utils.toLine)(type);
4546
+ const store = builtIn ? builtInFieldOptions : extraFieldOptions;
4547
+ const incoming = pickDefinedFieldOptions(options);
4548
+ const merged = {
4549
+ ...store.get(key),
4550
+ ...incoming
4551
+ };
4552
+ store.set(key, merged);
4553
+ if (incoming.walk && (incoming.innerConfig || typeof incoming.effect === "function")) console.warn(`[MForm] registerField("${key}"): walk is set together with innerConfig/effect; headless validation will use walk and innerConfig/effect will be ignored.`);
4554
+ if (incoming.component && incoming.container) console.warn(`[MForm] registerField("${key}"): component and container are both set; getField will use container, and both m-fields-* / m-form-* will be registered.`);
4555
+ if (merged.component) applyVueComponent(type, merged.component, app, builtIn, "fields");
4556
+ if (merged.container) applyVueComponent(type, merged.container, app, builtIn, "form");
4557
+ if (merged.typeMatch) registerTypeMatchRule(type, merged.typeMatch, builtIn);
4558
+ if (merged.walk) {
4559
+ registerContainerWalker(type, merged.walk, builtIn);
4560
+ if (!builtIn) {
4561
+ deleteLeafFieldType(type);
4562
+ deleteFieldInnerConfig(type);
4563
+ }
4564
+ return;
4565
+ }
4566
+ if (!builtIn) deleteContainerWalker(type);
4567
+ if (merged.innerConfig) {
4568
+ registerFieldInnerConfig(type, merged.innerConfig, builtIn);
4569
+ if (typeof merged.effect === "function") registerLeafFieldType(type, merged.effect, builtIn);
4570
+ else if (!builtIn) deleteLeafFieldType(type);
4571
+ return;
4572
+ }
4573
+ if (merged.container && !merged.component && typeof merged.effect !== "function") {
4574
+ if (!builtIn) deleteLeafFieldType(type);
4575
+ return;
3546
4576
  }
4577
+ if (!builtIn) deleteFieldInnerConfig(type);
4578
+ registerLeafFieldType(type, merged.effect, builtIn);
4579
+ };
4580
+ /**
4581
+ * 登记一个字段 type 在无渲染校验里的行为,以及可选的渲染组件。
4582
+ *
4583
+ * `type` 会按 Container 的规则归一化为中划线形式(`codeSelect` 与 `code-select` 等价)。
4584
+ * 对同一 type 重复登记按字段浅合并:后一次只覆盖自己传入的 key,未传入的 key 保留。
4585
+ * 传入 `app` 且带了 `component` / `container` 时,会同步 `app.component('m-fields-*'` / `'m-form-*')`。
4586
+ *
4587
+ * @param type - 字段 type
4588
+ * @param [options] - 登记项;省略或空对象视为叶子
4589
+ * @param [app] - Vue 应用;省略则不调用 `app.component`
4590
+ */
4591
+ var registerField = (type, options, app) => {
4592
+ registerFieldImpl(type, options, app, false);
4593
+ };
4594
+ /**
4595
+ * 批量登记字段。
4596
+ *
4597
+ * @param fields - type 到登记项的映射
4598
+ * @param [app] - Vue 应用;省略则不调用 `app.component`
4599
+ */
4600
+ var registerFields = (fields, app) => {
4601
+ for (const [type, options] of Object.entries(fields)) registerFieldImpl(type, options, app, false);
4602
+ };
4603
+ /**
4604
+ * 登记内置字段(`clearFields` / `unregisterField` 不会清掉)。
4605
+ *
4606
+ * @param fields - type 到登记项的映射
4607
+ * @param [app] - Vue 应用;省略则不调用 `app.component`
4608
+ */
4609
+ var registerBuiltInFields = (fields, app) => {
4610
+ for (const [type, options] of Object.entries(fields)) registerFieldImpl(type, options, app, true);
3547
4611
  };
3548
- /** 当前生效的静默叶子字段集合:内置默认 ∪ 已注册扩展 */
3549
- var getSilentLeafFieldTypes = () => {
3550
- if (extraSilentLeafFieldTypes.size === 0) return LEAF_FIELD_TYPES;
3551
- return /* @__PURE__ */ new Set([...LEAF_FIELD_TYPES, ...extraSilentLeafFieldTypes]);
4612
+ /**
4613
+ * 删除业务侧对指定 type 的登记(不影响内置;主要用于单测)。
4614
+ *
4615
+ * @param type - 字段 type
4616
+ */
4617
+ var unregisterField = (type) => {
4618
+ extraFieldOptions.delete((0, _tmagic_utils.toLine)(type));
4619
+ deleteLeafFieldType(type);
4620
+ deleteFieldInnerConfig(type);
4621
+ deleteTypeMatchRule(type);
4622
+ deleteContainerWalker(type);
4623
+ removeFormComponent(type);
3552
4624
  };
3553
- /** 清空业务侧追加的静默叶子字段(不影响内置默认集合;主要用于单测) */
3554
- var clearSilentLeafFieldTypes = () => {
3555
- extraSilentLeafFieldTypes.clear();
4625
+ /** 清空业务侧登记(不影响内置;主要用于单测)。 */
4626
+ var clearFields = () => {
4627
+ extraFieldOptions.clear();
4628
+ clearLeafFieldTypes();
4629
+ clearFieldInnerConfigs();
4630
+ clearTypeMatchRules();
4631
+ clearContainerWalkers();
4632
+ clearFormComponents();
3556
4633
  };
3557
4634
  //#endregion
3558
4635
  //#region packages/form/src/containers/FormLabel.vue?vue&type=script&setup=true&lang.ts
@@ -3612,7 +4689,7 @@
3612
4689
  };
3613
4690
  var _hoisted_3$7 = ["innerHTML"];
3614
4691
  var _hoisted_4$6 = ["innerHTML"];
3615
- var _hoisted_5$4 = ["innerHTML"];
4692
+ var _hoisted_5$5 = ["innerHTML"];
3616
4693
  var _hoisted_6$3 = ["innerHTML"];
3617
4694
  var _hoisted_7$2 = ["innerHTML"];
3618
4695
  var _hoisted_8$2 = ["innerHTML"];
@@ -3633,6 +4710,7 @@
3633
4710
  prop: { default: "" },
3634
4711
  disabled: { type: Boolean },
3635
4712
  labelWidth: {},
4713
+ labelPosition: {},
3636
4714
  expandMore: {
3637
4715
  type: Boolean,
3638
4716
  default: false
@@ -3668,20 +4746,8 @@
3668
4746
  return !isEqual(curValue, lastValue);
3669
4747
  });
3670
4748
  const items = (0, vue.computed)(() => props.config.items);
3671
- const itemProp = (0, vue.computed)(() => {
3672
- let n = "";
3673
- if (name.value) n = name.value;
3674
- else return props.prop;
3675
- if (typeof props.prop !== "undefined" && props.prop !== "") return `${props.prop}.${n}`;
3676
- return `${n}`;
3677
- });
3678
- const type = (0, vue.computed)(() => {
3679
- let type = "type" in props.config ? props.config.type : "";
3680
- type = type && filterFunction(mForm, type, props);
3681
- if (type === "form") return "";
3682
- if (type === "container") return "";
3683
- return type?.replace(/([A-Z])/g, "-$1").toLowerCase() || (items.value ? "" : "text");
3684
- });
4749
+ const itemProp = (0, vue.computed)(() => getItemProp(props.prop, name.value));
4750
+ const type = (0, vue.computed)(() => resolveItemType(mForm, props.config, props));
3685
4751
  const tagName = (0, vue.computed)(() => {
3686
4752
  if (type.value === "component" && props.config.component) return props.config.component;
3687
4753
  return getField(type.value || "container") || `m-${items.value ? "form" : "fields"}-${type.value}`;
@@ -3715,8 +4781,6 @@
3715
4781
  return new Set(DEFAULT_SELF_DIFF_FIELD_TYPES);
3716
4782
  });
3717
4783
  const isSelfDiffField = (0, vue.computed)(() => effectiveSelfDiffFieldTypes.value.has(type.value));
3718
- const silentMode = (0, vue.inject)(FORM_SILENT_MODE_KEY, false);
3719
- const shouldRenderLeafField = (0, vue.computed)(() => !silentMode || !getSilentLeafFieldTypes().has(type.value));
3720
4784
  const disabled = (0, vue.computed)(() => props.disabled || filterFunction(mForm, props.config.disabled, props));
3721
4785
  const text = (0, vue.computed)(() => filterFunction(mForm, props.config.text, props));
3722
4786
  const tooltip = (0, vue.computed)(() => {
@@ -3748,12 +4812,13 @@
3748
4812
  const formItemProps = (0, vue.computed)(() => ({
3749
4813
  prop: itemProp.value,
3750
4814
  labelWidth: itemLabelWidth.value,
3751
- labelPosition: props.config.labelPosition,
4815
+ labelPosition: itemLabelPosition.value,
3752
4816
  rules: rule.value,
3753
4817
  extra: filterFunction(mForm, props.config.extra, props),
3754
4818
  extraTips: props.config.extraTips
3755
4819
  }));
3756
4820
  const itemLabelWidth = (0, vue.computed)(() => props.config.labelWidth ?? props.labelWidth);
4821
+ const itemLabelPosition = (0, vue.computed)(() => props.config.labelPosition ?? props.labelPosition);
3757
4822
  (0, vue.watchEffect)(() => {
3758
4823
  expand.value = props.expandMore;
3759
4824
  });
@@ -3782,13 +4847,7 @@
3782
4847
  };
3783
4848
  const onAddDiffCount = () => emit("addDiffCount");
3784
4849
  const hasModifyKey = (eventDataItem) => typeof eventDataItem?.modifyKey !== "undefined" && eventDataItem.modifyKey !== "";
3785
- const isValidName = () => {
3786
- const valueType = typeof name.value;
3787
- if (valueType !== "string" && valueType !== "symbol" && valueType !== "number") return false;
3788
- if (name.value === "") return false;
3789
- if (typeof name.value === "number") return name.value >= 0;
3790
- return true;
3791
- };
4850
+ const isValidName$1 = () => isValidName(name.value);
3792
4851
  const createModelProxy = (target, setModelFn, pathPrefix = "") => {
3793
4852
  return new Proxy(target, {
3794
4853
  get: (obj, key) => {
@@ -3880,6 +4939,7 @@
3880
4939
  "step-active": __props.stepActive,
3881
4940
  "expand-more": expand.value,
3882
4941
  "label-width": itemLabelWidth.value,
4942
+ "label-position": itemLabelPosition.value,
3883
4943
  style: __props.config.fieldStyle,
3884
4944
  onChange: onChangeHandler,
3885
4945
  onAddDiffCount
@@ -3890,9 +4950,10 @@
3890
4950
  "step-active",
3891
4951
  "expand-more",
3892
4952
  "label-width",
4953
+ "label-position",
3893
4954
  "style"
3894
4955
  ])) : type.value && display$3.value && !showDiff.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicFormItem), (0, vue.mergeProps)(formItemProps.value, { class: { "tmagic-form-hidden": `${itemLabelWidth.value}` === "0" || !text.value } }), {
3895
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
4956
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
3896
4957
  config: __props.config,
3897
4958
  type: type.value,
3898
4959
  text: text.value,
@@ -3910,8 +4971,8 @@
3910
4971
  "use-label",
3911
4972
  "label-title",
3912
4973
  "text"
3913
- ]), __props.config.titleExtra && __props.config.labelPosition === "top" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_2$8, (0, vue.toDisplayString)(__props.config.titleExtra), 1)) : (0, vue.createCommentVNode)("v-if", true)], void 0, 0) : (0, vue.createCommentVNode)("v-if", true)]),
3914
- default: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4974
+ ]), __props.config.titleExtra && itemLabelPosition.value === "top" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_2$8, (0, vue.toDisplayString)(__props.config.titleExtra), 1)) : (0, vue.createCommentVNode)("v-if", true)])]),
4975
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
3915
4976
  key: 0,
3916
4977
  placement: tooltip.value.placement
3917
4978
  }, {
@@ -3938,9 +4999,9 @@
3938
4999
  "model",
3939
5000
  "last-values",
3940
5001
  "is-compare"
3941
- ]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5002
+ ]))]),
3942
5003
  _: 3
3943
- }, 16, ["class"]), shouldRenderLeafField.value && __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
5004
+ }, 16, ["class"]), __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
3944
5005
  key: 0,
3945
5006
  placement: "top"
3946
5007
  }, {
@@ -3960,7 +5021,7 @@
3960
5021
  "tmagic-form-hidden": `${itemLabelWidth.value}` === "0" || !text.value,
3961
5022
  "self-diff": true
3962
5023
  } }), {
3963
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
5024
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
3964
5025
  config: __props.config,
3965
5026
  type: type.value,
3966
5027
  text: text.value,
@@ -3978,12 +5039,12 @@
3978
5039
  "use-label",
3979
5040
  "label-title",
3980
5041
  "text"
3981
- ])], void 0, 0) : (0, vue.createCommentVNode)("v-if", true)]),
3982
- default: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
5042
+ ])])]),
5043
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
3983
5044
  key: 0,
3984
5045
  placement: tooltip.value.placement
3985
5046
  }, {
3986
- content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$4)]),
5047
+ content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$5)]),
3987
5048
  default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)(fieldsProps.value, {
3988
5049
  model: __props.model,
3989
5050
  "last-values": __props.lastValues,
@@ -4004,7 +5065,7 @@
4004
5065
  "model",
4005
5066
  "last-values",
4006
5067
  "is-compare"
4007
- ]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5068
+ ]))]),
4008
5069
  _: 3
4009
5070
  }, 16, ["class"])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
4010
5071
  (0, vue.createCommentVNode)(" 普通字段:渲染前后两份独立的组件用于对比 "),
@@ -4013,7 +5074,7 @@
4013
5074
  "tmagic-form-hidden": `${itemLabelWidth.value}` === "0" || !text.value,
4014
5075
  "show-before-diff": true
4015
5076
  } }), {
4016
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
5077
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
4017
5078
  config: __props.config,
4018
5079
  type: type.value,
4019
5080
  text: text.value,
@@ -4031,8 +5092,8 @@
4031
5092
  "use-label",
4032
5093
  "label-title",
4033
5094
  "text"
4034
- ])], void 0, 0) : (0, vue.createCommentVNode)("v-if", true)]),
4035
- default: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
5095
+ ])])]),
5096
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4036
5097
  key: 0,
4037
5098
  placement: tooltip.value.placement
4038
5099
  }, {
@@ -4045,10 +5106,10 @@
4045
5106
  }, 8, ["placement"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)({ key: 1 }, fieldsProps.value, {
4046
5107
  model: __props.lastValues,
4047
5108
  onChange: onChangeHandler
4048
- }), null, 16, ["model"]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5109
+ }), null, 16, ["model"]))]),
4049
5110
  _: 3
4050
5111
  }, 16, ["class"]),
4051
- shouldRenderLeafField.value && __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
5112
+ __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4052
5113
  key: 0,
4053
5114
  placement: "top"
4054
5115
  }, {
@@ -4070,7 +5131,7 @@
4070
5131
  "show-after-diff": true
4071
5132
  }
4072
5133
  }), {
4073
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
5134
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
4074
5135
  config: __props.config,
4075
5136
  type: type.value,
4076
5137
  text: text.value,
@@ -4088,8 +5149,8 @@
4088
5149
  "use-label",
4089
5150
  "label-title",
4090
5151
  "text"
4091
- ])], void 0, 0) : (0, vue.createCommentVNode)("v-if", true)]),
4092
- default: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
5152
+ ])])]),
5153
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4093
5154
  key: 0,
4094
5155
  placement: tooltip.value.placement
4095
5156
  }, {
@@ -4102,10 +5163,10 @@
4102
5163
  }, 8, ["placement"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)({ key: 1 }, fieldsProps.value, {
4103
5164
  model: __props.model,
4104
5165
  onChange: onChangeHandler
4105
- }), null, 16, ["model"]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5166
+ }), null, 16, ["model"]))]),
4106
5167
  _: 3
4107
5168
  }, 16, ["style", "class"]),
4108
- shouldRenderLeafField.value && __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
5169
+ __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4109
5170
  key: 1,
4110
5171
  placement: "top"
4111
5172
  }, {
@@ -4120,11 +5181,11 @@
4120
5181
  _: 1
4121
5182
  })) : (0, vue.createCommentVNode)("v-if", true)
4122
5183
  ], 64))
4123
- ], 64)) : items.value && display$3.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 4 }, [(isValidName() ? __props.model[name.value] : __props.model) ? ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, (0, vue.renderList)(items.value, (item) => {
5184
+ ], 64)) : items.value && display$3.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 4 }, [(isValidName$1() ? __props.model[name.value] : __props.model) ? ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, (0, vue.renderList)(items.value, (item) => {
4124
5185
  return (0, vue.openBlock)(), (0, vue.createBlock)(_component_Container, {
4125
5186
  key: key(item),
4126
- model: isValidName() ? __props.model[name.value] : __props.model,
4127
- "last-values": isValidName() ? __props.lastValues[name.value] || {} : __props.lastValues,
5187
+ model: isValidName$1() ? __props.model[name.value] : __props.model,
5188
+ "last-values": isValidName$1() ? __props.lastValues[name.value] || {} : __props.lastValues,
4128
5189
  "is-compare": __props.isCompare,
4129
5190
  config: item,
4130
5191
  size: __props.size,
@@ -4132,6 +5193,7 @@
4132
5193
  "step-active": __props.stepActive,
4133
5194
  "expand-more": expand.value,
4134
5195
  "label-width": itemLabelWidth.value,
5196
+ "label-position": itemLabelPosition.value,
4135
5197
  prop: itemProp.value,
4136
5198
  onChange: onChangeHandler,
4137
5199
  onAddDiffCount
@@ -4149,6 +5211,7 @@
4149
5211
  "step-active",
4150
5212
  "expand-more",
4151
5213
  "label-width",
5214
+ "label-position",
4152
5215
  "prop"
4153
5216
  ]);
4154
5217
  }), 128)) : (0, vue.createCommentVNode)("v-if", true)], 64)) : (0, vue.createCommentVNode)("v-if", true), __props.config.expand && type.value !== "fieldset" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_10, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
@@ -4204,7 +5267,7 @@
4204
5267
  type: Boolean,
4205
5268
  default: true
4206
5269
  },
4207
- extendState: {},
5270
+ context: {},
4208
5271
  showDiff: {},
4209
5272
  selfDiffFieldTypes: {},
4210
5273
  theme: {}
@@ -4224,8 +5287,6 @@
4224
5287
  const initialized = (0, vue.ref)(false);
4225
5288
  const values = (0, vue.ref)({});
4226
5289
  const lastValuesProcessed = (0, vue.ref)({});
4227
- const fields = /* @__PURE__ */ new Map();
4228
- const requestFuc = getConfig("request");
4229
5290
  /**
4230
5291
  * 当前表单生效的主题名称:
4231
5292
  * - 优先用本组件自己的 `props.theme`;
@@ -4255,18 +5316,14 @@
4255
5316
  * 2. `values` / `lastValuesProcessed` 是 ref,Vue 的 `reactive` 会自动解包,因此每次
4256
5317
  * 访问 `formState.values` / `formState.lastValuesProcessed` 也都是当前 ref 值。
4257
5318
  *
4258
- * 3. `extendState` 注入的字段在下方的 `watchEffect` 中合并到 `formState`:
4259
- * - data 描述符(普通字段)通过 `formState[key] = value` 写入,走 reactive proxy 的
4260
- * set,触发依赖通知;`extendState` 同步段读到的响应式数据变化时会自动重跑,
4261
- * 把最新值刷进 formState。
4262
- * - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
4263
- * 读时求值,每次读取都会重新执行 getter。
5319
+ * 3. 宿主业务上下文不再 merge 进 coreState,而是单独放在 `contextRef` 上,由读穿
5320
+ * Proxy 在 miss 时落到 context。核心字段结构性优先,`mForm.xxx` 永久兼容旧读法。
4264
5321
  *
4265
5322
  * 4. `popperClass` 会自动拼接 `themeClass`:调用方传入的 `popperClass` + 当前主题
4266
5323
  * 修饰类(含祖先 `<MEditor>` provide 的主题)。这样所有透传到 Element Plus 弹层
4267
5324
  * `popper-class` 的字段(Select / DateTime / Cascader 等)能自带主题作用域。
4268
5325
  */
4269
- const formState = (0, vue.reactive)({
5326
+ const coreState = (0, vue.reactive)({
4270
5327
  get keyProp() {
4271
5328
  return props.keyProp;
4272
5329
  },
@@ -4295,68 +5352,20 @@
4295
5352
  values,
4296
5353
  lastValuesProcessed,
4297
5354
  $emit: emit,
4298
- fields,
4299
- setField: (prop, field) => fields.set(prop, field),
4300
- getField: (prop) => fields.get(prop),
4301
- deleteField: (prop) => fields.delete(prop),
4302
- $messageBox: _tmagic_design.tMagicMessageBox,
4303
- $message: _tmagic_design.tMagicMessage,
4304
- post: (options) => {
4305
- if (requestFuc) return requestFuc({
4306
- method: "POST",
4307
- ...options
4308
- });
4309
- }
5355
+ ...createFormStateBase({
5356
+ $message: _tmagic_design.tMagicMessage,
5357
+ $messageBox: _tmagic_design.tMagicMessageBox
5358
+ })
4310
5359
  });
5360
+ const ancestorContext = (0, vue.inject)(FORM_CONTEXT_KEY, void 0);
4311
5361
  /**
4312
- * formState 的内置 key 快照(keyProp / values / $emit / fields / post 等)。
4313
- *
4314
- * 在 `extendState` 首次合并前捕获,`applyExtendState` 会据此禁止 `extendState`
4315
- * 覆盖这些已有字段(只能新增字段),避免表单核心状态被外部意外改写。
4316
- *
4317
- * 之所以在此处(effect 之外)捕获而不是在 `applyExtendState` 内动态取:
4318
- * `watchEffect` 会在依赖变化时重跑,若动态取,`extendState` 自己新增的字段在第二次
4319
- * 合并时也会被当成「已有 key」而拒绝刷新;这里只锁定内置字段即可规避该问题。
5362
+ * 宿主业务上下文:`props.context` 覆盖祖先注入的同名字段。
5363
+ * 嵌套表单(Link / FormBox / FormDialog)通过下面的 provide 自动继承。
4320
5364
  */
4321
- const reservedStateKeys = new Set(Reflect.ownKeys(formState));
4322
- /**
4323
- * `extendState` 的同步段(直到第一个 `await` 之前)所访问的任何响应式数据,
4324
- * 都会被 `watchEffect` 自动跟踪。这样可以兼容历史用法 ——
4325
- *
4326
- * extendState: (formState) => ({
4327
- * username: store.username, // 同步读 store,会被跟踪
4328
- * env: store.env,
4329
- * })
4330
- *
4331
- * 当 `store.username` 变化时,整个 effect 重跑,新值会被刷进 `formState`。
4332
- *
4333
- * prop 派生字段(initValues / config / ...)已经在上方用 getter 定义,
4334
- * 这里不再重复同步;因此 `props.initValues` 这类高频变化也不会再触发
4335
- * `extendState` 重跑(旧版的性能问题修复点)。
4336
- *
4337
- * 实现细节:合并逻辑统一收口在 `applyExtendState`(utils/form)——
4338
- * data 描述符走 reactive proxy 的 set 触发依赖通知(与旧版「逐项赋值」语义等价),
4339
- * accessor 描述符按原样 defineProperty 支持读时求值;
4340
- * props 派生的只读 getter 字段(keyProp 等)以普通字段形式返回时会被跳过并告警。
4341
- */
4342
- (0, vue.watchEffect)(async (onCleanup) => {
4343
- const { extendState } = props;
4344
- if (typeof extendState !== "function") return;
4345
- let stale = false;
4346
- onCleanup(() => {
4347
- stale = true;
4348
- });
4349
- let state = {};
4350
- try {
4351
- state = await extendState(formState) || {};
4352
- } catch (e) {
4353
- console.error("[MForm] extendState failed:", e);
4354
- return;
4355
- }
4356
- if (stale) return;
4357
- applyExtendState(formState, state, reservedStateKeys);
4358
- });
5365
+ const contextRef = (0, vue.computed)(() => mergeFormContexts(ancestorContext?.value, props.context));
5366
+ const formState = createFormStateProxy(coreState, () => contextRef.value);
4359
5367
  (0, vue.provide)("mForm", formState);
5368
+ (0, vue.provide)(FORM_CONTEXT_KEY, contextRef);
4360
5369
  /**
4361
5370
  * 把生效主题(自身或祖先)再 provide 出去,供 form 子树内含 `Teleport` 的组件
4362
5371
  * (如 `TMagicPopover`)在传送目标上挂 `m-theme--<theme>` 类。
@@ -4373,14 +5382,24 @@
4373
5382
  }
4374
5383
  });
4375
5384
  const changeRecords = (0, vue.shallowRef)([]);
4376
- (0, vue.watch)([() => props.config, () => props.initValues], ([config], [preConfig]) => {
5385
+ /**
5386
+ * 两份配置的结构是否一致;函数一律视为相等。
5387
+ *
5388
+ * 宿主(如编辑器属性面板)往往在每次节点更新后整份重新生成配置,其中的
5389
+ * `display` / `options` / `onChange` 都是新闭包,深比较必然判不等。若据此把 `initialized`
5390
+ * 置 false,整棵表单会卸载重挂,滚动位置、展开态、输入焦点全部丢失。
5391
+ * 配置是响应式 prop,闭包换了照样生效,只有结构变化(增删字段、换组件类型)才需要重挂。
5392
+ */
5393
+ const isSameConfigShape = (config, preConfig) => isEqualWith(config, preConfig, (a, b) => typeof a === "function" && typeof b === "function" ? true : void 0);
5394
+ (0, vue.watch)([() => props.config, () => props.initValues], async ([config], [preConfig]) => {
4377
5395
  changeRecords.value = [];
4378
- if (!isEqual((0, vue.toRaw)(config), (0, vue.toRaw)(preConfig))) initialized.value = false;
5396
+ if (!isSameConfigShape((0, vue.toRaw)(config), (0, vue.toRaw)(preConfig))) initialized.value = false;
4379
5397
  initValue(formState, {
4380
5398
  initValues: props.initValues,
4381
5399
  config: props.config
4382
5400
  }).then((value) => {
4383
5401
  values.value = value;
5402
+ applyMountValueEffects(formState, props.config, values.value);
4384
5403
  initialized.value = !props.isCompare;
4385
5404
  if (props.validateOnInit) (0, vue.nextTick)(() => {
4386
5405
  tMagicFormRef.value?.validate();
@@ -4391,6 +5410,7 @@
4391
5410
  config: props.config
4392
5411
  }).then((value) => {
4393
5412
  lastValuesProcessed.value = value;
5413
+ applyMountValueEffects(formState, props.config, lastValuesProcessed.value);
4394
5414
  initialized.value = true;
4395
5415
  });
4396
5416
  }, { immediate: true });
@@ -4408,51 +5428,17 @@
4408
5428
  const submitHandler = (e) => {
4409
5429
  if (props.preventSubmitDefault) e.preventDefault();
4410
5430
  };
4411
- /**
4412
- * 通过 name 从 config 中查找对应的 text
4413
- * @param name - 字段名,支持点分隔的路径格式,如 'a.b.c'
4414
- * @param config - 表单配置数组
4415
- * @returns 找到的 text 值,如果未找到则返回 undefined
4416
- */
4417
- const getTextByName = (name, config = props.config) => {
4418
- if (!name || !Array.isArray(config)) return void 0;
4419
- const nameParts = name.split(".");
4420
- const findInConfig = (configs, parts) => {
4421
- if (parts.length === 0) return void 0;
4422
- const [currentPart, ...remainingParts] = parts;
4423
- for (const item of configs) {
4424
- if (item.name === currentPart) {
4425
- if (remainingParts.length === 0) return typeof item.text === "string" ? item.text : void 0;
4426
- if ("items" in item && Array.isArray(item.items)) {
4427
- const result = findInConfig(item.items, remainingParts);
4428
- if (result !== void 0) return result;
4429
- }
4430
- }
4431
- if ("items" in item && Array.isArray(item.items)) {
4432
- const result = findInConfig(item.items, parts);
4433
- if (result !== void 0) return result;
4434
- }
4435
- }
4436
- };
4437
- return findInConfig(config, nameParts);
4438
- };
5431
+ const getTextByName$1 = (name, config = props.config) => getTextByName(name, config);
4439
5432
  /**
4440
5433
  * 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
4441
5434
  *
4442
- * 抽离为独立方法,供 `submitForm`(提交校验)与 `validate`(返回错误文案的校验)复用,
4443
- * 保证两种校验入口产出的错误文案格式完全一致。
5435
+ * 实现收口在 `utils/validateError`,供渲染式校验(本组件的 `submitForm` / `validate`)与
5436
+ * 无渲染校验(`validateValues`)共用,保证两条链路产出的错误文案格式完全一致。
4444
5437
  */
4445
- const formatValidateError = (invalidFields) => {
4446
- const error = [];
4447
- Object.entries(invalidFields).forEach(([prop, validateError]) => {
4448
- validateError.forEach(({ field, message }) => {
4449
- const name = field || prop;
4450
- const text = (props.useFieldTextInError ? getTextByName(name, props.config) : void 0) || name;
4451
- error.push(`${text} -> ${message}`);
4452
- });
4453
- });
4454
- return error.join("<br>");
4455
- };
5438
+ const formatValidateError$1 = (invalidFields) => formatValidateError(invalidFields, {
5439
+ config: props.config,
5440
+ useFieldTextInError: props.useFieldTextInError
5441
+ });
4456
5442
  __expose({
4457
5443
  values,
4458
5444
  lastValuesProcessed,
@@ -4472,7 +5458,7 @@
4472
5458
  return native ? values.value : cloneDeep((0, vue.toRaw)(values.value));
4473
5459
  } catch (invalidFields) {
4474
5460
  emit("error", invalidFields);
4475
- throw new Error(formatValidateError(invalidFields));
5461
+ throw new Error(formatValidateError$1(invalidFields));
4476
5462
  }
4477
5463
  },
4478
5464
  /**
@@ -4497,10 +5483,10 @@
4497
5483
  if (result !== true) throw result;
4498
5484
  return "";
4499
5485
  } catch (invalidFields) {
4500
- return formatValidateError(invalidFields);
5486
+ return formatValidateError$1(invalidFields);
4501
5487
  }
4502
5488
  },
4503
- getTextByName
5489
+ getTextByName: getTextByName$1
4504
5490
  });
4505
5491
  return (_ctx, _cache) => {
4506
5492
  return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicForm), {
@@ -4526,6 +5512,7 @@
4526
5512
  "last-values": lastValuesProcessed.value,
4527
5513
  "is-compare": __props.isCompare,
4528
5514
  "label-width": item.labelWidth || __props.labelWidth,
5515
+ "label-position": item.labelPosition || __props.labelPosition,
4529
5516
  "step-active": __props.stepActive,
4530
5517
  size: __props.size,
4531
5518
  onChange: changeHandler
@@ -4540,6 +5527,7 @@
4540
5527
  "last-values",
4541
5528
  "is-compare",
4542
5529
  "label-width",
5530
+ "label-position",
4543
5531
  "step-active",
4544
5532
  "size"
4545
5533
  ]);
@@ -4558,38 +5546,29 @@
4558
5546
  });
4559
5547
  //#endregion
4560
5548
  //#region packages/form/src/submitForm.ts
4561
- /** 未指定或传入非正数 timeout 时的兜底超时(毫秒),保证非 debug 挂载始终能被清理 */
4562
- var DEFAULT_MOUNT_TIMEOUT = 1e4;
4563
5549
  /**
4564
- * submitForm / validateForm 的公共脚手架:
5550
+ * `dialog: true` 时 submitForm / validateForm 的公共脚手架:
4565
5551
  *
4566
- * 创建临时容器 → 挂载一个 wrapper 组件(内含 MForm)→ 提供统一的 cleanup / timeout / appContext 继承 →
5552
+ * 创建临时容器 → 挂载一个 wrapper 组件(内含 MForm)→ 提供统一的 cleanup / appContext 继承 →
4567
5553
  * 由 `createWrapper` 决定「何时调用 MForm 的哪个方法、如何 resolve/reject」。
4568
5554
  *
4569
- * 调用方只需关注差异逻辑(调用 `submitForm` 还是 `validate`、结果如何包装、是否需要调试弹层),
4570
- * 容器创建、卸载、超时、上下文注入等模板代码在此统一收口。
5555
+ * 挂载出来的表单等待人工点击,因此没有超时兜底,靠「确定」/「取消」/`signal` 结束。
4571
5556
  */
4572
5557
  var mountFormInstance = (options) => {
4573
- const { formProps, appContext, timeout, timeoutMessage, hidden = true, skipTimeout = false, signal, createWrapper } = options;
5558
+ const { formProps, appContext, signal, createWrapper } = options;
4574
5559
  return new Promise((resolve, reject) => {
4575
5560
  if (signal?.aborted) {
4576
5561
  reject(signal.reason ?? /* @__PURE__ */ new Error("mountFormInstance aborted"));
4577
5562
  return;
4578
5563
  }
4579
5564
  let cleaned = false;
4580
- let timer = null;
4581
5565
  let onAbort = null;
4582
5566
  const instance = { app: null };
4583
5567
  const container = document.createElement("div");
4584
- if (hidden) container.style.display = "none";
4585
5568
  document.body.appendChild(container);
4586
5569
  const cleanup = () => {
4587
5570
  if (cleaned) return;
4588
5571
  cleaned = true;
4589
- if (timer) {
4590
- clearTimeout(timer);
4591
- timer = null;
4592
- }
4593
5572
  if (signal && onAbort) {
4594
5573
  signal.removeEventListener("abort", onAbort);
4595
5574
  onAbort = null;
@@ -4609,53 +5588,16 @@
4609
5588
  }
4610
5589
  try {
4611
5590
  const formRef = (0, vue.ref)(null);
4612
- const { extendState, ...restFormProps } = formProps;
4613
- const userWrapper = createWrapper({
5591
+ const wrapperComponent = createWrapper({
4614
5592
  formRef,
4615
- formProps: restFormProps,
5593
+ formProps,
4616
5594
  cleanup,
4617
5595
  resolve,
4618
5596
  reject
4619
5597
  });
4620
- const wrapperComponent = typeof extendState === "function" ? (0, vue.defineComponent)({
4621
- name: "MFormExtendStateInjector",
4622
- setup() {
4623
- (0, vue.watch)(() => formRef.value, (form) => {
4624
- if (!form) return;
4625
- let result;
4626
- try {
4627
- result = extendState(form.formState);
4628
- } catch (e) {
4629
- console.error("[MForm] extendState failed:", e);
4630
- return;
4631
- }
4632
- const reservedStateKeys = new Set(Reflect.ownKeys(form.formState));
4633
- const apply = (state) => applyExtendState(form.formState, state, reservedStateKeys);
4634
- if (result && typeof result.then === "function") result.then(apply, (e) => console.error("[MForm] extendState failed:", e));
4635
- else apply(result);
4636
- }, {
4637
- flush: "sync",
4638
- immediate: true
4639
- });
4640
- return () => (0, vue.h)(userWrapper);
4641
- }
4642
- }) : userWrapper;
4643
- const rootComponent = hidden ? (0, vue.defineComponent)({
4644
- name: "MFormSilentProvider",
4645
- setup() {
4646
- (0, vue.provide)(FORM_SILENT_MODE_KEY, true);
4647
- return () => (0, vue.h)(wrapperComponent);
4648
- }
4649
- }) : wrapperComponent;
4650
- const app = (0, vue.createApp)(rootComponent);
5598
+ const app = (0, vue.createApp)(wrapperComponent);
4651
5599
  instance.app = app;
4652
5600
  if (appContext) Object.assign(app._context, appContext);
4653
- if (!skipTimeout) timer = setTimeout(() => {
4654
- if (!cleaned) {
4655
- reject(new Error(timeoutMessage));
4656
- cleanup();
4657
- }
4658
- }, timeout > 0 ? timeout : DEFAULT_MOUNT_TIMEOUT);
4659
5601
  app.mount(container);
4660
5602
  } catch (err) {
4661
5603
  reject(err);
@@ -4664,12 +5606,12 @@
4664
5606
  });
4665
5607
  };
4666
5608
  /**
4667
- * 构造调试模式下的可见弹层 wrapper:以 fixed 遮罩居中渲染 MForm,提供「确定 / 取消」按钮与错误展示区。
5609
+ * 构造可见弹层 wrapper:以 fixed 遮罩居中渲染 MForm,提供「确定 / 取消」按钮与错误展示区。
4668
5610
  *
4669
- * submitForm 与 validateForm 的调试弹层 UI 完全一致,仅「确定」时调用的实例方法、错误处理与取消文案不同,
5611
+ * submitForm 与 validateForm 的弹层 UI 完全一致,仅「确定」时调用的实例方法、错误处理与取消文案不同,
4670
5612
  * 这些差异通过 `onConfirm` / `onCancel` 注入,弹层结构在此统一收口。
4671
5613
  */
4672
- var createDebugWrapper = (options) => {
5614
+ var createDialogWrapper = (options) => {
4673
5615
  const { formRef, formProps, title, name, onConfirm, onCancel } = options;
4674
5616
  const btnBase = {
4675
5617
  padding: "8px 20px",
@@ -4752,57 +5694,17 @@
4752
5694
  });
4753
5695
  };
4754
5696
  /**
4755
- * 以命令式方式调用 Form.vue 完成一次表单校验/提交。
5697
+ * 可见弹层提交:把表单渲染在弹层里,点击「确定」才调用挂载实例的 `submitForm`。
4756
5698
  *
4757
- * 类似 ElMessage 的用法:传入 props(包含 `config`/`initValues` 等),函数内部会临时挂载
4758
- * 一个不可见的 Form 组件实例,等待初始化完成后调用其 `submitForm` 方法,
4759
- * 校验通过则 resolve 表单值,校验失败则 reject 错误信息,最后自动卸载实例。
4760
- *
4761
- * @example
4762
- * ```ts
4763
- * import { submitForm } from '@tmagic/form';
4764
- *
4765
- * try {
4766
- * const values = await submitForm({
4767
- * config: [...],
4768
- * initValues: { name: 'foo' },
4769
- * });
4770
- * console.log(values);
4771
- * } catch (e) {
4772
- * console.error(e);
4773
- * }
4774
- *
4775
- * // 需要同时获取变更记录时:
4776
- * const { values, changeRecords } = await submitForm({
4777
- * config: [...],
4778
- * initValues: { name: 'foo' },
4779
- * returnChangeRecords: true,
4780
- * });
4781
- *
4782
- * // 调试模式:可见地渲染表单,点击「确定」才提交:
4783
- * const values = await submitForm({
4784
- * config: [...],
4785
- * initValues: { name: 'foo' },
4786
- * debug: true,
4787
- * });
4788
- * ```
5699
+ * 用于需要用户填写/确认的场景;默认路径仍是无渲染校验(见 `submitForm` 的说明)。
4789
5700
  */
4790
- var submitForm = (options) => {
4791
- const { native, appContext, timeout = 1e4, returnChangeRecords, debug = false, signal, ...formProps } = options;
5701
+ var submitFormByDialogRender = (options) => {
5702
+ const { native, appContext, returnChangeRecords, signal, dialog, title, ...formProps } = options;
4792
5703
  return mountFormInstance({
4793
5704
  formProps,
4794
5705
  appContext,
4795
- timeout,
4796
5706
  signal,
4797
- hidden: !debug,
4798
- skipTimeout: debug,
4799
- timeoutMessage: `submitForm timeout after ${timeout}ms: form is not initialized.`,
4800
5707
  createWrapper: ({ formRef, formProps, cleanup, resolve, reject }) => {
4801
- /**
4802
- * 执行一次提交:nextTick 等待子组件渲染 → 快照 changeRecords → 调用实例 submitForm → resolve。
4803
- * 校验失败时交给 `onValidateError` 决定「保留弹层展示错误(debug)」还是「reject 并清理(普通)」,
4804
- * 从而让 debug 与普通模式共享同一份提交逻辑。
4805
- */
4806
5708
  const doSubmit = async (onValidateError) => {
4807
5709
  try {
4808
5710
  await (0, vue.nextTick)();
@@ -4817,39 +5719,19 @@
4817
5719
  onValidateError(err);
4818
5720
  }
4819
5721
  };
4820
- if (debug) return createDebugWrapper({
5722
+ return createDialogWrapper({
4821
5723
  formRef,
4822
5724
  formProps,
4823
5725
  name: "MFormSubmitWrapper",
4824
- title: "submitForm 调试",
5726
+ title: title ?? "submitForm",
4825
5727
  onConfirm: (setError) => doSubmit((err) => {
4826
5728
  setError(err instanceof Error ? err.message : String(err));
4827
5729
  }),
4828
5730
  onCancel: () => {
4829
- reject(/* @__PURE__ */ new Error("submitForm canceled in debug mode."));
5731
+ reject(/* @__PURE__ */ new Error("submitForm canceled."));
4830
5732
  cleanup();
4831
5733
  }
4832
5734
  });
4833
- return (0, vue.defineComponent)({
4834
- name: "MFormSubmitWrapper",
4835
- setup() {
4836
- const stop = (0, vue.watch)(() => formRef.value?.initialized, (initialized) => {
4837
- if (!initialized) return;
4838
- stop();
4839
- doSubmit((err) => {
4840
- reject(err);
4841
- cleanup();
4842
- });
4843
- }, {
4844
- flush: "post",
4845
- immediate: true
4846
- });
4847
- return () => (0, vue.h)(Form_default, {
4848
- ...formProps,
4849
- ref: formRef
4850
- });
4851
- }
4852
- });
4853
5735
  }
4854
5736
  });
4855
5737
  };
@@ -4890,64 +5772,23 @@
4890
5772
  var stripTabItemsLazy = (config) => {
4891
5773
  const cloned = cloneConfigValue(config);
4892
5774
  removeTabItemsLazy(cloned);
4893
- return cloned;
4894
- };
4895
- /**
4896
- * 以命令式方式对一份「表单配置 + 值」做一次静默校验,**不依赖也不影响任何已渲染的表单**。
4897
- *
4898
- * 与 `submitForm` 类似,内部会临时挂载一个不可见的 MForm 实例,等待其初始化完成后调用
4899
- * 实例的 `validate` 方法,返回汇总后的错误文案,随后自动卸载实例。
4900
- *
4901
- * 与 `submitForm` 的区别:
4902
- * - 「静默」:校验失败不抛异常、不触发 `error` 事件、不返回表单值;
4903
- * - 仅用于「探测」配置是否合法,适合源码保存后校验、批量校验组件配置等场景。
4904
- *
4905
- * 由于每次都新建一个独立的 MForm 实例,调用方无需持有任何表单 ref,也不会污染
4906
- * 页面上正在展示的表单状态。
4907
- *
4908
- * @returns 校验通过返回空字符串 `''`,否则返回以 `<br>` 拼接的错误文案。
4909
- * 仅在初始化超时或挂载失败等异常情况下才会 reject。
4910
- *
4911
- * @example
4912
- * ```ts
4913
- * import { validateForm } from '@tmagic/form';
4914
- *
4915
- * const error = await validateForm({
4916
- * config: [...],
4917
- * initValues: { name: 'foo' },
4918
- * appContext: getCurrentInstance()?.appContext,
4919
- * });
4920
- * if (error) {
4921
- * // 配置不合法,error 为错误文案
4922
- * }
5775
+ return cloned;
5776
+ };
5777
+ /**
5778
+ * 可见弹层校验:把表单渲染在弹层里,点击「确定」才调用挂载实例的 `validate`。
4923
5779
  *
4924
- * // 调试模式:可见地渲染表单,点击「确定」才校验,校验失败保留弹层可修正重试:
4925
- * const error = await validateForm({
4926
- * config: [...],
4927
- * initValues: { name: 'foo' },
4928
- * debug: true,
4929
- * });
4930
- * ```
5780
+ * 用于需要用户填写/确认的场景;默认路径仍是无渲染校验(见 `validateForm` 的说明)。
4931
5781
  */
4932
- var validateForm = (options) => {
4933
- const { appContext, timeout = 1e4, debug = false, config, signal, ...rest } = options;
5782
+ var validateFormByDialogRender = (options) => {
5783
+ const { appContext, config, signal, dialog, title, ...rest } = options;
4934
5784
  return mountFormInstance({
4935
5785
  formProps: {
4936
5786
  ...rest,
4937
5787
  config: stripTabItemsLazy(config)
4938
5788
  },
4939
5789
  appContext,
4940
- timeout,
4941
5790
  signal,
4942
- hidden: !debug,
4943
- skipTimeout: debug,
4944
- timeoutMessage: `validateForm timeout after ${timeout}ms: form is not initialized.`,
4945
5791
  createWrapper: ({ formRef, formProps, cleanup, resolve, reject }) => {
4946
- /**
4947
- * 执行一次校验:nextTick 等待子组件渲染 → 调用实例 validate → 通过则 resolve ''。
4948
- * 校验失败(返回非空错误文案)时交给 `onInvalid` 决定「静默 resolve 错误文案(普通)」
4949
- * 还是「在弹层展示错误并保留供重试(debug)」,从而让两种模式共享同一份校验逻辑。
4950
- */
4951
5792
  const doValidate = async (onInvalid) => {
4952
5793
  try {
4953
5794
  await (0, vue.nextTick)();
@@ -4962,42 +5803,40 @@
4962
5803
  cleanup();
4963
5804
  }
4964
5805
  };
4965
- if (debug) return createDebugWrapper({
5806
+ return createDialogWrapper({
4966
5807
  formRef,
4967
5808
  formProps,
4968
5809
  name: "MFormValidateWrapper",
4969
- title: "validateForm 调试",
5810
+ title: title ?? "validateForm",
4970
5811
  onConfirm: (setError) => doValidate((error) => {
4971
5812
  setError(error);
4972
5813
  }),
4973
5814
  onCancel: () => {
4974
- reject(/* @__PURE__ */ new Error("validateForm canceled in debug mode."));
5815
+ reject(/* @__PURE__ */ new Error("validateForm canceled."));
4975
5816
  cleanup();
4976
5817
  }
4977
5818
  });
4978
- return (0, vue.defineComponent)({
4979
- name: "MFormValidateWrapper",
4980
- setup() {
4981
- const stop = (0, vue.watch)(() => formRef.value?.initialized, (initialized) => {
4982
- if (!initialized) return;
4983
- stop();
4984
- doValidate((error) => {
4985
- resolve(error);
4986
- cleanup();
4987
- });
4988
- }, {
4989
- flush: "post",
4990
- immediate: true
4991
- });
4992
- return () => (0, vue.h)(Form_default, {
4993
- ...formProps,
4994
- ref: formRef
4995
- });
4996
- }
4997
- });
4998
5819
  }
4999
5820
  });
5000
5821
  };
5822
+ /**
5823
+ * 以命令式方式对一份「表单配置 + 值」做一次校验并取回表单值。
5824
+ *
5825
+ * 默认走无渲染实现(见 `@tmagic/form/headless`)。`dialog: true` 会把表单以弹层渲染出来。
5826
+ */
5827
+ var submitForm = async (options) => {
5828
+ if (options.dialog) return submitFormByDialogRender(options);
5829
+ return submitForm$1(options);
5830
+ };
5831
+ /**
5832
+ * 以命令式方式对一份「表单配置 + 值」做一次静默校验。
5833
+ *
5834
+ * 默认走无渲染实现。`dialog: true` 的处理详见 `submitForm`。
5835
+ */
5836
+ var validateForm = async (options) => {
5837
+ if (options.dialog) return validateFormByDialogRender(options);
5838
+ return validateForm$1(options);
5839
+ };
5001
5840
  //#endregion
5002
5841
  //#region packages/form/src/FormDialog.vue?vue&type=script&setup=true&lang.ts
5003
5842
  var _hoisted_1$13 = { style: { "min-height": "1px" } };
@@ -5050,7 +5889,7 @@
5050
5889
  type: Boolean,
5051
5890
  default: true
5052
5891
  },
5053
- extendState: {},
5892
+ context: {},
5054
5893
  theme: {}
5055
5894
  },
5056
5895
  emits: [
@@ -5208,7 +6047,7 @@
5208
6047
  "use-field-text-in-error": __props.useFieldTextInError,
5209
6048
  "type-match-valid": __props.typeMatchValid,
5210
6049
  "validate-on-init": __props.validateOnInit,
5211
- "extend-state": __props.extendState,
6050
+ context: __props.context,
5212
6051
  theme: effectiveTheme.value,
5213
6052
  onChange: changeHandler
5214
6053
  }, null, 8, [
@@ -5225,7 +6064,7 @@
5225
6064
  "use-field-text-in-error",
5226
6065
  "type-match-valid",
5227
6066
  "validate-on-init",
5228
- "extend-state",
6067
+ "context",
5229
6068
  "theme"
5230
6069
  ]), (0, vue.renderSlot)(_ctx.$slots, "default")], 4)) : (0, vue.createCommentVNode)("v-if", true)]),
5231
6070
  _: 3
@@ -5280,7 +6119,7 @@
5280
6119
  default: true
5281
6120
  },
5282
6121
  beforeClose: {},
5283
- extendState: {},
6122
+ context: {},
5284
6123
  theme: {}
5285
6124
  },
5286
6125
  emits: [
@@ -5431,7 +6270,7 @@
5431
6270
  "use-field-text-in-error": __props.useFieldTextInError,
5432
6271
  "type-match-valid": __props.typeMatchValid,
5433
6272
  "validate-on-init": __props.validateOnInit,
5434
- "extend-state": __props.extendState,
6273
+ context: __props.context,
5435
6274
  theme: effectiveTheme.value,
5436
6275
  onChange: changeHandler
5437
6276
  }, null, 8, [
@@ -5447,7 +6286,7 @@
5447
6286
  "use-field-text-in-error",
5448
6287
  "type-match-valid",
5449
6288
  "validate-on-init",
5450
- "extend-state",
6289
+ "context",
5451
6290
  "theme"
5452
6291
  ]), (0, vue.renderSlot)(_ctx.$slots, "default")], 512)) : (0, vue.createCommentVNode)("v-if", true)]),
5453
6292
  _: 3
@@ -5493,7 +6332,7 @@
5493
6332
  type: Boolean,
5494
6333
  default: true
5495
6334
  },
5496
- extendState: {}
6335
+ context: {}
5497
6336
  },
5498
6337
  emits: [
5499
6338
  "change",
@@ -5557,7 +6396,7 @@
5557
6396
  inline: __props.inline,
5558
6397
  "prevent-submit-default": __props.preventSubmitDefault,
5559
6398
  "use-field-text-in-error": __props.useFieldTextInError,
5560
- "extend-state": __props.extendState,
6399
+ context: __props.context,
5561
6400
  "type-match-valid": __props.typeMatchValid,
5562
6401
  "validate-on-init": __props.validateOnInit,
5563
6402
  onChange: changeHandler
@@ -5572,7 +6411,7 @@
5572
6411
  "inline",
5573
6412
  "prevent-submit-default",
5574
6413
  "use-field-text-in-error",
5575
- "extend-state",
6414
+ "context",
5576
6415
  "type-match-valid",
5577
6416
  "validate-on-init"
5578
6417
  ]), (0, vue.renderSlot)(_ctx.$slots, "default")]),
@@ -5603,7 +6442,7 @@
5603
6442
  var _hoisted_2$7 = ["innerHTML"];
5604
6443
  var _hoisted_3$6 = { key: 1 };
5605
6444
  var _hoisted_4$5 = ["innerHTML"];
5606
- var _hoisted_5$3 = ["innerHTML"];
6445
+ var _hoisted_5$4 = ["innerHTML"];
5607
6446
  var _hoisted_6$2 = {
5608
6447
  key: 2,
5609
6448
  style: { "display": "flex" }
@@ -5617,6 +6456,7 @@
5617
6456
  __name: "Fieldset",
5618
6457
  props: {
5619
6458
  labelWidth: {},
6459
+ labelPosition: {},
5620
6460
  prop: { default: "" },
5621
6461
  size: {},
5622
6462
  model: {},
@@ -5656,6 +6496,7 @@
5656
6496
  if (props.config.items) return props.config.labelWidth || props.labelWidth;
5657
6497
  return props.config.labelWidth || props.labelWidth || (props.config.text ? void 0 : "0");
5658
6498
  });
6499
+ const lPosition = (0, vue.computed)(() => props.config.labelPosition || props.labelPosition);
5659
6500
  const valueChangeHandler = (value) => {
5660
6501
  emit("change", value, { modifyKey: checkboxName.value });
5661
6502
  };
@@ -5692,7 +6533,7 @@
5692
6533
  key: 0,
5693
6534
  innerHTML: __props.config.extra,
5694
6535
  class: "m-form-tip"
5695
- }, null, 8, _hoisted_5$3)) : (0, vue.createCommentVNode)("v-if", true)])), __props.config.schematic && show.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$2, [(0, vue.createElementVNode)("div", _hoisted_7$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
6536
+ }, null, 8, _hoisted_5$4)) : (0, vue.createCommentVNode)("v-if", true)])), __props.config.schematic && show.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$2, [(0, vue.createElementVNode)("div", _hoisted_7$1, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
5696
6537
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
5697
6538
  key: key(item, index),
5698
6539
  model: name.value ? __props.model[name.value] : __props.model,
@@ -5703,6 +6544,7 @@
5703
6544
  prop: __props.prop,
5704
6545
  disabled: __props.disabled,
5705
6546
  labelWidth: lWidth.value,
6547
+ labelPosition: lPosition.value,
5706
6548
  size: __props.size,
5707
6549
  onChange: changeHandler,
5708
6550
  onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
@@ -5715,6 +6557,7 @@
5715
6557
  "prop",
5716
6558
  "disabled",
5717
6559
  "labelWidth",
6560
+ "labelPosition",
5718
6561
  "size"
5719
6562
  ]);
5720
6563
  }), 128))]), (0, vue.createElementVNode)("img", {
@@ -5730,6 +6573,7 @@
5730
6573
  config: item,
5731
6574
  prop: __props.prop,
5732
6575
  labelWidth: lWidth.value,
6576
+ labelPosition: lPosition.value,
5733
6577
  size: __props.size,
5734
6578
  disabled: __props.disabled,
5735
6579
  onChange: changeHandler,
@@ -5742,6 +6586,7 @@
5742
6586
  "config",
5743
6587
  "prop",
5744
6588
  "labelWidth",
6589
+ "labelPosition",
5745
6590
  "size",
5746
6591
  "disabled"
5747
6592
  ]);
@@ -5761,6 +6606,7 @@
5761
6606
  config: {},
5762
6607
  name: {},
5763
6608
  labelWidth: {},
6609
+ labelPosition: {},
5764
6610
  prop: {},
5765
6611
  size: {},
5766
6612
  disabled: { type: Boolean }
@@ -5794,6 +6640,7 @@
5794
6640
  size: __props.size,
5795
6641
  disabled: __props.disabled,
5796
6642
  "label-width": __props.config.labelWidth || __props.labelWidth,
6643
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5797
6644
  onChange: changeHandler,
5798
6645
  onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
5799
6646
  }, null, 8, [
@@ -5804,7 +6651,8 @@
5804
6651
  "prop",
5805
6652
  "size",
5806
6653
  "disabled",
5807
- "label-width"
6654
+ "label-width",
6655
+ "label-position"
5808
6656
  ]);
5809
6657
  }), 128))], 4);
5810
6658
  };
@@ -5823,7 +6671,7 @@
5823
6671
  style: { "display": "flex" }
5824
6672
  };
5825
6673
  var _hoisted_4$4 = { style: { "flex": "1" } };
5826
- var _hoisted_5$2 = ["src"];
6674
+ var _hoisted_5$3 = ["src"];
5827
6675
  //#endregion
5828
6676
  //#region packages/form/src/containers/Panel.vue
5829
6677
  var Panel_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -5836,6 +6684,7 @@
5836
6684
  config: {},
5837
6685
  name: {},
5838
6686
  labelWidth: {},
6687
+ labelPosition: {},
5839
6688
  prop: {},
5840
6689
  size: {},
5841
6690
  disabled: { type: Boolean },
@@ -5898,6 +6747,7 @@
5898
6747
  size: __props.size,
5899
6748
  disabled: __props.disabled,
5900
6749
  "label-width": __props.config.labelWidth || __props.labelWidth,
6750
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5901
6751
  onChange: changeHandler,
5902
6752
  onAddDiffCount: _cache[2] || (_cache[2] = ($event) => onAddDiffCount())
5903
6753
  }, null, 8, [
@@ -5908,12 +6758,13 @@
5908
6758
  "prop",
5909
6759
  "size",
5910
6760
  "disabled",
5911
- "label-width"
6761
+ "label-width",
6762
+ "label-position"
5912
6763
  ]);
5913
6764
  }), 128))]), (0, vue.createElementVNode)("img", {
5914
6765
  class: "m-form-schematic",
5915
6766
  src: __props.config.schematic
5916
- }, null, 8, _hoisted_5$2)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
6767
+ }, null, 8, _hoisted_5$3)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
5917
6768
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
5918
6769
  key: item[(0, vue.unref)(mForm)?.keyProp || "__key"] ?? index,
5919
6770
  config: item,
@@ -5924,6 +6775,7 @@
5924
6775
  size: __props.size,
5925
6776
  disabled: __props.disabled,
5926
6777
  "label-width": __props.config.labelWidth || __props.labelWidth,
6778
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5927
6779
  onChange: changeHandler,
5928
6780
  onAddDiffCount: _cache[3] || (_cache[3] = ($event) => onAddDiffCount())
5929
6781
  }, null, 8, [
@@ -5934,7 +6786,8 @@
5934
6786
  "prop",
5935
6787
  "size",
5936
6788
  "disabled",
5937
- "label-width"
6789
+ "label-width",
6790
+ "label-position"
5938
6791
  ]);
5939
6792
  }), 128))])]),
5940
6793
  _: 3
@@ -5953,6 +6806,7 @@
5953
6806
  isCompare: { type: Boolean },
5954
6807
  config: {},
5955
6808
  labelWidth: {},
6809
+ labelPosition: {},
5956
6810
  expandMore: { type: Boolean },
5957
6811
  span: {},
5958
6812
  size: {},
@@ -5977,6 +6831,7 @@
5977
6831
  config: __props.config,
5978
6832
  prop: __props.prop,
5979
6833
  "label-width": __props.config.labelWidth || __props.labelWidth,
6834
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5980
6835
  "expand-more": __props.expandMore,
5981
6836
  size: __props.size,
5982
6837
  disabled: __props.disabled,
@@ -5990,6 +6845,7 @@
5990
6845
  "config",
5991
6846
  "prop",
5992
6847
  "label-width",
6848
+ "label-position",
5993
6849
  "expand-more",
5994
6850
  "size",
5995
6851
  "disabled"
@@ -6011,6 +6867,7 @@
6011
6867
  config: {},
6012
6868
  name: {},
6013
6869
  labelWidth: {},
6870
+ labelPosition: {},
6014
6871
  prop: {},
6015
6872
  size: {},
6016
6873
  expandMore: { type: Boolean },
@@ -6033,6 +6890,7 @@
6033
6890
  span: col.span || __props.config.span || 24 / __props.config.items.length,
6034
6891
  config: col,
6035
6892
  labelWidth: __props.config.labelWidth || __props.labelWidth,
6893
+ labelPosition: __props.config.labelPosition || __props.labelPosition,
6036
6894
  expandMore: __props.expandMore,
6037
6895
  model: __props.name ? __props.model[__props.name] : __props.model,
6038
6896
  lastValues: __props.name ? __props.lastValues[__props.name] : __props.lastValues,
@@ -6046,6 +6904,7 @@
6046
6904
  "span",
6047
6905
  "config",
6048
6906
  "labelWidth",
6907
+ "labelPosition",
6049
6908
  "expandMore",
6050
6909
  "model",
6051
6910
  "lastValues",
@@ -6076,6 +6935,7 @@
6076
6935
  name: {},
6077
6936
  size: {},
6078
6937
  labelWidth: {},
6938
+ labelPosition: {},
6079
6939
  prop: { default: "" },
6080
6940
  expandMore: { type: Boolean },
6081
6941
  disabled: { type: Boolean }
@@ -6152,12 +7012,15 @@
6152
7012
  prop: props.prop,
6153
7013
  config: props.config
6154
7014
  });
7015
+ const tabsModel = props.model[props.name];
7016
+ if (Array.isArray(tabsModel) && Array.isArray(props.config.items)) for (const tab of tabsModel) applyMountValueEffects(mForm, props.config.items, tab);
6155
7017
  emit("change", props.model[props.name]);
6156
7018
  } else {
6157
7019
  const newObj = await initValue(mForm, {
6158
7020
  config: props.config.items,
6159
7021
  initValues: {}
6160
7022
  });
7023
+ applyMountValueEffects(mForm, props.config.items, newObj);
6161
7024
  newObj.title = `标签${tabs.value.length + 1}`;
6162
7025
  props.model[props.name].push(newObj);
6163
7026
  emit("change", props.model[props.name], { changeRecords: [{
@@ -6227,6 +7090,7 @@
6227
7090
  prop: __props.config.dynamic ? `${__props.prop}${__props.prop ? "." : ""}${String(tabIndex)}` : tab.name ? `${__props.prop}${__props.prop ? "." : ""}${tab.name}` : __props.prop,
6228
7091
  size: __props.size,
6229
7092
  "label-width": tab.labelWidth || __props.labelWidth,
7093
+ "label-position": tab.labelPosition || __props.labelPosition,
6230
7094
  "expand-more": __props.expandMore,
6231
7095
  onChange: changeHandler,
6232
7096
  onAddDiffCount: ($event) => onAddDiffCount(Number(tabIndex))
@@ -6239,6 +7103,7 @@
6239
7103
  "prop",
6240
7104
  "size",
6241
7105
  "label-width",
7106
+ "label-position",
6242
7107
  "expand-more",
6243
7108
  "onAddDiffCount"
6244
7109
  ]);
@@ -6254,12 +7119,14 @@
6254
7119
  //#endregion
6255
7120
  //#region packages/form/src/containers/GroupListItem.vue?vue&type=script&setup=true&lang.ts
6256
7121
  var _hoisted_1$9 = { class: "m-fields-group-list-item-header" };
6257
- var _hoisted_2$5 = ["innerHTML"];
6258
- var _hoisted_3$4 = { style: {
7122
+ var _hoisted_2$5 = { class: "m-fields-group-list-item-title" };
7123
+ var _hoisted_3$4 = ["innerHTML"];
7124
+ var _hoisted_4$3 = { class: "m-fields-group-list-item-actions" };
7125
+ var _hoisted_5$2 = { style: {
6259
7126
  "text-align": "right",
6260
7127
  "margin-top": "20px"
6261
7128
  } };
6262
- var _hoisted_4$3 = ["innerHTML"];
7129
+ var _hoisted_6$1 = ["innerHTML"];
6263
7130
  //#endregion
6264
7131
  //#region packages/form/src/containers/GroupListItem.vue
6265
7132
  var GroupListItem_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -6272,6 +7139,7 @@
6272
7139
  groupModel: {},
6273
7140
  config: {},
6274
7141
  labelWidth: {},
7142
+ labelPosition: {},
6275
7143
  prop: {},
6276
7144
  size: {},
6277
7145
  index: {},
@@ -6290,13 +7158,8 @@
6290
7158
  const mForm = (0, vue.inject)("mForm");
6291
7159
  const defaultExpandQuantity = props.config.defaultExpandQuantity ?? 7;
6292
7160
  const expand = (0, vue.ref)(props.config.expandAll || defaultExpandQuantity > props.index);
6293
- const rowConfig = (0, vue.computed)(() => ({
6294
- type: "row",
6295
- span: props.config.span || 24,
6296
- items: props.config.items,
6297
- labelWidth: props.config.labelWidth,
6298
- [mForm?.keyProp || "__key"]: `${props.config[mForm?.keyProp || "__key"]}${String(props.index)}`
6299
- }));
7161
+ const rowConfig = (0, vue.computed)(() => getGroupListRowConfig(props.config, props.index, mForm?.keyProp));
7162
+ const rowProp = (0, vue.computed)(() => appendProp(props.prop, props.index));
6300
7163
  const title = (0, vue.computed)(() => {
6301
7164
  if (props.config.titleKey && props.model[props.config.titleKey]) {
6302
7165
  const { titlePrefix } = props.config;
@@ -6356,101 +7219,112 @@
6356
7219
  })]),
6357
7220
  _: 1
6358
7221
  }, 8, ["disabled"]),
6359
- (0, vue.createElementVNode)("span", { innerHTML: title.value }, null, 8, _hoisted_2$5),
6360
- (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), { content: `删除 ${title.value}` }, {
6361
- default: (0, vue.withCtx)(() => [!__props.isCompare ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
7222
+ (0, vue.createElementVNode)("div", _hoisted_2$5, [(0, vue.renderSlot)(_ctx.$slots, "title", {
7223
+ model: __props.model,
7224
+ index: __props.index,
7225
+ lastValues: __props.lastValues,
7226
+ prop: rowProp.value,
7227
+ title: title.value
7228
+ }, () => [(0, vue.createElementVNode)("span", { innerHTML: title.value }, null, 8, _hoisted_3$4)])]),
7229
+ (0, vue.createElementVNode)("div", _hoisted_4$3, [
7230
+ copyable.value && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
6362
7231
  key: 0,
7232
+ link: "",
6363
7233
  size: "default",
7234
+ type: "primary",
7235
+ icon: (0, vue.unref)(_element_plus_icons_vue.DocumentCopy),
7236
+ disabled: __props.disabled,
7237
+ onClick: copyHandler
7238
+ }, {
7239
+ default: (0, vue.withCtx)(() => [..._cache[6] || (_cache[6] = [(0, vue.createTextVNode)("复制", -1)])]),
7240
+ _: 1
7241
+ }, 8, ["icon", "disabled"])) : (0, vue.createCommentVNode)("v-if", true),
7242
+ movable.value && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [(0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6364
7243
  link: "",
6365
- class: "delete-button",
6366
- icon: (0, vue.unref)(_element_plus_icons_vue.Delete),
7244
+ size: "default",
6367
7245
  disabled: __props.disabled,
6368
- onClick: removeHandler
6369
- }, null, 8, ["icon", "disabled"])), [[vue.vShow, showDelete.value]]) : (0, vue.createCommentVNode)("v-if", true)]),
6370
- _: 1
6371
- }, 8, ["content"]),
6372
- copyable.value && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
6373
- key: 0,
6374
- link: "",
6375
- size: "default",
6376
- type: "primary",
6377
- icon: (0, vue.unref)(_element_plus_icons_vue.DocumentCopy),
6378
- disabled: __props.disabled,
6379
- onClick: copyHandler
6380
- }, {
6381
- default: (0, vue.withCtx)(() => [..._cache[6] || (_cache[6] = [(0, vue.createTextVNode)("复制", -1)])]),
6382
- _: 1
6383
- }, 8, ["icon", "disabled"])) : (0, vue.createCommentVNode)("v-if", true),
6384
- movable.value && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [(0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6385
- link: "",
6386
- size: "default",
6387
- disabled: __props.disabled,
6388
- icon: (0, vue.unref)(_element_plus_icons_vue.Top),
6389
- onClick: _cache[0] || (_cache[0] = ($event) => changeOrder(-1))
6390
- }, {
6391
- default: (0, vue.withCtx)(() => [..._cache[7] || (_cache[7] = [(0, vue.createTextVNode)("上移", -1)])]),
6392
- _: 1
6393
- }, 8, ["disabled", "icon"]), [[vue.vShow, __props.index !== 0]]), (0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6394
- link: "",
6395
- size: "default",
6396
- disabled: __props.disabled,
6397
- icon: (0, vue.unref)(_element_plus_icons_vue.Bottom),
6398
- onClick: _cache[1] || (_cache[1] = ($event) => changeOrder(1))
6399
- }, {
6400
- default: (0, vue.withCtx)(() => [..._cache[8] || (_cache[8] = [(0, vue.createTextVNode)("下移", -1)])]),
6401
- _: 1
6402
- }, 8, ["disabled", "icon"]), [[vue.vShow, __props.index !== length.value - 1]])], 64)) : (0, vue.createCommentVNode)("v-if", true),
6403
- __props.config.moveSpecifyLocation && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicPopover), {
6404
- key: 2,
6405
- trigger: "click",
6406
- placement: "top",
6407
- width: "200",
6408
- visible: moveSpecifyLocationVisible.value
6409
- }, {
6410
- reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7246
+ icon: (0, vue.unref)(_element_plus_icons_vue.Top),
7247
+ onClick: _cache[0] || (_cache[0] = ($event) => changeOrder(-1))
7248
+ }, {
7249
+ default: (0, vue.withCtx)(() => [..._cache[7] || (_cache[7] = [(0, vue.createTextVNode)("上移", -1)])]),
7250
+ _: 1
7251
+ }, 8, ["disabled", "icon"]), [[vue.vShow, __props.index !== 0]]), (0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6411
7252
  link: "",
6412
- size: "small",
6413
- type: "primary",
6414
- icon: (0, vue.unref)(_element_plus_icons_vue.Position),
7253
+ size: "default",
6415
7254
  disabled: __props.disabled,
6416
- onClick: _cache[2] || (_cache[2] = ($event) => moveSpecifyLocationVisible.value = true)
7255
+ icon: (0, vue.unref)(_element_plus_icons_vue.Bottom),
7256
+ onClick: _cache[1] || (_cache[1] = ($event) => changeOrder(1))
6417
7257
  }, {
6418
- default: (0, vue.withCtx)(() => [..._cache[9] || (_cache[9] = [(0, vue.createTextVNode)("移动至", -1)])]),
7258
+ default: (0, vue.withCtx)(() => [..._cache[8] || (_cache[8] = [(0, vue.createTextVNode)("下移", -1)])]),
6419
7259
  _: 1
6420
- }, 8, ["icon", "disabled"])]),
6421
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("div", null, [
6422
- _cache[10] || (_cache[10] = (0, vue.createTextVNode)(" 第", -1)),
6423
- (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicInputNumber), {
6424
- style: { "margin": "0 5px" },
6425
- modelValue: moveSpecifyLocationIndex.value,
6426
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => moveSpecifyLocationIndex.value = $event),
6427
- size: "small",
6428
- min: 1,
6429
- disabled: __props.disabled
6430
- }, null, 8, ["modelValue", "disabled"]),
6431
- _cache[11] || (_cache[11] = (0, vue.createTextVNode)("行 ", -1))
6432
- ]), (0, vue.createElementVNode)("div", _hoisted_3$4, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6433
- size: "small",
6434
- text: "",
6435
- onClick: _cache[4] || (_cache[4] = ($event) => moveSpecifyLocationVisible.value = false)
7260
+ }, 8, ["disabled", "icon"]), [[vue.vShow, __props.index !== length.value - 1]])], 64)) : (0, vue.createCommentVNode)("v-if", true),
7261
+ __props.config.moveSpecifyLocation && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicPopover), {
7262
+ key: 2,
7263
+ trigger: "click",
7264
+ placement: "top",
7265
+ width: "200",
7266
+ visible: moveSpecifyLocationVisible.value
6436
7267
  }, {
6437
- default: (0, vue.withCtx)(() => [..._cache[12] || (_cache[12] = [(0, vue.createTextVNode)("取消", -1)])]),
7268
+ reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7269
+ link: "",
7270
+ size: "small",
7271
+ type: "primary",
7272
+ icon: (0, vue.unref)(_element_plus_icons_vue.Position),
7273
+ disabled: __props.disabled,
7274
+ onClick: _cache[2] || (_cache[2] = ($event) => moveSpecifyLocationVisible.value = true)
7275
+ }, {
7276
+ default: (0, vue.withCtx)(() => [..._cache[9] || (_cache[9] = [(0, vue.createTextVNode)("移动至", -1)])]),
7277
+ _: 1
7278
+ }, 8, ["icon", "disabled"])]),
7279
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("div", null, [
7280
+ _cache[10] || (_cache[10] = (0, vue.createTextVNode)(" 第", -1)),
7281
+ (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicInputNumber), {
7282
+ style: { "margin": "0 5px" },
7283
+ modelValue: moveSpecifyLocationIndex.value,
7284
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => moveSpecifyLocationIndex.value = $event),
7285
+ size: "small",
7286
+ min: 1,
7287
+ disabled: __props.disabled
7288
+ }, null, 8, ["modelValue", "disabled"]),
7289
+ _cache[11] || (_cache[11] = (0, vue.createTextVNode)("行 ", -1))
7290
+ ]), (0, vue.createElementVNode)("div", _hoisted_5$2, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7291
+ size: "small",
7292
+ text: "",
7293
+ onClick: _cache[4] || (_cache[4] = ($event) => moveSpecifyLocationVisible.value = false)
7294
+ }, {
7295
+ default: (0, vue.withCtx)(() => [..._cache[12] || (_cache[12] = [(0, vue.createTextVNode)("取消", -1)])]),
7296
+ _: 1
7297
+ }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7298
+ size: "small",
7299
+ type: "primary",
7300
+ onClick: moveSpecifyLocationHandler
7301
+ }, {
7302
+ default: (0, vue.withCtx)(() => [..._cache[13] || (_cache[13] = [(0, vue.createTextVNode)("确认", -1)])]),
7303
+ _: 1
7304
+ })])])]),
6438
7305
  _: 1
6439
- }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6440
- size: "small",
6441
- type: "primary",
6442
- onClick: moveSpecifyLocationHandler
7306
+ }, 8, ["visible"])) : (0, vue.createCommentVNode)("v-if", true),
7307
+ (0, vue.createCommentVNode)(" v-if 必须挂在 Tooltip 上:子节点为 false 时 ElOnlyChild 会报 no valid child node found "),
7308
+ !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
7309
+ key: 3,
7310
+ content: `删除 ${title.value}`
6443
7311
  }, {
6444
- default: (0, vue.withCtx)(() => [..._cache[13] || (_cache[13] = [(0, vue.createTextVNode)("确认", -1)])]),
7312
+ default: (0, vue.withCtx)(() => [(0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7313
+ size: "default",
7314
+ link: "",
7315
+ class: "delete-button",
7316
+ icon: (0, vue.unref)(_element_plus_icons_vue.Delete),
7317
+ disabled: __props.disabled,
7318
+ onClick: removeHandler
7319
+ }, null, 8, ["icon", "disabled"]), [[vue.vShow, showDelete.value]])]),
6445
7320
  _: 1
6446
- })])])]),
6447
- _: 1
6448
- }, 8, ["visible"])) : (0, vue.createCommentVNode)("v-if", true),
6449
- itemExtra.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
6450
- key: 3,
6451
- innerHTML: itemExtra.value,
6452
- class: "m-form-tip"
6453
- }, null, 8, _hoisted_4$3)) : (0, vue.createCommentVNode)("v-if", true)
7321
+ }, 8, ["content"])) : (0, vue.createCommentVNode)("v-if", true),
7322
+ itemExtra.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
7323
+ key: 4,
7324
+ innerHTML: itemExtra.value,
7325
+ class: "m-form-tip"
7326
+ }, null, 8, _hoisted_6$1)) : (0, vue.createCommentVNode)("v-if", true)
7327
+ ])
6454
7328
  ])]),
6455
7329
  default: (0, vue.withCtx)(() => [expand.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
6456
7330
  key: 0,
@@ -6459,7 +7333,8 @@
6459
7333
  lastValues: __props.lastValues,
6460
7334
  "is-compare": __props.isCompare,
6461
7335
  labelWidth: __props.labelWidth,
6462
- prop: `${__props.prop}${__props.prop ? "." : ""}${String(__props.index)}`,
7336
+ labelPosition: __props.labelPosition,
7337
+ prop: rowProp.value,
6463
7338
  size: __props.size,
6464
7339
  disabled: __props.disabled,
6465
7340
  onChange: changeHandler,
@@ -6470,11 +7345,12 @@
6470
7345
  "lastValues",
6471
7346
  "is-compare",
6472
7347
  "labelWidth",
7348
+ "labelPosition",
6473
7349
  "prop",
6474
7350
  "size",
6475
7351
  "disabled"
6476
7352
  ])) : (0, vue.createCommentVNode)("v-if", true)]),
6477
- _: 1
7353
+ _: 3
6478
7354
  }, 8, ["flat", "body-style"]);
6479
7355
  };
6480
7356
  }
@@ -6488,11 +7364,7 @@
6488
7364
  class: "el-table__empty-block"
6489
7365
  };
6490
7366
  var _hoisted_4$2 = { class: "el-table__empty-text t-table__empty" };
6491
- var _hoisted_5$1 = {
6492
- key: 3,
6493
- class: "m-fields-group-list-footer"
6494
- };
6495
- var _hoisted_6$1 = { style: {
7367
+ var _hoisted_5$1 = { style: {
6496
7368
  "display": "flex",
6497
7369
  "justify-content": "flex-end",
6498
7370
  "flex": "1"
@@ -6509,6 +7381,7 @@
6509
7381
  config: {},
6510
7382
  name: {},
6511
7383
  labelWidth: {},
7384
+ labelPosition: {},
6512
7385
  prop: {},
6513
7386
  size: {},
6514
7387
  disabled: { type: Boolean },
@@ -6537,7 +7410,23 @@
6537
7410
  emit("change", props.model[props.name]);
6538
7411
  };
6539
7412
  const onAddDiffCount = () => emit("addDiffCount");
6540
- const getLastValues = (item, index) => item?.[index] || {};
7413
+ const asList = (value) => Array.isArray(value) ? value : [];
7414
+ const currentList = (0, vue.computed)(() => asList(props.model[props.name]));
7415
+ /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
7416
+ const displayItems = (0, vue.computed)(() => {
7417
+ const current = currentList.value;
7418
+ if (!props.isCompare) return current.map((item, index) => ({
7419
+ item: item ?? {},
7420
+ last: {},
7421
+ index
7422
+ }));
7423
+ const last = asList(props.lastValues?.[props.name]);
7424
+ return Array.from({ length: Math.max(current.length, last.length) }, (_, index) => ({
7425
+ item: current[index] ?? {},
7426
+ last: last[index] ?? {},
7427
+ index
7428
+ }));
7429
+ });
6541
7430
  return (_ctx, _cache) => {
6542
7431
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$8, [
6543
7432
  __props.config.extra ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
@@ -6545,25 +7434,30 @@
6545
7434
  innerHTML: __props.config.extra,
6546
7435
  style: { "color": "rgba(0, 0, 0, 0.45)" }
6547
7436
  }, null, 8, _hoisted_2$4)) : (0, vue.createCommentVNode)("v-if", true),
6548
- !__props.model[__props.name] || !__props.model[__props.name].length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_3$3, [(0, vue.createElementVNode)("span", _hoisted_4$2, "暂无" + (0, vue.toDisplayString)(__props.config.titlePrefix || "") + "数据", 1)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(__props.model[__props.name], (item, index) => {
7437
+ !displayItems.value.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_3$3, [(0, vue.createElementVNode)("span", _hoisted_4$2, "暂无" + (0, vue.toDisplayString)(__props.config.titlePrefix || "") + "数据", 1)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(displayItems.value, (entry) => {
6549
7438
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupListItem_default, {
6550
- key: index,
6551
- model: item,
6552
- lastValues: getLastValues(__props.lastValues?.[__props.name], Number(index)),
7439
+ key: entry.index,
7440
+ model: entry.item,
7441
+ lastValues: entry.last,
6553
7442
  "is-compare": __props.isCompare,
6554
7443
  config: __props.config,
6555
7444
  prop: __props.prop,
6556
- index: Number(index),
7445
+ index: entry.index,
6557
7446
  "label-width": __props.labelWidth,
7447
+ "label-position": __props.labelPosition,
6558
7448
  size: __props.size,
6559
7449
  disabled: __props.disabled,
6560
- "group-model": __props.model[__props.name],
7450
+ "group-model": currentList.value,
6561
7451
  onRemoveItem: removeHandler,
6562
7452
  onCopyItem: copyHandler,
6563
7453
  onSwapItem: swapHandler,
6564
7454
  onChange: changeHandler,
6565
7455
  onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
6566
- }, null, 8, [
7456
+ }, (0, vue.createSlots)({ _: 2 }, [_ctx.$slots.title ? {
7457
+ name: "title",
7458
+ fn: (0, vue.withCtx)((slotProps) => [(0, vue.renderSlot)(_ctx.$slots, "title", (0, vue.mergeProps)({ ref_for: true }, slotProps))]),
7459
+ key: "0"
7460
+ } : void 0]), 1032, [
6567
7461
  "model",
6568
7462
  "lastValues",
6569
7463
  "is-compare",
@@ -6571,12 +7465,16 @@
6571
7465
  "prop",
6572
7466
  "index",
6573
7467
  "label-width",
7468
+ "label-position",
6574
7469
  "size",
6575
7470
  "disabled",
6576
7471
  "group-model"
6577
7472
  ]);
6578
7473
  }), 128)),
6579
- !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_5$1, [(0, vue.renderSlot)(_ctx.$slots, "toggle-button"), (0, vue.createElementVNode)("div", _hoisted_6$1, [(0, vue.renderSlot)(_ctx.$slots, "add-button")])])) : (0, vue.createCommentVNode)("v-if", true)
7474
+ !__props.isCompare && (_ctx.$slots["toggle-button"] || _ctx.$slots["add-button"]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7475
+ key: 3,
7476
+ class: (0, vue.normalizeClass)(["m-fields-group-list-footer", { "is-sticky-full": Boolean(__props.config.addButtonConfig?.sticky) }])
7477
+ }, [(0, vue.renderSlot)(_ctx.$slots, "toggle-button"), (0, vue.createElementVNode)("div", _hoisted_5$1, [(0, vue.renderSlot)(_ctx.$slots, "add-button")])], 2)) : (0, vue.createCommentVNode)("v-if", true)
6580
7478
  ]);
6581
7479
  };
6582
7480
  }
@@ -6946,15 +7844,7 @@
6946
7844
  if (typeof props.config.selection === "function") return props.config.selection(mForm, { model: props.model[modelName.value] });
6947
7845
  return props.config.selection;
6948
7846
  });
6949
- const getProp = (index) => {
6950
- return `${props.prop}${props.prop ? "." : ""}${index + 1 + currentPage.value * pageSize.value - 1}`;
6951
- };
6952
- const makeConfig = (config, row) => {
6953
- const newConfig = cloneDeep(config);
6954
- if (typeof config.itemsFunction === "function") newConfig.items = config.itemsFunction(row);
6955
- delete newConfig.display;
6956
- return newConfig;
6957
- };
7847
+ const getProp = (index) => appendProp(props.prop, index + currentPage.value * pageSize.value);
6958
7848
  const changeHandler = (v, eventData) => {
6959
7849
  emit("change", props.model, eventData);
6960
7850
  };
@@ -7033,7 +7923,7 @@
7033
7923
  },
7034
7924
  cell: ({ $index }) => (0, vue.h)("span", $index + 1 + currentPage.value * pageSize.value)
7035
7925
  });
7036
- for (const column of props.config.items) if (column.type !== "hidden" && display$1(column.display)) {
7926
+ for (const column of props.config.items) if (isTableColumnRendered(column, display$1)) {
7037
7927
  const titleTipValue = titleTip(column.titleTip);
7038
7928
  columns.push({
7039
7929
  props: {
@@ -7049,7 +7939,7 @@
7049
7939
  disabled: props.disabled,
7050
7940
  prop: getProp($index),
7051
7941
  rules: column.rules,
7052
- config: makeConfig(column, row),
7942
+ config: makeTableColumnConfig(column, row),
7053
7943
  model: row,
7054
7944
  lastValues: lastData.value[$index],
7055
7945
  isCompare: props.isCompare,
@@ -7081,7 +7971,7 @@
7081
7971
  } };
7082
7972
  var _hoisted_3$2 = { style: { "display": "flex" } };
7083
7973
  var _hoisted_4$1 = {
7084
- key: 1,
7974
+ key: 2,
7085
7975
  class: "bottom",
7086
7976
  style: { "text-align": "right" }
7087
7977
  };
@@ -7148,12 +8038,14 @@
7148
8038
  style: { "color": "rgba(0, 0, 0, 0.45)" },
7149
8039
  innerHTML: __props.config.extra
7150
8040
  }, null, 8, _hoisted_1$7)) : (0, vue.createCommentVNode)("v-if", true),
7151
- (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
8041
+ (0, vue.createCommentVNode)(" v-if 必须挂在 Tooltip 上:表格未就绪时 ElOnlyChild 会报 no valid child node found "),
8042
+ __props.model[modelName.value] ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
8043
+ key: 1,
7152
8044
  content: "拖拽可排序",
7153
8045
  placement: "left-start",
7154
8046
  disabled: __props.config.dropSort !== true || __props.config.dropSortHandle
7155
8047
  }, {
7156
- default: (0, vue.withCtx)(() => [__props.model[modelName.value] ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTable), {
8048
+ default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTable), {
7157
8049
  ref: "tMagicTable",
7158
8050
  style: { "width": "100%" },
7159
8051
  "show-header": "",
@@ -7175,9 +8067,9 @@
7175
8067
  "max-height",
7176
8068
  "onSelect",
7177
8069
  "empty-text"
7178
- ])) : (0, vue.createCommentVNode)("v-if", true)]),
8070
+ ]))]),
7179
8071
  _: 1
7180
- }, 8, ["disabled"]),
8072
+ }, 8, ["disabled"])) : (0, vue.createCommentVNode)("v-if", true),
7181
8073
  (0, vue.renderSlot)(_ctx.$slots, "default"),
7182
8074
  (0, vue.createElementVNode)("div", _hoisted_2$3, [(0, vue.createElementVNode)("div", _hoisted_3$2, [
7183
8075
  !(0, vue.unref)(isFullscreen) ? (0, vue.renderSlot)(_ctx.$slots, "toggle-button", {}, void 0, void 0, 0) : (0, vue.createCommentVNode)("v-if", true),
@@ -7256,39 +8148,46 @@
7256
8148
  const addable = (0, vue.computed)(() => {
7257
8149
  const modelName = props.name || props.config.name || "";
7258
8150
  if (!modelName) return false;
7259
- if (!props.model[modelName]?.length) return true;
7260
- if (typeof props.config.addable === "function") return props.config.addable(mForm, {
8151
+ if (typeof props.config.addable === "function") return Boolean(props.config.addable(mForm, {
7261
8152
  model: props.model[modelName],
7262
8153
  formValue: mForm?.values,
7263
8154
  prop: props.prop,
7264
8155
  config: props.config
7265
- });
7266
- return typeof props.config.addable === "undefined" ? true : props.config.addable;
8156
+ }));
8157
+ if (!props.model[modelName]?.length) return true;
8158
+ return typeof props.config.addable === "undefined" ? true : Boolean(props.config.addable);
7267
8159
  });
8160
+ /**
8161
+ * 新增一项,返回新增后列表应有的长度;没有新增(超上限 / 被 beforeAddRow 拦下 / enum 用尽)返回 null。
8162
+ *
8163
+ * 只抛 change,不直接改 `props.model`:写回由 `MForm` 按 changeRecords 的 propPath 完成。
8164
+ * 返回长度是给调用方用的——新项要等写回后才出现在 DOM 里,靠它才能判断等到了没有。
8165
+ */
7268
8166
  const newHandler = async (row) => {
7269
- const modelName = props.name || props.config.name || "";
7270
- if (props.config.max && props.model[modelName].length >= props.config.max) {
8167
+ const modelName = `${props.name || props.config.name || ""}`;
8168
+ const list = Array.isArray(props.model[modelName]) ? props.model[modelName] : [];
8169
+ if (props.config.max && list.length >= props.config.max) {
7271
8170
  _tmagic_design.tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
7272
- return;
8171
+ return null;
7273
8172
  }
7274
8173
  if (typeof props.config.beforeAddRow === "function") {
7275
8174
  if (!await props.config.beforeAddRow(mForm, {
7276
- model: props.model[modelName],
8175
+ model: list,
7277
8176
  formValue: mForm?.values,
7278
8177
  prop: props.prop
7279
- })) return;
8178
+ })) return null;
7280
8179
  }
7281
8180
  const columns = props.config.items;
7282
8181
  const enumValues = props.config.enum || [];
7283
8182
  let enumV = [];
7284
- const { length } = props.model[modelName];
8183
+ const { length } = list;
7285
8184
  const key = props.config.key || "id";
7286
8185
  let inputs = {};
7287
8186
  if (enumValues.length) {
7288
- if (length >= enumValues.length) return;
8187
+ if (length >= enumValues.length) return null;
7289
8188
  enumV = enumValues.filter((item) => {
7290
8189
  let i = 0;
7291
- for (; i < length; i++) if (item[key] === props.model[modelName][i][key]) break;
8190
+ for (; i < length; i++) if (item[key] === list[i][key]) break;
7292
8191
  return i === length;
7293
8192
  });
7294
8193
  if (enumV.length > 0) inputs = enumV[0];
@@ -7297,7 +8196,7 @@
7297
8196
  });
7298
8197
  else {
7299
8198
  if (typeof props.config.defaultAdd === "function") inputs = await props.config.defaultAdd(mForm, {
7300
- model: props.model[modelName],
8199
+ model: list,
7301
8200
  prop: props.prop,
7302
8201
  formValue: mForm?.values
7303
8202
  });
@@ -7307,11 +8206,13 @@
7307
8206
  initValues: inputs
7308
8207
  });
7309
8208
  }
7310
- if (props.sortKey && length) inputs[props.sortKey] = props.model[modelName][length - 1][props.sortKey] - 1;
7311
- emit("change", [...props.model[modelName], inputs], { changeRecords: [{
7312
- propPath: `${props.prop}.${props.model[modelName].length}`,
8209
+ applyMountValueEffects(mForm, columns, inputs);
8210
+ if (props.sortKey && length) inputs[props.sortKey] = list[length - 1][props.sortKey] - 1;
8211
+ emit("change", [...list, inputs], { changeRecords: [{
8212
+ propPath: `${props.prop}.${length}`,
7313
8213
  value: inputs
7314
8214
  }] });
8215
+ return length + 1;
7315
8216
  };
7316
8217
  return {
7317
8218
  addable,
@@ -7319,6 +8220,76 @@
7319
8220
  };
7320
8221
  };
7321
8222
  //#endregion
8223
+ //#region packages/form/src/containers/table-group-list/useScrollLastItemIntoView.ts
8224
+ var nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
8225
+ /** 滚动要用的列表根与最后一项;组件已卸载(宿主重建了整棵表单)时返回 null */
8226
+ var resolveScrollTarget = (listRef) => {
8227
+ const inst = listRef.value;
8228
+ const root = inst instanceof HTMLElement ? inst : inst?.$el;
8229
+ if (!(root instanceof HTMLElement)) return null;
8230
+ const items = root.querySelectorAll(":scope > .m-fields-group-list-item");
8231
+ const last = items[items.length - 1];
8232
+ return {
8233
+ root,
8234
+ last: last instanceof HTMLElement ? last : null,
8235
+ count: items.length
8236
+ };
8237
+ };
8238
+ /** 吸顶标题自身的高度:滚动后新项的标题不能被上一层标题压住 */
8239
+ var getStickyHeaderOffset = (item) => {
8240
+ const header = item.querySelector(":scope > .el-card__header, :scope > .t-card__header");
8241
+ if (!(header instanceof HTMLElement)) return 0;
8242
+ return Number.parseFloat(getComputedStyle(header).top) || 0;
8243
+ };
8244
+ /** 吸底「新增」按钮盖住的高度;嵌套列表的 footer 还用 bottom 给外层按钮留了位 */
8245
+ var getStickyFooterOffset = (root) => {
8246
+ const footer = root.querySelector(":scope > .m-fields-group-list-footer.is-sticky-full");
8247
+ if (!(footer instanceof HTMLElement)) return 0;
8248
+ return footer.getBoundingClientRect().height + (Number.parseFloat(getComputedStyle(footer).bottom) || 0);
8249
+ };
8250
+ /**
8251
+ * 等列表渲染出至少 `expectedCount` 项、且最后一项的 DOM 节点连续两帧不变再返回。
8252
+ *
8253
+ * 新增只抛 change,写回要经过宿主(如编辑器属性面板)的异步校验与表单值重建,
8254
+ * 新项不一定当帧就出现;期间宿主还可能自己再追加项,末尾节点会换掉。
8255
+ * 只判「节点不变」会滚到旧的最后一项上,所以先等够数量再等稳定。
8256
+ */
8257
+ var waitForStableTarget = async (listRef, expectedCount, timeout = 600) => {
8258
+ const deadline = Date.now() + timeout;
8259
+ let previous = null;
8260
+ while (Date.now() < deadline) {
8261
+ const target = resolveScrollTarget(listRef);
8262
+ if (!target) return null;
8263
+ if (target.count >= expectedCount && target.last && target.last === previous) return target;
8264
+ previous = target.last;
8265
+ await nextFrame();
8266
+ }
8267
+ return resolveScrollTarget(listRef);
8268
+ };
8269
+ /**
8270
+ * 新增后把最后一项滚进视口,避开吸顶标题与吸底按钮。
8271
+ *
8272
+ * `enabled` 为 false(未开配置、或不在 group-list 形态)时直接返回。
8273
+ */
8274
+ var useScrollLastItemIntoView = (listRef, enabled) => {
8275
+ const scrollLastItemIntoView = async (expectedCount) => {
8276
+ if (!enabled()) return;
8277
+ await (0, vue.nextTick)();
8278
+ const target = await waitForStableTarget(listRef, expectedCount);
8279
+ if (!target?.last) return;
8280
+ const { root, last } = target;
8281
+ const top = getStickyHeaderOffset(last);
8282
+ const bottom = getStickyFooterOffset(root);
8283
+ last.style.scrollMarginTop = top ? `${top}px` : "";
8284
+ last.style.scrollMarginBottom = bottom ? `${bottom}px` : "";
8285
+ last.scrollIntoView({
8286
+ behavior: "auto",
8287
+ block: "nearest"
8288
+ });
8289
+ };
8290
+ return { scrollLastItemIntoView };
8291
+ };
8292
+ //#endregion
7322
8293
  //#region packages/form/src/containers/table-group-list/TableGroupList.vue
7323
8294
  var TableGroupList_default = /* @__PURE__ */ (0, vue.defineComponent)({
7324
8295
  name: "MFormTableGroupList",
@@ -7332,6 +8303,7 @@
7332
8303
  name: {},
7333
8304
  prop: {},
7334
8305
  labelWidth: {},
8306
+ labelPosition: {},
7335
8307
  disabled: { type: Boolean },
7336
8308
  size: {},
7337
8309
  enableToggleMode: { type: Boolean },
@@ -7348,63 +8320,47 @@
7348
8320
  const props = __props;
7349
8321
  const emit = __emit;
7350
8322
  const { addable, newHandler } = useAdd(props, emit);
7351
- const isGroupListType = (type) => type === "groupList" || type === "group-list";
7352
8323
  const displayMode = (0, vue.ref)(isGroupListType(props.config.type) ? "groupList" : "table");
7353
- const calcLabelWidth = (label) => {
7354
- if (!label) return "0px";
7355
- const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
7356
- const chLength = label.length - zhLength;
7357
- return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
7358
- };
7359
- const tableConfig = (0, vue.computed)(() => {
7360
- if (!isGroupListType(props.config.type)) return props.config;
7361
- const source = props.config;
7362
- return {
7363
- ...props.config,
7364
- type: "table",
7365
- groupItems: source.items,
7366
- items: source.tableItems || source.items.map((item) => ({
7367
- ...item,
7368
- label: item.label || item.text,
7369
- text: null
7370
- }))
7371
- };
7372
- });
7373
- const groupListConfig = (0, vue.computed)(() => {
7374
- if (isGroupListType(props.config.type)) return props.config;
7375
- const source = props.config;
7376
- return {
7377
- ...props.config,
7378
- type: "groupList",
7379
- tableItems: source.items,
7380
- items: source.groupItems || source.items.map((item) => {
7381
- const text = item.text || item.label;
7382
- return {
7383
- ...item,
7384
- text,
7385
- labelWidth: calcLabelWidth(text),
7386
- span: item.span || 12
7387
- };
7388
- })
7389
- };
7390
- });
8324
+ const tableConfig = (0, vue.computed)(() => toTableConfig(props.config));
8325
+ const groupListConfig = (0, vue.computed)(() => toGroupListConfig(props.config));
7391
8326
  const currentConfig = (0, vue.computed)(() => displayMode.value === "table" ? tableConfig.value : groupListConfig.value);
7392
8327
  const addButtonSize = (0, vue.computed)(() => {
8328
+ if (currentConfig.value.addButtonConfig?.sticky) return "default";
7393
8329
  if (displayMode.value === "table") return "small";
7394
8330
  return props.config.enableToggleMode !== false ? "small" : "default";
7395
8331
  });
8332
+ const addButtonProps = (0, vue.computed)(() => {
8333
+ const custom = currentConfig.value.addButtonConfig?.props;
8334
+ if (custom) return {
8335
+ type: "primary",
8336
+ ...custom
8337
+ };
8338
+ if (displayMode.value === "table") return {
8339
+ type: "primary",
8340
+ plain: true
8341
+ };
8342
+ return {
8343
+ type: "primary",
8344
+ text: true
8345
+ };
8346
+ });
7396
8347
  const toggleDisplayMode = () => {
7397
8348
  displayMode.value = displayMode.value === "table" ? "groupList" : "table";
7398
8349
  };
8350
+ const tableGroupListRef = (0, vue.useTemplateRef)("tableGroupList");
8351
+ const { scrollLastItemIntoView } = useScrollLastItemIntoView(tableGroupListRef, () => Boolean(currentConfig.value.scrollLastItemIntoView) && displayMode.value === "groupList");
8352
+ const handleAdd = async () => {
8353
+ const expectedCount = await newHandler();
8354
+ if (expectedCount !== null) await scrollLastItemIntoView(expectedCount);
8355
+ };
7399
8356
  const onChange = (v, eventData) => emit("change", v, eventData);
7400
8357
  const onSelect = (...args) => emit("select", ...args);
7401
8358
  const onAddDiffCount = () => emit("addDiffCount");
7402
- const onAdd = (rows) => {
7403
- rows.forEach((row) => {
7404
- newHandler(row);
7405
- });
8359
+ const onAdd = async (rows) => {
8360
+ let expectedCount = null;
8361
+ for (const row of rows) expectedCount = await newHandler(row) ?? expectedCount;
8362
+ if (expectedCount !== null) await scrollLastItemIntoView(expectedCount);
7406
8363
  };
7407
- const tableGroupListRef = (0, vue.useTemplateRef)("tableGroupList");
7408
8364
  __expose({ toggleRowSelection: (row, selected) => tableGroupListRef.value?.toggleRowSelection?.(row, selected) });
7409
8365
  return (_ctx, _cache) => {
7410
8366
  return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(displayMode.value === "table" ? Table_default : GroupList_default), (0, vue.mergeProps)({ ref: "tableGroupList" }, _ctx.$attrs, {
@@ -7417,6 +8373,7 @@
7417
8373
  "last-values": __props.lastValues,
7418
8374
  prop: __props.prop,
7419
8375
  "label-width": __props.labelWidth,
8376
+ "label-position": __props.labelPosition,
7420
8377
  "show-index": __props.showIndex,
7421
8378
  "sort-key": __props.sortKey,
7422
8379
  sort: __props.sort,
@@ -7424,39 +8381,43 @@
7424
8381
  onSelect,
7425
8382
  onAddDiffCount,
7426
8383
  onAdd
7427
- }), (0, vue.createSlots)({
7428
- "toggle-button": (0, vue.withCtx)(() => [__props.config.enableToggleMode || __props.enableToggleMode ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
7429
- key: 0,
7430
- icon: (0, vue.unref)(_element_plus_icons_vue.Grid),
7431
- size: "small",
7432
- onClick: toggleDisplayMode
7433
- }, {
7434
- default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(displayMode.value === "table" ? "展开配置" : "切换为表格"), 1)]),
7435
- _: 1
7436
- }, 8, ["icon"])) : (0, vue.createCommentVNode)("v-if", true)]),
7437
- _: 2
7438
- }, [(0, vue.unref)(addable) ? {
7439
- name: "add-button",
7440
- fn: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
7441
- class: displayMode.value === "table" ? "m-form-table-add-button" : "",
7442
- size: addButtonSize.value,
7443
- plain: displayMode.value === "table",
7444
- icon: (0, vue.unref)(_element_plus_icons_vue.Plus),
7445
- text: "",
7446
- disabled: __props.disabled
7447
- }, currentConfig.value.addButtonConfig?.props || { type: "primary" }, { onClick: (0, vue.unref)(newHandler) }), {
7448
- default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(currentConfig.value.addButtonConfig?.text || (displayMode.value === "table" ? `新增一行${groupListConfig.value.titlePrefix || ""}` : `新增${groupListConfig.value.titlePrefix || ""}`)), 1)]),
7449
- _: 1
7450
- }, 16, [
7451
- "class",
7452
- "size",
7453
- "plain",
7454
- "icon",
7455
- "disabled",
7456
- "onClick"
7457
- ])]),
7458
- key: "0"
7459
- } : void 0]), 1040, [
8384
+ }), (0, vue.createSlots)({ _: 2 }, [
8385
+ __props.config.enableToggleMode || __props.enableToggleMode ? {
8386
+ name: "toggle-button",
8387
+ fn: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
8388
+ icon: (0, vue.unref)(_element_plus_icons_vue.Grid),
8389
+ size: "small",
8390
+ onClick: toggleDisplayMode
8391
+ }, {
8392
+ default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(displayMode.value === "table" ? "展开配置" : "切换为表格"), 1)]),
8393
+ _: 1
8394
+ }, 8, ["icon"])]),
8395
+ key: "0"
8396
+ } : void 0,
8397
+ (0, vue.unref)(addable) ? {
8398
+ name: "add-button",
8399
+ fn: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
8400
+ class: displayMode.value === "table" ? "m-form-table-add-button" : "",
8401
+ size: addButtonSize.value,
8402
+ icon: (0, vue.unref)(_element_plus_icons_vue.Plus),
8403
+ disabled: __props.disabled
8404
+ }, addButtonProps.value, { onClick: handleAdd }), {
8405
+ default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(currentConfig.value.addButtonConfig?.text || (displayMode.value === "table" ? `新增一行${groupListConfig.value.titlePrefix || ""}` : `新增${groupListConfig.value.titlePrefix || ""}`)), 1)]),
8406
+ _: 1
8407
+ }, 16, [
8408
+ "class",
8409
+ "size",
8410
+ "icon",
8411
+ "disabled"
8412
+ ])]),
8413
+ key: "1"
8414
+ } : void 0,
8415
+ _ctx.$slots.title ? {
8416
+ name: "title",
8417
+ fn: (0, vue.withCtx)((slotProps) => [(0, vue.renderSlot)(_ctx.$slots, "title", (0, vue.normalizeProps)((0, vue.guardReactiveProps)(slotProps)))]),
8418
+ key: "2"
8419
+ } : void 0
8420
+ ]), 1040, [
7460
8421
  "model",
7461
8422
  "name",
7462
8423
  "config",
@@ -7466,6 +8427,7 @@
7466
8427
  "last-values",
7467
8428
  "prop",
7468
8429
  "label-width",
8430
+ "label-position",
7469
8431
  "show-index",
7470
8432
  "sort-key",
7471
8433
  "sort"
@@ -7784,13 +8746,13 @@
7784
8746
  }
7785
8747
  });
7786
8748
  //#endregion
7787
- //#region packages/form/src/fields/NumberRange.vue?vue&type=script&setup=true&lang.ts
8749
+ //#region packages/form/src/fields/NumberRange/Index.vue?vue&type=script&setup=true&lang.ts
7788
8750
  var _hoisted_1$5 = { class: "m-fields-number-range" };
7789
8751
  //#endregion
7790
- //#region packages/form/src/fields/NumberRange.vue
7791
- var NumberRange_default = /* @__PURE__ */ (0, vue.defineComponent)({
8752
+ //#region packages/form/src/fields/NumberRange/Index.vue
8753
+ var Index_default$5 = /* @__PURE__ */ (0, vue.defineComponent)({
7792
8754
  name: "MFormNumberRange",
7793
- __name: "NumberRange",
8755
+ __name: "Index",
7794
8756
  props: {
7795
8757
  config: {},
7796
8758
  model: {},
@@ -7820,7 +8782,6 @@
7820
8782
  deep: true
7821
8783
  });
7822
8784
  useAddField(props.prop);
7823
- if (!Array.isArray(props.model[props.name])) props.model[props.name] = [];
7824
8785
  const minChangeHandler = (v) => {
7825
8786
  emit("change", [Number(v), props.model[props.name][1]]);
7826
8787
  };
@@ -7920,10 +8881,10 @@
7920
8881
  }
7921
8882
  });
7922
8883
  //#endregion
7923
- //#region packages/form/src/fields/Date.vue
7924
- var Date_default = /* @__PURE__ */ (0, vue.defineComponent)({
8884
+ //#region packages/form/src/fields/Date/Index.vue
8885
+ var Index_default$1 = /* @__PURE__ */ (0, vue.defineComponent)({
7925
8886
  name: "MFormDate",
7926
- __name: "Date",
8887
+ __name: "Index",
7927
8888
  props: {
7928
8889
  config: {},
7929
8890
  model: {},
@@ -7944,7 +8905,6 @@
7944
8905
  const props = __props;
7945
8906
  const emit = __emit;
7946
8907
  useAddField(props.prop);
7947
- props.model[props.name] = datetimeFormatter(props.model[props.name], "", props.config.valueFormat || "YYYY/MM/DD");
7948
8908
  const changeHandler = (v) => {
7949
8909
  emit("change", v);
7950
8910
  };
@@ -7970,10 +8930,10 @@
7970
8930
  }
7971
8931
  });
7972
8932
  //#endregion
7973
- //#region packages/form/src/fields/DateTime.vue
7974
- var DateTime_default = /* @__PURE__ */ (0, vue.defineComponent)({
8933
+ //#region packages/form/src/fields/DateTime/Index.vue
8934
+ var Index_default$2 = /* @__PURE__ */ (0, vue.defineComponent)({
7975
8935
  name: "MFormDateTime",
7976
- __name: "DateTime",
8936
+ __name: "Index",
7977
8937
  props: {
7978
8938
  config: {},
7979
8939
  model: {},
@@ -7994,9 +8954,6 @@
7994
8954
  const props = __props;
7995
8955
  const emit = __emit;
7996
8956
  useAddField(props.prop);
7997
- const value = props.model?.[props.name]?.toString();
7998
- if (props.model) if (!value || value === "Invalid Date") props.model[props.name] = "";
7999
- else props.model[props.name] = datetimeFormatter(props.model[props.name], "", props.config.valueFormat || "YYYY/MM/DD HH:mm:ss");
8000
8957
  const changeHandler = (v) => {
8001
8958
  emit("change", v);
8002
8959
  };
@@ -8426,10 +9383,10 @@
8426
9383
  }
8427
9384
  });
8428
9385
  //#endregion
8429
- //#region packages/form/src/fields/CheckboxGroup.vue
8430
- var CheckboxGroup_default = /* @__PURE__ */ (0, vue.defineComponent)({
9386
+ //#region packages/form/src/fields/CheckboxGroup/Index.vue
9387
+ var Index_default = /* @__PURE__ */ (0, vue.defineComponent)({
8431
9388
  name: "MFormCheckGroup",
8432
- __name: "CheckboxGroup",
9389
+ __name: "Index",
8433
9390
  props: {
8434
9391
  config: {},
8435
9392
  model: {},
@@ -8450,7 +9407,6 @@
8450
9407
  const props = __props;
8451
9408
  const emit = __emit;
8452
9409
  useAddField(props.prop);
8453
- if (props.model && !props.model[props.name]) props.model[props.name] = [];
8454
9410
  const changeHandler = (v) => {
8455
9411
  emit("change", v);
8456
9412
  };
@@ -8571,13 +9527,13 @@
8571
9527
  }
8572
9528
  });
8573
9529
  //#endregion
8574
- //#region packages/form/src/fields/Display.vue?vue&type=script&setup=true&lang.ts
9530
+ //#region packages/form/src/fields/Display/Index.vue?vue&type=script&setup=true&lang.ts
8575
9531
  var _hoisted_1$3 = { key: 0 };
8576
9532
  //#endregion
8577
- //#region packages/form/src/fields/Display.vue
8578
- var Display_default = /* @__PURE__ */ (0, vue.defineComponent)({
9533
+ //#region packages/form/src/fields/Display/Index.vue
9534
+ var Index_default$3 = /* @__PURE__ */ (0, vue.defineComponent)({
8579
9535
  name: "MFormDisplay",
8580
- __name: "Display",
9536
+ __name: "Index",
8581
9537
  props: {
8582
9538
  config: {},
8583
9539
  model: {},
@@ -8596,7 +9552,6 @@
8596
9552
  setup(__props) {
8597
9553
  const props = __props;
8598
9554
  const mForm = (0, vue.inject)("mForm");
8599
- if (props.config.initValue && props.model) props.model[props.name] = props.config.initValue;
8600
9555
  const text = (0, vue.computed)(() => {
8601
9556
  if (props.config.displayText) return filterFunction(mForm, props.config.displayText, props);
8602
9557
  return props.model[props.name];
@@ -9204,13 +10159,50 @@
9204
10159
  }
9205
10160
  });
9206
10161
  //#endregion
9207
- //#region packages/form/src/fields/DynamicField.vue?vue&type=script&setup=true&lang.ts
10162
+ //#region packages/form/src/fields/DynamicField/effect.ts
10163
+ /**
10164
+ * 遍历动态字段列表,按「原值为空且声明了 defaultValue 则取默认值」求出每个字段当前的值。
10165
+ *
10166
+ * `isDefaultApplied` 为真表示该值来自 `defaultValue`,需要写回表单值
10167
+ * (组件通过 emit change 写回,初始化写入直接写 `values`)。
10168
+ */
10169
+ var eachDynamicField = (fields, model, onField) => {
10170
+ for (const field of fields) {
10171
+ if (typeof field !== "object" || field?.name === void 0) continue;
10172
+ let value = model?.[field.name] || "";
10173
+ let isDefaultApplied = false;
10174
+ if (!value && field.defaultValue !== void 0) {
10175
+ value = field.defaultValue;
10176
+ isDefaultApplied = true;
10177
+ }
10178
+ onField(field, value, isDefaultApplied);
10179
+ }
10180
+ };
10181
+ /**
10182
+ * 同步 `returnFields` 的 defaultValue 按 prop 写入值根对象。
10183
+ *
10184
+ * 该组件读取的是同级 model,但写入走 Container 的 modifyKey 分支,落在 `${prop}.${key}`,
10185
+ * 这里保持与渲染一致(含这层不对称),避免两条链路产出不同的值。
10186
+ * 异步 returnFields 由组件挂载后的 watch 负责,此处不等待。
10187
+ */
10188
+ var effect$5 = ({ config, model, prop, values }) => {
10189
+ const { returnFields, dynamicKey } = config;
10190
+ if (typeof returnFields !== "function" || !model) return;
10191
+ if (model[dynamicKey] === "") return;
10192
+ const result = returnFields(config, model, getConfig("request"));
10193
+ if (!result || typeof result.then === "function" || !Array.isArray(result)) return;
10194
+ eachDynamicField(result, model, (field, value, isDefaultApplied) => {
10195
+ if (isDefaultApplied) (0, _tmagic_utils.setValueByKeyPath)(`${prop}.${field.name}`, value, values || model);
10196
+ });
10197
+ };
10198
+ //#endregion
10199
+ //#region packages/form/src/fields/DynamicField/Index.vue?vue&type=script&setup=true&lang.ts
9208
10200
  var _hoisted_1 = { class: "m-fields-dynamic-field" };
9209
10201
  //#endregion
9210
- //#region packages/form/src/fields/DynamicField.vue
9211
- var DynamicField_default = /* @__PURE__ */ (0, vue.defineComponent)({
10202
+ //#region packages/form/src/fields/DynamicField/Index.vue
10203
+ var Index_default$4 = /* @__PURE__ */ (0, vue.defineComponent)({
9212
10204
  name: "MFormDynamicField",
9213
- __name: "DynamicField",
10205
+ __name: "Index",
9214
10206
  props: {
9215
10207
  config: {},
9216
10208
  model: {},
@@ -9248,15 +10240,10 @@
9248
10240
  const fields = await props.config.returnFields(props.config, props.model, request);
9249
10241
  fieldMap.value = {};
9250
10242
  fieldLabelMap.value = {};
9251
- fields.forEach((v) => {
9252
- if (typeof v !== "object" || v.name === void 0) return;
9253
- let oldVal = props.model?.[v.name] || "";
9254
- if (!oldVal && v.defaultValue !== void 0) {
9255
- oldVal = v.defaultValue;
9256
- emit("change", oldVal, { modifyKey: v.name });
9257
- }
9258
- fieldMap.value[v.name] = oldVal;
9259
- fieldLabelMap.value[v.name] = v.label || "";
10243
+ eachDynamicField(fields, props.model, (field, value, isDefaultApplied) => {
10244
+ if (isDefaultApplied) emit("change", value, { modifyKey: field.name });
10245
+ fieldMap.value[field.name] = value;
10246
+ fieldLabelMap.value[field.name] = field.label || "";
9260
10247
  });
9261
10248
  };
9262
10249
  const unwatch = (0, vue.watch)(() => props.model?.[props.config.dynamicKey], (val) => {
@@ -9295,146 +10282,245 @@
9295
10282
  }
9296
10283
  });
9297
10284
  //#endregion
9298
- //#region packages/form/src/containers/Step.vue
9299
- var Step_default = /* @__PURE__ */ (0, vue.defineComponent)({
9300
- name: "MFormStep",
9301
- __name: "Step",
9302
- props: {
9303
- model: {},
9304
- name: {},
9305
- lastValues: {},
9306
- isCompare: { type: Boolean },
9307
- config: {},
9308
- stepActive: { default: 1 },
9309
- labelWidth: {},
9310
- size: {},
9311
- disabled: { type: Boolean }
9312
- },
9313
- emits: ["change", "addDiffCount"],
9314
- setup(__props, { emit: __emit }) {
9315
- const props = __props;
9316
- const emit = __emit;
9317
- const mForm = (0, vue.inject)("mForm");
9318
- const active = (0, vue.ref)(1);
9319
- (0, vue.watchEffect)(() => {
9320
- active.value = props.stepActive;
9321
- });
9322
- const stepClick = (index) => {
9323
- active.value = index + 1;
9324
- mForm?.$emit("update:stepActive", active.value);
9325
- };
9326
- const changeHandler = (v, eventData) => {
9327
- emit("change", props.model, eventData);
9328
- };
9329
- const onAddDiffCount = () => emit("addDiffCount");
9330
- return (_ctx, _cache) => {
9331
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", null, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicSteps), {
9332
- active: active.value,
9333
- "align-center": "",
9334
- space: __props.config.space
9335
- }, {
9336
- default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
9337
- return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicStep), {
9338
- key: item.__key,
9339
- title: item.title,
9340
- active: active.value,
9341
- onClick: ($event) => stepClick(index)
9342
- }, null, 8, [
9343
- "title",
9344
- "active",
9345
- "onClick"
9346
- ]);
9347
- }), 128))]),
9348
- _: 1
9349
- }, 8, ["active", "space"]), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (step, index) => {
9350
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(step.items, (item) => {
9351
- return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [item ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
9352
- key: item[(0, vue.unref)(mForm)?.keyProp || "__key"],
9353
- config: item,
9354
- model: step.name ? __props.model[step.name] : __props.model,
9355
- lastValues: step.name ? __props.lastValues[step.name] : __props.lastValues,
9356
- "is-compare": __props.isCompare,
9357
- prop: `${step.name}`,
9358
- size: __props.size,
9359
- disabled: __props.disabled,
9360
- "label-width": __props.config.labelWidth || __props.labelWidth,
9361
- onChange: changeHandler,
9362
- onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
9363
- }, null, 8, [
9364
- "config",
9365
- "model",
9366
- "lastValues",
9367
- "is-compare",
9368
- "prop",
9369
- "size",
9370
- "disabled",
9371
- "label-width"
9372
- ])), [[vue.vShow, active.value - 1 === index]]) : (0, vue.createCommentVNode)("v-if", true)], 64);
9373
- }), 256))], 64);
9374
- }), 256))]);
9375
- };
10285
+ //#region packages/form/src/fields/CheckboxGroup/effect.ts
10286
+ /** 空值初始化为空数组。 */
10287
+ var effect$4 = ({ config, model }) => {
10288
+ const { name } = config;
10289
+ if (model && !model[name]) model[name] = [];
10290
+ };
10291
+ //#endregion
10292
+ //#region packages/form/src/fields/Date/effect.ts
10293
+ /** 按 `valueFormat` 归一化日期值。 */
10294
+ var effect$3 = ({ config, model }) => {
10295
+ if (!model) return;
10296
+ const { name, valueFormat } = config;
10297
+ model[name] = datetimeFormatter(model[name], "", valueFormat || "YYYY/MM/DD");
10298
+ };
10299
+ //#endregion
10300
+ //#region packages/form/src/fields/DateTime/effect.ts
10301
+ /** 按 `valueFormat` 归一化日期时间值,空值与非法值统一为空字符串。 */
10302
+ var effect$2 = ({ config, model }) => {
10303
+ if (!model) return;
10304
+ const { name, valueFormat } = config;
10305
+ const value = model[name]?.toString();
10306
+ if (!value || value === "Invalid Date") {
10307
+ model[name] = "";
10308
+ return;
9376
10309
  }
9377
- });
10310
+ model[name] = datetimeFormatter(model[name], "", valueFormat || "YYYY/MM/DD HH:mm:ss");
10311
+ };
10312
+ //#endregion
10313
+ //#region packages/form/src/fields/Display/effect.ts
10314
+ /** 把 `initValue` 写入 model。 */
10315
+ var effect$1 = ({ config, model }) => {
10316
+ const { initValue, name } = config;
10317
+ if (initValue && model) model[name] = initValue;
10318
+ };
10319
+ //#endregion
10320
+ //#region packages/form/src/fields/NumberRange/effect.ts
10321
+ /** 值不是数组时修正为空数组。 */
10322
+ var effect = ({ config, model }) => {
10323
+ const { name } = config;
10324
+ if (model && !Array.isArray(model[name])) model[name] = [];
10325
+ };
10326
+ //#endregion
10327
+ //#region packages/form/src/utils/builtInFields.ts
10328
+ /**
10329
+ * 内置字段的无渲染登记表(不含 Vue 组件)。
10330
+ *
10331
+ * 本模块只导出数据,不登记。调用方自行 `registerBuiltInFields(builtInFields)`,
10332
+ * 或 `app.use(MagicForm)`(install 里会登记叠上 Vue 组件后的表)。
10333
+ */
10334
+ var builtInFields = {
10335
+ text: {},
10336
+ "img-upload": {},
10337
+ number: {},
10338
+ "number-range": { effect },
10339
+ textarea: {},
10340
+ hidden: {},
10341
+ date: { effect: effect$3 },
10342
+ datetime: { effect: effect$2 },
10343
+ daterange: {},
10344
+ timerange: {},
10345
+ time: {},
10346
+ checkbox: {},
10347
+ switch: {},
10348
+ "color-picker": {},
10349
+ "checkbox-group": { effect: effect$4 },
10350
+ "radio-group": {},
10351
+ display: { effect: effect$1 },
10352
+ link: {},
10353
+ select: {},
10354
+ cascader: {},
10355
+ "dynamic-field": { effect: effect$5 },
10356
+ component: {},
10357
+ tab: { walk: expandTab },
10358
+ row: { walk: expandRow },
10359
+ "flex-layout": { walk: expandRow },
10360
+ fieldset: { walk: expandFieldset },
10361
+ panel: { walk: expandPanel },
10362
+ step: { walk: expandStep },
10363
+ table: { walk: expandTableGroupList },
10364
+ "group-list": { walk: expandTableGroupList },
10365
+ "table-group-list": { walk: expandTableGroupList }
10366
+ };
9378
10367
  //#endregion
9379
10368
  //#region packages/form/src/plugin.ts
10369
+ var builtInFieldVue = {
10370
+ text: { component: Text_default },
10371
+ "img-upload": { component: Text_default },
10372
+ number: { component: Number_default },
10373
+ "number-range": { component: Index_default$5 },
10374
+ textarea: { component: Textarea_default },
10375
+ hidden: { component: Hidden_default },
10376
+ date: { component: Index_default$1 },
10377
+ datetime: { component: Index_default$2 },
10378
+ daterange: { component: Daterange_default },
10379
+ timerange: { component: Timerange_default },
10380
+ time: { component: Time_default },
10381
+ checkbox: { component: Checkbox_default },
10382
+ switch: { component: Switch_default },
10383
+ "color-picker": { component: ColorPicker_default },
10384
+ "checkbox-group": { component: Index_default },
10385
+ "radio-group": { component: RadioGroup_default },
10386
+ display: { component: Index_default$3 },
10387
+ link: { component: Link_default },
10388
+ select: { component: Select_default },
10389
+ cascader: { component: Cascader_default },
10390
+ "dynamic-field": { component: Index_default$4 },
10391
+ container: { container: Container_default },
10392
+ tab: { container: Tabs_default },
10393
+ row: { container: Row_default },
10394
+ "flex-layout": { container: FlexLayout_default },
10395
+ fieldset: { container: Fieldset_default },
10396
+ panel: { container: Panel_default },
10397
+ step: { container: /* @__PURE__ */ (0, vue.defineComponent)({
10398
+ name: "MFormStep",
10399
+ __name: "Step",
10400
+ props: {
10401
+ model: {},
10402
+ name: {},
10403
+ lastValues: {},
10404
+ isCompare: { type: Boolean },
10405
+ config: {},
10406
+ stepActive: { default: 1 },
10407
+ labelWidth: {},
10408
+ labelPosition: {},
10409
+ size: {},
10410
+ disabled: { type: Boolean }
10411
+ },
10412
+ emits: ["change", "addDiffCount"],
10413
+ setup(__props, { emit: __emit }) {
10414
+ const props = __props;
10415
+ const emit = __emit;
10416
+ const mForm = (0, vue.inject)("mForm");
10417
+ const active = (0, vue.ref)(1);
10418
+ (0, vue.watchEffect)(() => {
10419
+ active.value = props.stepActive;
10420
+ });
10421
+ const stepClick = (index) => {
10422
+ active.value = index + 1;
10423
+ mForm?.$emit("update:stepActive", active.value);
10424
+ };
10425
+ const changeHandler = (v, eventData) => {
10426
+ emit("change", props.model, eventData);
10427
+ };
10428
+ const onAddDiffCount = () => emit("addDiffCount");
10429
+ return (_ctx, _cache) => {
10430
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", null, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicSteps), {
10431
+ active: active.value,
10432
+ "align-center": "",
10433
+ space: __props.config.space
10434
+ }, {
10435
+ default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
10436
+ return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicStep), {
10437
+ key: item.__key,
10438
+ title: item.title,
10439
+ active: active.value,
10440
+ onClick: ($event) => stepClick(index)
10441
+ }, null, 8, [
10442
+ "title",
10443
+ "active",
10444
+ "onClick"
10445
+ ]);
10446
+ }), 128))]),
10447
+ _: 1
10448
+ }, 8, ["active", "space"]), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (step, index) => {
10449
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(step.items, (item) => {
10450
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [item ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
10451
+ key: item[(0, vue.unref)(mForm)?.keyProp || "__key"],
10452
+ config: item,
10453
+ model: step.name ? __props.model[step.name] : __props.model,
10454
+ lastValues: step.name ? __props.lastValues[step.name] : __props.lastValues,
10455
+ "is-compare": __props.isCompare,
10456
+ prop: `${step.name}`,
10457
+ size: __props.size,
10458
+ disabled: __props.disabled,
10459
+ "label-width": __props.config.labelWidth || __props.labelWidth,
10460
+ "label-position": __props.config.labelPosition || __props.labelPosition,
10461
+ onChange: changeHandler,
10462
+ onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
10463
+ }, null, 8, [
10464
+ "config",
10465
+ "model",
10466
+ "lastValues",
10467
+ "is-compare",
10468
+ "prop",
10469
+ "size",
10470
+ "disabled",
10471
+ "label-width",
10472
+ "label-position"
10473
+ ])), [[vue.vShow, active.value - 1 === index]]) : (0, vue.createCommentVNode)("v-if", true)], 64);
10474
+ }), 256))], 64);
10475
+ }), 256))]);
10476
+ };
10477
+ }
10478
+ }) },
10479
+ table: { container: TableGroupList_default },
10480
+ "group-list": { container: TableGroupList_default },
10481
+ "table-group-list": { container: TableGroupList_default }
10482
+ };
9380
10483
  var defaultInstallOpt = {};
9381
- var plugin_default = { install(app, opt = {}) {
9382
- const option = Object.assign(defaultInstallOpt, opt);
10484
+ var plugin_default = {
10485
+ /**
10486
+ * 安装 `@tmagic/form`:登记内置字段、挂载 `m-form` / `m-form-dialog` / `m-form-drawer`。
10487
+ *
10488
+ * @param app - Vue 应用
10489
+ * @param [opt] - 安装选项
10490
+ */
10491
+ install(app, opt = {}) {
10492
+ const option = {
10493
+ ...defaultInstallOpt,
10494
+ ...opt
10495
+ };
9383
10496
  app.config.globalProperties.$MAGIC_FORM = option;
9384
10497
  setConfig(option);
9385
- if (option.typeMatchRules) registerTypeMatchRules(option.typeMatchRules);
10498
+ registerBuiltInFields(builtInFields);
10499
+ registerBuiltInFields(builtInFieldVue, app);
10500
+ if (option.fields) registerFields(option.fields, app);
9386
10501
  app.component("m-form", Form_default);
9387
10502
  app.component("m-form-dialog", FormDialog_default);
9388
10503
  app.component("m-form-drawer", FormDrawer_default);
9389
- app.component("m-form-container", Container_default);
9390
- app.component("m-form-fieldset", Fieldset_default);
9391
- app.component("m-form-group-list", TableGroupList_default);
9392
- app.component("m-form-panel", Panel_default);
9393
- app.component("m-form-row", Row_default);
9394
- app.component("m-form-step", Step_default);
9395
- app.component("m-form-table", TableGroupList_default);
9396
- app.component("m-form-tab", Tabs_default);
9397
- app.component("m-form-flex-layout", FlexLayout_default);
9398
- app.component("m-fields-text", Text_default);
9399
- app.component("m-fields-img-upload", Text_default);
9400
- app.component("m-fields-number", Number_default);
9401
- app.component("m-fields-number-range", NumberRange_default);
9402
- app.component("m-fields-textarea", Textarea_default);
9403
- app.component("m-fields-hidden", Hidden_default);
9404
- app.component("m-fields-date", Date_default);
9405
- app.component("m-fields-datetime", DateTime_default);
9406
- app.component("m-fields-daterange", Daterange_default);
9407
- app.component("m-fields-timerange", Timerange_default);
9408
- app.component("m-fields-time", Time_default);
9409
- app.component("m-fields-checkbox", Checkbox_default);
9410
- app.component("m-fields-switch", Switch_default);
9411
- app.component("m-fields-color-picker", ColorPicker_default);
9412
- app.component("m-fields-checkbox-group", CheckboxGroup_default);
9413
- app.component("m-fields-radio-group", RadioGroup_default);
9414
- app.component("m-fields-display", Display_default);
9415
- app.component("m-fields-link", Link_default);
9416
- app.component("m-fields-select", Select_default);
9417
- app.component("m-fields-cascader", Cascader_default);
9418
- app.component("m-fields-dynamic-field", DynamicField_default);
9419
10504
  } };
9420
10505
  //#endregion
9421
10506
  //#region packages/form/src/index.ts
9422
10507
  var createForm = (config) => config;
9423
10508
  //#endregion
10509
+ exports.FORM_CONTEXT_KEY = FORM_CONTEXT_KEY;
9424
10510
  exports.FORM_DIFF_CONFIG_KEY = FORM_DIFF_CONFIG_KEY;
9425
- exports.FORM_SILENT_MODE_KEY = FORM_SILENT_MODE_KEY;
9426
10511
  exports.FORM_TYPE_MATCH_VALID_KEY = FORM_TYPE_MATCH_VALID_KEY;
10512
+ exports.FieldInnerConfigError = FieldInnerConfigError;
9427
10513
  exports.MAX_SUGGESTION_OPTIONS = MAX_SUGGESTION_OPTIONS;
9428
10514
  exports.MCascader = Cascader_default;
9429
10515
  exports.MCheckbox = Checkbox_default;
9430
- exports.MCheckboxGroup = CheckboxGroup_default;
10516
+ exports.MCheckboxGroup = Index_default;
9431
10517
  exports.MColorPicker = ColorPicker_default;
9432
10518
  exports.MContainer = Container_default;
9433
- exports.MDate = Date_default;
9434
- exports.MDateTime = DateTime_default;
10519
+ exports.MDate = Index_default$1;
10520
+ exports.MDateTime = Index_default$2;
9435
10521
  exports.MDaterange = Daterange_default;
9436
- exports.MDisplay = Display_default;
9437
- exports.MDynamicField = DynamicField_default;
10522
+ exports.MDisplay = Index_default$3;
10523
+ exports.MDynamicField = Index_default$4;
9438
10524
  exports.MFieldset = Fieldset_default;
9439
10525
  exports.MFlexLayout = FlexLayout_default;
9440
10526
  exports.MForm = Form_default;
@@ -9445,7 +10531,7 @@
9445
10531
  exports.MHidden = Hidden_default;
9446
10532
  exports.MLink = Link_default;
9447
10533
  exports.MNumber = Number_default;
9448
- exports.MNumberRange = NumberRange_default;
10534
+ exports.MNumberRange = Index_default$5;
9449
10535
  exports.MPanel = Panel_default;
9450
10536
  exports.MRadioGroup = RadioGroup_default;
9451
10537
  exports.MRow = Row_default;
@@ -9459,37 +10545,52 @@
9459
10545
  exports.MTime = Time_default;
9460
10546
  exports.MTimerange = Timerange_default;
9461
10547
  exports.adaptFormValidator = adaptFormValidator;
9462
- exports.applyExtendState = applyExtendState;
9463
- exports.clearSilentLeafFieldTypes = clearSilentLeafFieldTypes;
10548
+ exports.appendProp = appendProp;
10549
+ exports.applyMountValueEffects = applyMountValueEffects;
10550
+ exports.builtInFields = builtInFields;
10551
+ exports.clearFields = clearFields;
9464
10552
  exports.clearTypeMatchRules = clearTypeMatchRules;
10553
+ exports.collectValidatableFields = collectValidatableFields;
9465
10554
  exports.createForm = createForm;
10555
+ exports.createFormStateBase = createFormStateBase;
10556
+ exports.createFormStateProxy = createFormStateProxy;
10557
+ exports.createHeadlessFormState = createHeadlessFormState;
9466
10558
  exports.createObjectProp = createObjectProp;
9467
10559
  exports.createValues = createValues;
9468
10560
  exports.datetimeFormatter = datetimeFormatter;
9469
10561
  exports.default = plugin_default;
9470
- exports.deleteFormField = deleteField;
9471
10562
  exports.deleteTypeMatchRule = deleteTypeMatchRule;
9472
10563
  exports.display = display;
9473
10564
  exports.filterFunction = filterFunction;
10565
+ exports.formatValidateError = formatValidateError;
9474
10566
  exports.getDataByPage = getDataByPage;
9475
10567
  exports.getFormField = getField;
10568
+ exports.getItemProp = getItemProp;
10569
+ exports.getNativeRules = getNativeRules;
9476
10570
  exports.getRules = getRules;
9477
- exports.getSilentLeafFieldTypes = getSilentLeafFieldTypes;
10571
+ exports.getTextByName = getTextByName;
9478
10572
  exports.getTypeMatchRule = getTypeMatchRule;
9479
10573
  exports.initValue = initValue;
10574
+ exports.isFieldInnerConfigError = isFieldInnerConfigError;
10575
+ exports.isLeafFieldType = isLeafFieldType;
10576
+ exports.isValidName = isValidName;
10577
+ exports.mergeFieldOptions = mergeFieldOptions;
10578
+ exports.mergeFormContexts = mergeFormContexts;
9480
10579
  exports.optionSuggestion = optionSuggestion;
9481
- exports.registerFormField = registerField;
9482
- exports.registerSilentLeafFieldTypes = registerSilentLeafFieldTypes;
9483
- exports.registerTypeMatchRule = registerTypeMatchRule;
9484
- exports.registerTypeMatchRules = registerTypeMatchRules;
10580
+ exports.registerBuiltInFields = registerBuiltInFields;
10581
+ exports.registerField = registerField;
10582
+ exports.registerFields = registerFields;
10583
+ exports.resolveItemType = resolveItemType;
9485
10584
  exports.sortArray = sortArray;
9486
10585
  exports.sortChange = sortChange;
9487
10586
  exports.stringifyExampleValue = stringifyExampleValue;
9488
10587
  exports.stripTabItemsLazy = stripTabItemsLazy;
9489
10588
  exports.submitForm = submitForm;
10589
+ exports.unregisterField = unregisterField;
9490
10590
  exports.useAddField = useAddField;
9491
10591
  exports.validateForm = validateForm;
9492
10592
  exports.validateTypeMatch = validateTypeMatch;
10593
+ exports.validateValues = validateValues;
9493
10594
  var _tmagic_form_schema = require("@tmagic/form-schema");
9494
10595
  Object.keys(_tmagic_form_schema).forEach(function(k) {
9495
10596
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {