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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +47 -120
  2. package/dist/es/FormBox.vue_vue_type_script_setup_true_lang.js +3 -3
  3. package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +3 -3
  4. package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +3 -3
  5. package/dist/es/containers/Col.vue_vue_type_script_setup_true_lang.js +3 -0
  6. package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +36 -51
  7. package/dist/es/containers/Fieldset.vue_vue_type_script_setup_true_lang.js +6 -0
  8. package/dist/es/containers/FlexLayout.vue_vue_type_script_setup_true_lang.js +4 -1
  9. package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +37 -15
  10. package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +109 -97
  11. package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +7 -2
  12. package/dist/es/containers/Row.vue_vue_type_script_setup_true_lang.js +3 -0
  13. package/dist/es/containers/Step.vue_vue_type_script_setup_true_lang.js +4 -1
  14. package/dist/es/containers/Tabs.vue_vue_type_script_setup_true_lang.js +7 -0
  15. package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +7 -5
  16. package/dist/es/containers/table/useTableColumns.js +5 -13
  17. package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +71 -78
  18. package/dist/es/containers/table-group-list/useAdd.js +26 -16
  19. package/dist/es/containers/table-group-list/useScrollLastItemIntoView.js +72 -0
  20. package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +1 -1
  21. package/dist/es/fields/CheckboxGroup/Index.js +5 -0
  22. package/dist/es/fields/{CheckboxGroup.vue_vue_type_script_setup_true_lang.js → CheckboxGroup/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
  23. package/dist/es/fields/CheckboxGroup/effect.js +8 -0
  24. package/dist/es/fields/Date/Index.js +5 -0
  25. package/dist/es/fields/{Date.vue_vue_type_script_setup_true_lang.js → Date/Index.vue_vue_type_script_setup_true_lang.js} +5 -7
  26. package/dist/es/fields/Date/effect.js +10 -0
  27. package/dist/es/fields/DateTime/Index.js +5 -0
  28. package/dist/es/fields/{DateTime.vue_vue_type_script_setup_true_lang.js → DateTime/Index.vue_vue_type_script_setup_true_lang.js} +5 -9
  29. package/dist/es/fields/DateTime/effect.js +15 -0
  30. package/dist/es/fields/Display/Index.js +5 -0
  31. package/dist/es/fields/{Display.vue_vue_type_script_setup_true_lang.js → Display/Index.vue_vue_type_script_setup_true_lang.js} +6 -7
  32. package/dist/es/fields/Display/effect.js +8 -0
  33. package/dist/es/fields/DynamicField/Index.js +5 -0
  34. package/dist/es/fields/{DynamicField.vue_vue_type_script_setup_true_lang.js → DynamicField/Index.vue_vue_type_script_setup_true_lang.js} +11 -15
  35. package/dist/es/fields/DynamicField/effect.js +40 -0
  36. package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +1 -1
  37. package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +1 -1
  38. package/dist/es/fields/NumberRange/Index.js +5 -0
  39. package/dist/es/fields/{NumberRange.vue_vue_type_script_setup_true_lang.js → NumberRange/Index.vue_vue_type_script_setup_true_lang.js} +5 -6
  40. package/dist/es/fields/NumberRange/effect.js +8 -0
  41. package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +13 -6
  42. package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +1 -1
  43. package/dist/es/headless.js +13 -0
  44. package/dist/es/index.js +17 -12
  45. package/dist/es/plugin.js +57 -41
  46. package/dist/es/schema.js +6 -8
  47. package/dist/es/style.css +81 -1
  48. package/dist/es/submitForm.js +44 -194
  49. package/dist/es/utils/builtInFields.js +49 -0
  50. package/dist/es/utils/collectFields.js +407 -0
  51. package/dist/es/utils/config.js +1 -8
  52. package/dist/es/utils/fieldInnerConfig.js +42 -0
  53. package/dist/es/utils/fieldValueEffects.js +80 -0
  54. package/dist/es/utils/form.js +96 -43
  55. package/dist/es/utils/formStateProxy.js +111 -0
  56. package/dist/es/utils/registerField.js +158 -0
  57. package/dist/es/utils/submitHeadless.js +54 -0
  58. package/dist/es/utils/tableGroupList.js +85 -0
  59. package/dist/es/utils/typeMatch.js +17 -19
  60. package/dist/es/utils/validateError.js +57 -0
  61. package/dist/es/utils/validateValues.js +115 -0
  62. package/dist/style.css +81 -1
  63. package/dist/themes/magic-admin.css +83 -7
  64. package/dist/tmagic-form-headless.umd.cjs +4080 -0
  65. package/dist/tmagic-form.umd.cjs +2020 -876
  66. package/package.json +10 -4
  67. package/src/Form.vue +49 -149
  68. package/src/FormBox.vue +5 -4
  69. package/src/FormDialog.vue +5 -5
  70. package/src/FormDrawer.vue +5 -5
  71. package/src/containers/Col.vue +2 -0
  72. package/src/containers/Container.vue +86 -157
  73. package/src/containers/Fieldset.vue +5 -0
  74. package/src/containers/FlexLayout.vue +2 -0
  75. package/src/containers/GroupList.vue +41 -11
  76. package/src/containers/GroupListItem.vue +83 -80
  77. package/src/containers/Panel.vue +3 -0
  78. package/src/containers/Row.vue +2 -0
  79. package/src/containers/Step.vue +2 -0
  80. package/src/containers/Tabs.vue +13 -0
  81. package/src/containers/table/ActionsColumn.vue +1 -1
  82. package/src/containers/table/Table.vue +4 -3
  83. package/src/containers/table/usePagination.ts +1 -1
  84. package/src/containers/table/useSortable.ts +1 -1
  85. package/src/containers/table/useTableColumns.ts +9 -19
  86. package/src/containers/table-group-list/TableGroupList.vue +43 -71
  87. package/src/containers/table-group-list/useAdd.ts +40 -25
  88. package/src/containers/table-group-list/useScrollLastItemIntoView.ts +94 -0
  89. package/src/fields/{CheckboxGroup.vue → CheckboxGroup/Index.vue} +3 -8
  90. package/src/fields/CheckboxGroup/effect.ts +27 -0
  91. package/src/fields/{Date.vue → Date/Index.vue} +2 -5
  92. package/src/fields/Date/effect.ts +29 -0
  93. package/src/fields/{DateTime.vue → DateTime/Index.vue} +2 -16
  94. package/src/fields/DateTime/effect.ts +36 -0
  95. package/src/fields/{Display.vue → Display/Index.vue} +3 -7
  96. package/src/fields/Display/effect.ts +28 -0
  97. package/src/fields/{DynamicField.vue → DynamicField/Index.vue} +11 -11
  98. package/src/fields/DynamicField/effect.ts +74 -0
  99. package/src/fields/{NumberRange.vue → NumberRange/Index.vue} +2 -6
  100. package/src/fields/NumberRange/effect.ts +27 -0
  101. package/src/fields/Select.vue +13 -4
  102. package/src/headless.ts +78 -0
  103. package/src/index.ts +32 -15
  104. package/src/plugin.ts +63 -42
  105. package/src/schema.ts +6 -8
  106. package/src/submitForm.ts +81 -409
  107. package/src/theme/group-list.scss +107 -1
  108. package/src/theme/themes/magic-admin/index.scss +2 -8
  109. package/src/utils/builtInFields.ts +69 -0
  110. package/src/utils/collectFields.ts +561 -0
  111. package/src/utils/config.ts +1 -15
  112. package/src/utils/fieldInnerConfig.ts +127 -0
  113. package/src/utils/fieldValueEffects.ts +140 -0
  114. package/src/utils/form.ts +141 -75
  115. package/src/utils/formStateProxy.ts +149 -0
  116. package/src/utils/registerField.ts +308 -0
  117. package/src/utils/submitHeadless.ts +206 -0
  118. package/src/utils/tableGroupList.ts +120 -0
  119. package/src/utils/typeMatch.ts +19 -15
  120. package/src/utils/validateError.ts +92 -0
  121. package/src/utils/validateValues.ts +177 -0
  122. package/types/headless.d.ts +682 -0
  123. package/types/index.d.ts +631 -272
  124. package/dist/es/fields/CheckboxGroup.js +0 -5
  125. package/dist/es/fields/Date.js +0 -5
  126. package/dist/es/fields/DateTime.js +0 -5
  127. package/dist/es/fields/Display.js +0 -5
  128. package/dist/es/fields/DynamicField.js +0 -5
  129. package/dist/es/fields/NumberRange.js +0 -5
  130. package/dist/es/utils/silentLeafFieldTypes.js +0 -49
  131. package/src/utils/silentLeafFieldTypes.ts +0 -71
@@ -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);
2909
+ };
2910
+ /** 获取某个字段 type 的 typeMatch 校验规则(业务侧优先于内置) */
2911
+ var getTypeMatchRule = (type) => {
2912
+ const key = (0, _tmagic_utils.toLine)(type);
2913
+ return extraTypeMatchRules.get(key) ?? builtInTypeMatchRules.get(key);
2747
2914
  };
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 校验规则 */
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,117 @@
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
+ * 存量下发配置沿用 Vue2 时代的写法,把回调第一个参数当组件实例用,靠 `vm.mForm.xxx`
3415
+ * 取表单状态。这类配置除了读,还会往上面挂方法做跨字段通信,例如:
3416
+ *
3417
+ * ```js
3418
+ * vm.mForm.checkPropertyLimit = async (...) => { ... } // 一个字段的 validator 里挂
3419
+ * await vm.mForm.checkPropertyLimit(...) // 另一处再取出来调用
3420
+ * ```
3421
+ *
3422
+ * 所以 `mForm` 必须能指回某个 formState:读能落到 core / context,写能经 `set` trap
3423
+ * 落到 coreState 并持久化。
3424
+ *
3425
+ * 优先级是 core > context > 合成自引用,三者都是刻意的:
3426
+ *
3427
+ * - **core**:`formState.mForm = formState` 这类直写(如 FormPreview 用自建 services 时)
3428
+ * 必须最优先。
3429
+ * - **context**:唯一的生产者是把父 formState 整体当 context 传下来的嵌套表单
3430
+ * (ComponentForm)。此时 `mForm` 命中 context 指向**父表单**,与 `extendState` 时代
3431
+ * 把父 formState 并入子状态的结果一致,跨字段通信仍落在同一份对象上。父 formState
3432
+ * 本身也是 Proxy,写入照样持久化,所以这里让 context 赢是对的,别「修正」成指向子表单。
3433
+ * - **合成自引用**:前两者都没有时才兜底,让 `vm.mForm.xxx = fn` 落到自己的 coreState。
3434
+ *
3435
+ * 由此推出一条约束:**不要往 context 里塞普通对象充当 `mForm`**。context 通常是 computed
3436
+ * 产物,依赖一变就重建,挂上去的方法会静默丢失。要么直写 core,要么什么都不放交给兜底。
3437
+ *
3438
+ * 枚举语义上合成的自引用表现得像原型链上的属性:`'mForm' in state` 为真,但不出现在
3439
+ * `Object.keys(state)` 里,`getOwnPropertyDescriptor` 也返回 undefined。这是故意的——
3440
+ * 按扩展字段打包 formState 的调用方(如发给 AI 的逻辑)会因循环引用炸掉。
3441
+ */
3442
+ var SELF_REF_KEY = "mForm";
3443
+ /**
3444
+ * 将 coreState 与宿主业务上下文关联:读取时优先 core,miss 再读穿到 context。
3445
+ *
3446
+ * - `get` 用属性访问而非 `Reflect.get(t, k, receiver)`,避免破坏 Vue reactive 的 `__v_raw`;
3447
+ * - symbol 键一律只走 core,四个 trap 保持一致。context 是业务数据袋,不承载 symbol 键,
3448
+ * 把 Vue / 工具链的内部 symbol 隔离在 core 上才能让 `toRaw` / `isReactive` 判定正确;
3449
+ * - `ownKeys` + `getOwnPropertyDescriptor` 保证 `Object.entries(formState)` 能枚举到扩展字段
3450
+ * (admin-web-next 的 `pickPanelFormStateExtendFields` 依赖此语义);
3451
+ * - `set` 写入 coreState,第三方 `formState.xxx = v` 仍生效且优先于 context。
3452
+ *
3453
+ * 独立成文件,避免与 `form.ts` ↔ `typeMatch.ts` 形成循环依赖。
3454
+ */
3455
+ var createFormStateProxy = (coreState, getContext) => {
3456
+ const resolve = () => {
3457
+ const ctx = (typeof getContext === "function" ? getContext() : (0, vue.unref)(getContext)) || EMPTY_CONTEXT;
3458
+ return ctx === proxy ? EMPTY_CONTEXT : ctx;
3459
+ };
3460
+ const proxy = new Proxy(coreState, {
3461
+ get(t, k) {
3462
+ if (typeof k === "symbol") return Reflect.get(t, k);
3463
+ const v = t[k];
3464
+ if (v !== void 0 || Reflect.has(t, k)) return v;
3465
+ const ctx = resolve();
3466
+ if (k in ctx) return ctx[k];
3467
+ return k === SELF_REF_KEY ? proxy : void 0;
3468
+ },
3469
+ set(t, k, value) {
3470
+ t[k] = value;
3471
+ return true;
3472
+ },
3473
+ has: (t, k) => Reflect.has(t, k) || typeof k !== "symbol" && (k in resolve() || k === SELF_REF_KEY),
3474
+ ownKeys: (t) => [.../* @__PURE__ */ new Set([...Reflect.ownKeys(t), ...Reflect.ownKeys(resolve()).filter((k) => typeof k !== "symbol")])],
3475
+ getOwnPropertyDescriptor: (t, k) => {
3476
+ const own = Reflect.getOwnPropertyDescriptor(t, k);
3477
+ if (own) return own;
3478
+ if (typeof k === "symbol") return void 0;
3479
+ const ctx = resolve();
3480
+ if (!(k in ctx)) return void 0;
3481
+ return {
3482
+ configurable: true,
3483
+ enumerable: true,
3484
+ writable: true,
3485
+ value: ctx[k]
3486
+ };
3487
+ }
3488
+ });
3489
+ return proxy;
3490
+ };
3491
+ //#endregion
3218
3492
  //#region packages/form/src/utils/form.ts
3219
- var isTDesignAdapter = () => (0, _tmagic_design.getDesignConfig)("adapterType") === "tdesign-vue-next";
3493
+ var isTDesignAdapter = () => (0, _tmagic_design_headless.getDesignConfig)("adapterType") === "tdesign-vue-next";
3220
3494
  /**
3221
3495
  * 将 async-validator(Element Plus)风格的 validator 适配到当前 UI 库。
3222
3496
  * TDesign 调用签名为 `(val) => boolean | CustomValidateObj | Promise`,无 callback。
@@ -3326,6 +3600,79 @@
3326
3600
  if (type === "daterange" && !names) return [];
3327
3601
  return "";
3328
3602
  };
3603
+ /**
3604
+ * formState 中与表单 props 无关的公共部分:字段注册表、消息组件、post 请求。
3605
+ *
3606
+ * 由渲染式表单(`Form.vue`)与无渲染校验(`createHeadlessFormState`)共用,
3607
+ * 保证两者提供给字段配置(`onChange` / `validator` / 异步 options 等)的能力一致。
3608
+ */
3609
+ var fallbackMessage = {
3610
+ error: (msg) => console.error(msg),
3611
+ success: (msg) => console.log(msg),
3612
+ warning: (msg) => console.warn(msg),
3613
+ info: (msg) => console.info(msg),
3614
+ closeAll: () => void 0
3615
+ };
3616
+ var fallbackMessageBox = {
3617
+ alert: (msg) => console.log(msg),
3618
+ confirm: (msg) => console.log(msg),
3619
+ close: (msg) => console.log(msg)
3620
+ };
3621
+ var createFormStateBase = (ui) => {
3622
+ const fields = /* @__PURE__ */ new Map();
3623
+ const requestFuc = getConfig("request");
3624
+ return {
3625
+ fields,
3626
+ setField: (prop, field) => fields.set(prop, field),
3627
+ getField: (prop) => fields.get(prop),
3628
+ deleteField: (prop) => fields.delete(prop),
3629
+ $messageBox: ui?.$messageBox ?? fallbackMessageBox,
3630
+ $message: ui?.$message ?? fallbackMessage,
3631
+ post: (options) => {
3632
+ if (requestFuc) return requestFuc({
3633
+ method: "POST",
3634
+ ...options
3635
+ });
3636
+ }
3637
+ };
3638
+ };
3639
+ /**
3640
+ * 配置项的 `name` 是否可用于从 model 中下钻取值。
3641
+ *
3642
+ * 供 `Container.vue` 与无渲染校验(`collectValidatableFields`)共用,两者对「该不该下钻」
3643
+ * 的判断必须一致。
3644
+ */
3645
+ var isValidName = (name) => {
3646
+ const valueType = typeof name;
3647
+ if (valueType !== "string" && valueType !== "symbol" && valueType !== "number") return false;
3648
+ if (name === "") return false;
3649
+ if (valueType === "number") return name >= 0;
3650
+ return true;
3651
+ };
3652
+ /**
3653
+ * 在 `prop` 后追加一段路径。
3654
+ *
3655
+ * 用于容器把 `prop` 透传给子级时再拼上索引 / key(如 group-list 的行、table 的行)。
3656
+ */
3657
+ var appendProp = (prop = "", key) => `${prop}${prop ? "." : ""}${key}`;
3658
+ /**
3659
+ * 由父级 `prop` 与配置项 `name` 拼出该配置项的完整 `prop`(即 FormItem 的 prop)。
3660
+ *
3661
+ * 调用方传入的 name 已按 `config.name || ''` 归一化,空 name 表示该配置项不占据值路径上的一层,
3662
+ * 此时沿用父级 `prop`。
3663
+ */
3664
+ var getItemProp = (prop = "", name = "") => name === "" ? prop ?? "" : appendProp(prop, name);
3665
+ /**
3666
+ * 归一化配置项的 `type`:求值动态 type、剥离容器语义的 type、驼峰转中划线、补默认值。
3667
+ *
3668
+ * 供 `Container.vue` 与无渲染校验共用,保证两者分派到的 type 完全一致。
3669
+ */
3670
+ var resolveItemType = (mForm, config, props) => {
3671
+ let type = "type" in (config || {}) ? config.type : "";
3672
+ type = type && filterFunction(mForm, type, props);
3673
+ if (type === "form" || type === "container") return "";
3674
+ return type?.replace(/([A-Z])/g, "-$1").toLowerCase() || (config?.items ? "" : "text");
3675
+ };
3329
3676
  var filterFunction = (mForm, config, props) => {
3330
3677
  if (typeof config === "function") return config(mForm, {
3331
3678
  values: (0, vue.readonly)(mForm?.initValues || {}),
@@ -3345,18 +3692,18 @@
3345
3692
  if (config === false) return false;
3346
3693
  return true;
3347
3694
  };
3348
- var getRules = function(mForm, r = [], props, typeMatchValid) {
3695
+ var buildRules = function(mForm, r = [], props, typeMatchValid, adapt = (validator) => validator) {
3349
3696
  let rules = cloneDeep(r);
3350
3697
  if (typeof rules === "object" && !Array.isArray(rules)) rules = [rules];
3351
- if (typeMatchValid?.value && !rules.some((r) => typeof r.typeMatch !== "undefined")) rules.push({ typeMatch: true });
3698
+ if ((0, vue.unref)(typeMatchValid) && !rules.some((r) => typeof r.typeMatch !== "undefined")) rules.push({ typeMatch: true });
3352
3699
  return rules.map((item) => {
3353
3700
  if (item.typeMatch) {
3354
- item.validator = adaptFormValidator(createTypeMatchValidator(mForm, props, item));
3701
+ item.validator = adapt(createTypeMatchValidator(mForm, props, item));
3355
3702
  return item;
3356
3703
  }
3357
3704
  if (typeof item.validator === "function") {
3358
3705
  const fnc = item.validator;
3359
- item.validator = adaptFormValidator((rule, value, callback, source, options) => fnc({
3706
+ item.validator = adapt((rule, value, callback, source, options) => fnc({
3360
3707
  rule,
3361
3708
  value: props.config.names ? props.model : value,
3362
3709
  callback,
@@ -3377,6 +3724,18 @@
3377
3724
  return true;
3378
3725
  });
3379
3726
  };
3727
+ var getRules = function(mForm, r = [], props, typeMatchValid) {
3728
+ return buildRules(mForm, r, props, typeMatchValid, adaptFormValidator);
3729
+ };
3730
+ /**
3731
+ * 与 `getRules` 相同,但 validator 保持 async-validator(Element Plus)原生签名,不做 UI 库适配。
3732
+ *
3733
+ * 供无渲染校验(`validateValues`)使用:它直接把规则交给 async-validator 执行,
3734
+ * 不经过 TDesign 的 `validator(val)` 调用约定,因此不能套 `adaptFormValidator`。
3735
+ */
3736
+ var getNativeRules = function(mForm, r = [], props, typeMatchValid) {
3737
+ return buildRules(mForm, r, props, typeMatchValid);
3738
+ };
3380
3739
  var initValue = async (mForm, { initValues, config }) => {
3381
3740
  if (!Array.isArray(config)) throw new Error("config应该为数组");
3382
3741
  const initValuesCopy = cloneDeep(initValues);
@@ -3393,7 +3752,7 @@
3393
3752
  let time;
3394
3753
  if (["x", "timestamp"].includes(format)) time = (0, dayjs.default)(Number.isNaN(Number(v)) ? v : Number(v)).valueOf();
3395
3754
  else if (typeof v === "string" && v.includes("Z") || v instanceof Date) {
3396
- dayjs.default.extend(dayjs_plugin_utc.default);
3755
+ dayjs.default.extend(dayjs_plugin_utc_js.default);
3397
3756
  time = (0, dayjs.default)(v).utcOffset(8).format(format);
3398
3757
  } else time = (0, dayjs.default)(v).format(format);
3399
3758
  if (time !== "Invalid Date") return time;
@@ -3417,47 +3776,712 @@
3417
3776
  if (order === "ascending") data.sort((a, b) => a[prop] - b[prop]);
3418
3777
  else if (order === "descending") data.sort((a, b) => b[prop] - a[prop]);
3419
3778
  };
3779
+ var createObjectProp = (prop, key, name) => {
3780
+ if (prop === "") return key;
3781
+ const itemPath = `${prop}`.split(".");
3782
+ if (name) {
3783
+ if (`${itemPath[itemPath.length - 1]}` === `${name}`) return `${[...itemPath.slice(0, -1), key].join(".")}`;
3784
+ }
3785
+ return `${[...itemPath, key].join(".")}`;
3786
+ };
3787
+ //#endregion
3788
+ //#region packages/form/src/utils/tableGroupList.ts
3420
3789
  /**
3421
- * 将 extendState 返回的扩展字段合并进 formState。
3790
+ * table / group-list 是同一份配置的两种展示形态,可以互相切换(`TableGroupList.vue`)。
3422
3791
  *
3423
- * - data 描述符(普通字段)通过 `formState[key] = value` 写入,走 reactive proxy 的 set,
3424
- * 触发依赖通知;
3425
- * - accessor 描述符(`{ get stage() { return ... } }`)按原样 defineProperty,调用方
3426
- * 可控制读时求值;强制 `configurable: true` 以便下一次合并可再 define。
3792
+ * 本模块收口两种形态之间的配置派生,以及各形态下「子项配置怎么算出来」的规则,
3793
+ * 供渲染(`TableGroupList.vue` / `GroupListItem.vue` / `useTableColumns.ts`)与无渲染校验
3794
+ * (`collectValidatableFields`)共用,避免两条链路各写一份而产生偏差。
3795
+ */
3796
+ /** group-list 形态的 type(兼容驼峰与中划线两种写法) */
3797
+ var isGroupListType = (type) => type === "groupList" || type === "group-list";
3798
+ /** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
3799
+ var calcLabelWidth = (label) => {
3800
+ if (!label) return "0px";
3801
+ const zhLength = label.match(/[^\x00-\xff]/g)?.length || 0;
3802
+ const chLength = label.length - zhLength;
3803
+ return `${Math.max(chLength * 8 + zhLength * 20, 80)}px`;
3804
+ };
3805
+ /** 由 group-list 形态的配置派生出 table 形态所需的配置;本身已是 table 形态则原样返回 */
3806
+ var toTableConfig = (config) => {
3807
+ if (!isGroupListType(config.type)) return config;
3808
+ const source = config;
3809
+ return {
3810
+ ...config,
3811
+ type: "table",
3812
+ groupItems: source.items,
3813
+ items: source.tableItems || source.items.map((item) => ({
3814
+ ...item,
3815
+ label: item.label || item.text,
3816
+ text: null
3817
+ }))
3818
+ };
3819
+ };
3820
+ /** 由 table 形态的配置派生出 group-list 形态所需的配置;本身已是 group-list 形态则原样返回 */
3821
+ var toGroupListConfig = (config) => {
3822
+ if (isGroupListType(config.type)) return config;
3823
+ const source = config;
3824
+ return {
3825
+ ...config,
3826
+ type: "groupList",
3827
+ tableItems: source.items,
3828
+ items: source.groupItems || source.items.map((item) => {
3829
+ const text = item.text || item.label;
3830
+ return {
3831
+ ...item,
3832
+ text,
3833
+ labelWidth: calcLabelWidth(text),
3834
+ span: item.span || 12
3835
+ };
3836
+ })
3837
+ };
3838
+ };
3839
+ /**
3840
+ * group-list 每一行渲染的 row 容器配置(`GroupListItem.vue` 渲染的 `Container` 的 config)。
3841
+ *
3842
+ * `keyProp` 由调用方从 `mForm.keyProp` 取,默认 `__key`。
3843
+ */
3844
+ var getGroupListRowConfig = (config, index, keyProp) => {
3845
+ const key = keyProp || "__key";
3846
+ return {
3847
+ type: "row",
3848
+ span: config.span || 24,
3849
+ items: config.items,
3850
+ labelWidth: config.labelWidth,
3851
+ labelPosition: config.labelPosition,
3852
+ [key]: `${config[key]}${String(index)}`
3853
+ };
3854
+ };
3855
+ /**
3856
+ * 列是否会被渲染成一列(`hidden` 列不渲染,`display` 为假的列也不渲染)。
3427
3857
  *
3428
- * 注意:extendState 只能向 formState「新增」字段,不允许覆盖其已有 key。
3429
- * 调用方可通过 `reservedKeys` 传入合并前已存在的内置 key 快照(keyProp / popperClass /
3430
- * config / initValues / isCompare / lastValues / parentValues / values / $emit / fields /
3431
- * post 等),命中这些 key 时统一跳过并告警。
3858
+ * `display` 的求值上下文是表格自身的 props,由调用方通过 `evalDisplay` 注入。
3859
+ */
3860
+ var isTableColumnRendered = (column, evalDisplay) => column.type !== "hidden" && Boolean(evalDisplay(column.display));
3861
+ /**
3862
+ * 单元格内 `Container` 实际收到的列配置:按行求值 `itemsFunction`,并去掉已在列层面消费过的 `display`。
3863
+ */
3864
+ var makeTableColumnConfig = (column, row) => {
3865
+ const newConfig = cloneDeep(column);
3866
+ if (typeof column.itemsFunction === "function") newConfig.items = column.itemsFunction(row);
3867
+ delete newConfig.display;
3868
+ return newConfig;
3869
+ };
3870
+ //#endregion
3871
+ //#region packages/form/src/utils/collectFields.ts
3872
+ /** 已登记的 innerConfig 回调自身抛错时抛出(机制故障,不是漏登记) */
3873
+ var FieldInnerConfigError = class extends Error {
3874
+ code = "FIELD_INNER_CONFIG";
3875
+ type;
3876
+ prop;
3877
+ constructor(type, prop, cause) {
3878
+ const reason = cause instanceof Error ? cause.message : String(cause);
3879
+ super(`[MForm] innerConfig for "${type}" at "${prop}" failed: ${reason}`);
3880
+ this.name = "FieldInnerConfigError";
3881
+ this.type = type;
3882
+ this.prop = prop;
3883
+ if (cause instanceof Error) this.cause = cause;
3884
+ }
3885
+ };
3886
+ var isFieldInnerConfigError = (error) => error instanceof FieldInnerConfigError || typeof error === "object" && error !== null && error.code === "FIELD_INNER_CONFIG";
3887
+ /** `effects` 模式下遍历全部配置,不受 display / 折叠状态影响 */
3888
+ var ignoresDisplay = (ctx) => ctx.mode === "effects";
3889
+ var extraContainerWalkers = /* @__PURE__ */ new Map();
3890
+ var builtInContainerWalkers = /* @__PURE__ */ new Map();
3891
+ var registerContainerWalker = (type, walker, builtIn = false) => {
3892
+ if (typeof type !== "string" || !type || typeof walker !== "function") return;
3893
+ const key = (0, _tmagic_utils.toLine)(type);
3894
+ if (builtIn) {
3895
+ builtInContainerWalkers.set(key, walker);
3896
+ return;
3897
+ }
3898
+ extraContainerWalkers.set(key, walker);
3899
+ };
3900
+ var getContainerWalker = (type) => {
3901
+ const key = (0, _tmagic_utils.toLine)(type);
3902
+ return extraContainerWalkers.get(key) ?? builtInContainerWalkers.get(key);
3903
+ };
3904
+ var deleteContainerWalker = (type) => extraContainerWalkers.delete((0, _tmagic_utils.toLine)(type));
3905
+ var clearContainerWalkers = () => extraContainerWalkers.clear();
3906
+ var getItems = (config) => config?.items;
3907
+ /**
3908
+ * `resolveItemType` 的静态版本:不求值函数型 `type`,由调用方按「未知」处理。
3432
3909
  *
3433
- * 兜底:未传 `reservedKeys` 时,仍会拦截 props 派生的只读 getter 字段(无 setter),
3434
- * 否则以普通字段形式赋值会让 proxy 的 set trap 抛出
3435
- * `TypeError: 'set' on proxy: trap returned falsish`。
3910
+ * 两者必须保持一致,否则预扫描判定的 type 与实际遍历的不同,会漏执行 effect。
3436
3911
  */
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;
3912
+ var staticItemType = (config) => {
3913
+ const type = "type" in config ? config.type : "";
3914
+ if (type === "form" || type === "container") return "";
3915
+ return `${type || ""}`.replace(/([A-Z])/g, "-$1").toLowerCase() || (config.items ? "" : "text");
3916
+ };
3917
+ /**
3918
+ * 静态预判一份配置子树是否可能触发值初始化写入。
3919
+ *
3920
+ * 只有「确定不会」时才返回 false。函数型 `type`、`itemsFunction`、业务登记的容器遍历器、
3921
+ * 登记了 innerConfig 的复合字段,其运行期结构静态看不出来,一律按可能触发处理。
3922
+ *
3923
+ * 只用在同一份 `items` 会被逐行 / 逐标签页重复展开的地方(table、group-list、dynamic tab):
3924
+ * 预判成本是 O(子项数),跳过省下的是 O(行数 × 子项数),无 effect 的多行表格能省一个量级。
3925
+ * 其余位置不做预判——扫描与遍历的单节点成本相当,扫完再遍历只会更慢。
3926
+ *
3927
+ * 只看 `items`:table / group-list 只遍历当前形态的子项,另一形态的
3928
+ * `tableItems` / `groupItems` 不参与遍历,判定范围与遍历保持一致。
3929
+ */
3930
+ var mayRunEffects = (config) => {
3931
+ if (!config) return false;
3932
+ if (typeof config.type === "function" || typeof config.itemsFunction === "function") return true;
3933
+ const type = staticItemType(config);
3934
+ if (type === "hidden") return false;
3935
+ if (type) {
3936
+ const key = (0, _tmagic_utils.toLine)(type);
3937
+ if (extraContainerWalkers.has(key)) return true;
3938
+ if (!builtInContainerWalkers.has(key)) {
3939
+ if (getFieldInnerConfig(type) || getFieldMountValueEffect(type)) return true;
3940
+ if (isLeafFieldType(type)) return false;
3445
3941
  }
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;
3942
+ }
3943
+ return itemsMayRunEffects(config.items);
3944
+ };
3945
+ var itemsMayRunEffects = (items) => Array.isArray(items) && items.some((item) => mayRunEffects(item));
3946
+ /**
3947
+ * 复刻 `Container.vue` 的 `display`。
3948
+ *
3949
+ * 与渲染的唯一差异:`display: 'expand'`(「展开更多配置」按钮背后的字段)在此视为可见。
3950
+ * 「是否已展开」纯粹是交互状态,不改变字段是否属于这份配置,无渲染校验按配置声明的范围校验。
3951
+ */
3952
+ var resolveDisplay = (ctx, config, nodeProps) => {
3953
+ if (ignoresDisplay(ctx)) return true;
3954
+ const value = display(ctx.mForm, config?.display, nodeProps);
3955
+ if (value === "expand") return true;
3956
+ return Boolean(value);
3957
+ };
3958
+ var addField = (ctx, node, itemProp, nodeProps) => {
3959
+ if (ctx.mode !== "collect") return;
3960
+ const rules = getNativeRules(ctx.mForm, node.config.rules, nodeProps, ctx.typeMatchValid);
3961
+ if (!rules.length) return;
3962
+ ctx.fields.push({
3963
+ prop: itemProp,
3964
+ rules,
3965
+ config: node.config,
3966
+ model: node.model
3967
+ });
3968
+ };
3969
+ var walkChildren = (ctx, items, model, prop) => {
3970
+ if (!Array.isArray(items)) return;
3971
+ for (const item of items) {
3972
+ if (!item) continue;
3973
+ walkNode(ctx, {
3974
+ config: item,
3975
+ model,
3976
+ prop
3977
+ });
3978
+ }
3979
+ };
3980
+ var getContainerScope = (node) => {
3981
+ const { config, model } = node;
3982
+ const name = config.name || "";
3983
+ return {
3984
+ config,
3985
+ model,
3986
+ name,
3987
+ items: getItems(config),
3988
+ childModel: name ? model?.[name] : model
3989
+ };
3990
+ };
3991
+ /** containers/Tabs.vue */
3992
+ var expandTab = (ctx, node, itemProp) => {
3993
+ const { config, model, name, items, childModel } = getContainerScope(node);
3994
+ const tabsProps = {
3995
+ model,
3996
+ config,
3997
+ prop: itemProp
3998
+ };
3999
+ if (config.dynamic) {
4000
+ if (!name) return;
4001
+ const tabs = model?.[name] || [];
4002
+ if (!tabs.length) return;
4003
+ if (ctx.mode === "effects" && !itemsMayRunEffects(items)) return;
4004
+ tabs.forEach((_tab, tabIndex) => {
4005
+ walkChildren(ctx, items, childModel?.[tabIndex], appendProp(itemProp, tabIndex));
4006
+ });
4007
+ return;
4008
+ }
4009
+ const tabs = ignoresDisplay(ctx) ? items || [] : (items || []).filter((item) => display(ctx.mForm, item?.display, tabsProps));
4010
+ for (const tab of tabs) {
4011
+ const tabName = tab.name;
4012
+ walkChildren(ctx, getItems(tab), tabName ? childModel?.[tabName] : childModel, tabName ? appendProp(itemProp, tabName) : itemProp);
4013
+ }
4014
+ };
4015
+ /** containers/Row.vue → Col.vue:col 用 v-show 控制显隐,始终渲染 */
4016
+ var expandRow = (ctx, node, itemProp) => {
4017
+ const { items, childModel } = getContainerScope(node);
4018
+ walkChildren(ctx, items, childModel, itemProp);
4019
+ };
4020
+ /** containers/Fieldset.vue */
4021
+ var expandFieldset = (ctx, node, itemProp) => {
4022
+ const { config, items, childModel } = getContainerScope(node);
4023
+ if (!childModel) return;
4024
+ const { checkbox } = config;
4025
+ const checkboxName = typeof checkbox === "object" && typeof checkbox.name === "string" ? checkbox.name : "value";
4026
+ const checkboxTrueValue = typeof checkbox === "object" && typeof checkbox.trueValue !== "undefined" ? checkbox.trueValue : 1;
4027
+ if (!ignoresDisplay(ctx) && config.expand && childModel?.[checkboxName] !== checkboxTrueValue) return;
4028
+ walkChildren(ctx, items, childModel, itemProp);
4029
+ };
4030
+ /** containers/Panel.vue:折叠仅 display:none,子项照常渲染 */
4031
+ var expandPanel = (ctx, node, itemProp) => {
4032
+ const { items, childModel } = getContainerScope(node);
4033
+ if (!items?.length) return;
4034
+ walkChildren(ctx, items, childModel, itemProp);
4035
+ };
4036
+ /** containers/Step.vue:非当前步仅 v-show 隐藏,子项照常渲染;prop 基准被重置为 step.name */
4037
+ var expandStep = (ctx, node, _itemProp) => {
4038
+ const { model, items } = getContainerScope(node);
4039
+ for (const step of items || []) {
4040
+ const stepName = step?.name;
4041
+ walkChildren(ctx, getItems(step), stepName ? model?.[stepName] : model, `${stepName}`);
4042
+ }
4043
+ };
4044
+ /**
4045
+ * 遍历 table / group-list。
4046
+ *
4047
+ * `TableGroupList.vue` 按原始 `config.type` 决定初始展示形态(table 或 groupList),
4048
+ * 两种形态的子项结构不同,这里分别复刻(形态间的配置派生复用 `utils/tableGroupList`,
4049
+ * 与渲染式实现同源):
4050
+ * - table:逐列(跳过 hidden 列与 display 为假的列)× 逐行渲染 Container,行 prop 为 `${prop}.${index}`;
4051
+ * - groupList:逐行渲染一个 row 容器,包含 `items` 全部字段。
4052
+ *
4053
+ * 与渲染的差异:不做分页截断(table)、不跳过折叠行(groupList)。二者都是列表的视图状态,
4054
+ * 与「这些行的值是否合法」无关,无渲染校验覆盖全部行。
4055
+ */
4056
+ var expandTableGroupList = (ctx, node, itemProp) => {
4057
+ const { config, model } = node;
4058
+ const name = config.name || "";
4059
+ const rows = model?.[name];
4060
+ if (!Array.isArray(rows) || !rows.length) return;
4061
+ if (isGroupListType(config.type)) {
4062
+ const groupListConfig = toGroupListConfig(config);
4063
+ if (ctx.mode === "effects" && !itemsMayRunEffects(groupListConfig.items)) return;
4064
+ rows.forEach((row, index) => {
4065
+ walkNode(ctx, {
4066
+ config: getGroupListRowConfig(groupListConfig, index, ctx.mForm?.keyProp),
4067
+ model: row,
4068
+ prop: appendProp(itemProp, index)
4069
+ });
4070
+ });
4071
+ return;
4072
+ }
4073
+ const tableItems = toTableConfig(config).items;
4074
+ if (!Array.isArray(tableItems)) return;
4075
+ if (ctx.mode === "effects" && !itemsMayRunEffects(tableItems)) return;
4076
+ const tableProps = {
4077
+ model,
4078
+ config,
4079
+ prop: itemProp
4080
+ };
4081
+ const evalDisplay = (display$4) => display(ctx.mForm, display$4, tableProps);
4082
+ const isRendered = (column) => ignoresDisplay(ctx) || isTableColumnRendered(column, evalDisplay);
4083
+ rows.forEach((row, index) => {
4084
+ for (const column of tableItems) {
4085
+ if (!column || !isRendered(column)) continue;
4086
+ walkNode(ctx, {
4087
+ config: makeTableColumnConfig(column, row),
4088
+ model: row,
4089
+ prop: appendProp(itemProp, index)
4090
+ });
3450
4091
  }
3451
- formState[key] = state[key];
4092
+ });
4093
+ };
4094
+ /**
4095
+ * 执行某个叶子字段登记的值初始化写入。
4096
+ *
4097
+ * 跑在表单初始化路径上,单个字段的 effect 抛错不应该让整张表单渲染不出来,因此只记录并继续。
4098
+ */
4099
+ var runMountValueEffect = (ctx, type, node, itemProp) => {
4100
+ const effect = getFieldMountValueEffect(type);
4101
+ if (!effect) return;
4102
+ try {
4103
+ effect({
4104
+ config: node.config,
4105
+ model: node.model,
4106
+ prop: itemProp,
4107
+ mForm: ctx.mForm,
4108
+ values: ctx.values
4109
+ });
4110
+ } catch (e) {
4111
+ console.error(`[MForm] mount value effect for "${type}" at "${itemProp}" failed:`, e);
3452
4112
  }
3453
4113
  };
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(".")}`;
4114
+ /**
4115
+ * 遍历已登记 innerConfig 的复合字段。
4116
+ *
4117
+ * `collect` 模式下回调抛错视为机制故障,包装成 `FieldInnerConfigError` 抛出,由校验流程暴露。
4118
+ * `effects` 模式跑在表单初始化路径上,抛出会让整张表单渲染不出来,因此只记录并跳过该子树。
4119
+ */
4120
+ var walkInnerConfig = (ctx, type, node, itemProp) => {
4121
+ const resolve = getFieldInnerConfig(type);
4122
+ if (!resolve) return false;
4123
+ let result;
4124
+ try {
4125
+ result = resolve({
4126
+ config: node.config,
4127
+ model: node.model,
4128
+ prop: itemProp,
4129
+ parentProp: node.prop,
4130
+ mForm: ctx.mForm
4131
+ });
4132
+ } catch (e) {
4133
+ if (ctx.mode === "collect") throw new FieldInnerConfigError(type, itemProp, e);
4134
+ console.error(new FieldInnerConfigError(type, itemProp, e));
4135
+ return true;
4136
+ }
4137
+ if (!result) return true;
4138
+ const innerModel = result.model ?? node.model;
4139
+ const innerProp = result.prop ?? itemProp;
4140
+ walkChildren(ctx, Array.isArray(result.config) ? result.config : [result.config], innerModel, innerProp);
4141
+ return true;
4142
+ };
4143
+ /**
4144
+ * 遍历一个 Container 节点,分支顺序与 `Container.vue` 的模板保持一致。
4145
+ *
4146
+ * 对比模式(`isCompare`)在此被忽略:它只改变同一个 prop 渲染几份 FormItem,
4147
+ * 规则与取值完全相同,对校验结果没有影响。
4148
+ */
4149
+ var walkNode = (ctx, node) => {
4150
+ const { config, prop } = node;
4151
+ const model = node.model ?? {};
4152
+ const nodeProps = {
4153
+ model,
4154
+ config,
4155
+ prop
4156
+ };
4157
+ const name = config.name || "";
4158
+ const items = getItems(config);
4159
+ const type = resolveItemType(ctx.mForm, config, nodeProps);
4160
+ const itemProp = getItemProp(prop, name);
4161
+ const text = filterFunction(ctx.mForm, config.text, nodeProps);
4162
+ if (type === "hidden") {
4163
+ addField(ctx, {
4164
+ ...node,
4165
+ model
4166
+ }, itemProp, nodeProps);
4167
+ return;
4168
+ }
4169
+ const display = resolveDisplay(ctx, config, nodeProps);
4170
+ if (items && !text && type && display) {
4171
+ dispatchByType(ctx, type, {
4172
+ ...node,
4173
+ model
4174
+ }, itemProp);
4175
+ return;
4176
+ }
4177
+ if (type && display) {
4178
+ addField(ctx, {
4179
+ ...node,
4180
+ model
4181
+ }, itemProp, nodeProps);
4182
+ dispatchByType(ctx, type, {
4183
+ ...node,
4184
+ model
4185
+ }, itemProp);
4186
+ return;
4187
+ }
4188
+ if (items && display) {
4189
+ const childModel = isValidName(name) ? model[name] : model;
4190
+ if (!childModel) return;
4191
+ walkChildren(ctx, items, childModel, itemProp);
4192
+ }
4193
+ };
4194
+ /**
4195
+ * 按 type 分派:已登记 walk 的容器 → 按容器模板遍历;
4196
+ * `effects` 模式下先执行本字段的 effect(可与 innerConfig 并存,如 `code-select` 先归一化再下钻);
4197
+ * 登记了 innerConfig 的复合字段 → 遍历其内部配置;叶子字段无子树。
4198
+ *
4199
+ * 未登记的 type:配置里若有 `items` 则按普通容器下钻;否则视为没有嵌套表单项。
4200
+ * 自身 rules 已在 FormItem 分支收集,有 rules 就会校验。
4201
+ */
4202
+ var dispatchByType = (ctx, type, node, itemProp) => {
4203
+ const walkContainer = getContainerWalker(type);
4204
+ if (walkContainer) {
4205
+ walkContainer(ctx, node, itemProp);
4206
+ return;
4207
+ }
4208
+ if (ctx.mode === "effects") runMountValueEffect(ctx, type, node, itemProp);
4209
+ if (walkInnerConfig(ctx, type, node, itemProp)) return;
4210
+ if (isLeafFieldType(type)) return;
4211
+ const items = getItems(node.config);
4212
+ if (items?.length) {
4213
+ const { childModel } = getContainerScope(node);
4214
+ walkChildren(ctx, items, childModel, itemProp);
4215
+ }
4216
+ };
4217
+ /**
4218
+ * 纯逻辑收集「一份 config + 一份 values」中所有参与校验的字段。
4219
+ *
4220
+ * 遍历规则与 `Container.vue` 及各容器组件的模板一一对应,因此产出的 prop / rules
4221
+ * 与渲染式校验(挂载 MForm 后调用 `validate()`)等价,无需任何 DOM 或组件实例。
4222
+ *
4223
+ * 本函数只读不写。字段的值初始化副作用由 `applyMountValueEffects` 负责,
4224
+ * 调用方需在初始化表单值之后、收集字段之前执行一次(`validateValues` 已经这么做)。
4225
+ *
4226
+ * @param mForm - 表单状态;无渲染时由 `createHeadlessFormState` 构造
4227
+ * @param config - 表单配置
4228
+ * @param values - 表单值
4229
+ * @param [typeMatchValid] - 是否自动注入 typeMatch 规则;为 true 时,未显式声明
4230
+ * `typeMatch` 的字段会补上 `{ typeMatch: true }`,按字段 type 校验值形态
4231
+ * @returns 带规则的字段列表
4232
+ */
4233
+ var collectValidatableFields = (mForm, config, values, typeMatchValid) => {
4234
+ const ctx = {
4235
+ mForm,
4236
+ typeMatchValid,
4237
+ fields: [],
4238
+ values,
4239
+ mode: "collect"
4240
+ };
4241
+ if (Array.isArray(config)) walkChildren(ctx, config, values, "");
4242
+ return ctx.fields;
4243
+ };
4244
+ /**
4245
+ * 执行「一份 config + 一份 values」中所有叶子字段登记的值初始化写入,就地改写 `values`。
4246
+ *
4247
+ * 渲染与无渲染两条链路共用的唯一执行点:字段组件自身不再在 setup 里改写 model,
4248
+ * 由持有完整表单值的一方(`Form.vue` / `validateValues`)在表单值初始化完成、
4249
+ * 且已挂到 `mForm.values` 之后调用一次。这样 effect 与动态 `type` 回调读到的
4250
+ * `formValue` 就是最新值,而不是上一轮的旧值。
4251
+ *
4252
+ * 与 `collectValidatableFields` 的差异:不看 `display`(`display: false` 的字段也会规整)。
4253
+ * `type: 'hidden'` 会在该节点停止遍历,内部字段不执行 effect。
4254
+ *
4255
+ * `prop` 与 `values` 都以传入的 `values` 为根,因此也可以用于 tab / table 新增行这类
4256
+ * 「先构造一份子树的值,再挂到表单上」的场景:传入子树配置与该行的值即可。
4257
+ *
4258
+ * @param mForm - 表单状态;无渲染时由 `createHeadlessFormState` 构造
4259
+ * @param config - 表单配置
4260
+ * @param values - 表单值(会被就地修改)
4261
+ */
4262
+ var applyMountValueEffects = (mForm, config, values) => {
4263
+ if (!Array.isArray(config)) return;
4264
+ walkChildren({
4265
+ mForm,
4266
+ fields: [],
4267
+ values,
4268
+ mode: "effects"
4269
+ }, config, values, "");
4270
+ };
4271
+ //#endregion
4272
+ //#region packages/form/src/utils/validateError.ts
4273
+ /**
4274
+ * 通过 name 从 config 中查找对应的 text
4275
+ *
4276
+ * @param name - 字段名,支持点分隔的路径格式,如 'a.b.c'
4277
+ * @param config - 表单配置数组
4278
+ * @returns 找到的 text 值,如果未找到则返回 undefined
4279
+ */
4280
+ var getTextByName = (name, config = []) => {
4281
+ if (!name || !Array.isArray(config)) return void 0;
4282
+ const nameParts = name.split(".");
4283
+ const findInConfig = (configs, parts) => {
4284
+ if (parts.length === 0) return void 0;
4285
+ const [currentPart, ...remainingParts] = parts;
4286
+ for (const item of configs) {
4287
+ if (item.name === currentPart) {
4288
+ if (remainingParts.length === 0) return typeof item.text === "string" ? item.text : void 0;
4289
+ if ("items" in item && Array.isArray(item.items)) {
4290
+ const result = findInConfig(item.items, remainingParts);
4291
+ if (result !== void 0) return result;
4292
+ }
4293
+ }
4294
+ if ("items" in item && Array.isArray(item.items)) {
4295
+ const result = findInConfig(item.items, parts);
4296
+ if (result !== void 0) return result;
4297
+ }
4298
+ }
4299
+ };
4300
+ return findInConfig(config, nameParts);
4301
+ };
4302
+ /**
4303
+ * 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
4304
+ *
4305
+ * `useFieldTextInError` 为 `true`(默认)时用字段的 text 文案作为错误前缀,找不到则回退为字段 name。
4306
+ *
4307
+ * 由渲染式校验(Form.vue 的 `submitForm` / `validate`)与无渲染校验(`validateValues`)共用,
4308
+ * 保证两条校验链路产出的错误文案格式完全一致。
4309
+ *
4310
+ * @param invalidFields - async-validator 产出的字段错误表
4311
+ * @param [options] - 文案选项
4312
+ * @param [options.config] - 用于查找 text 的表单配置
4313
+ * @param [options.useFieldTextInError=true] - 错误前缀是否使用字段 text
4314
+ * @returns 拼接后的错误文案;无错误时为空串
4315
+ */
4316
+ var formatValidateError = (invalidFields, { config = [], useFieldTextInError = true } = {}) => {
4317
+ const error = [];
4318
+ Object.entries(invalidFields).forEach(([prop, validateError]) => {
4319
+ validateError.forEach(({ field, message }) => {
4320
+ const name = field || prop;
4321
+ const text = (useFieldTextInError ? getTextByName(name, config) : void 0) || name;
4322
+ error.push(`${text} -> ${message}`);
4323
+ });
4324
+ });
4325
+ return error.join("<br>");
4326
+ };
4327
+ //#endregion
4328
+ //#region packages/form/src/utils/validateValues.ts
4329
+ /**
4330
+ * 构造一个不依赖组件实例的 `formState`。
4331
+ *
4332
+ * 字段结构与 `Form.vue` 中 provide 给子树的 `mForm` 保持一致,使配置里的
4333
+ * `display` / `rules.validator` / `filter` 等回调拿到的上下文与渲染式校验相同。
4334
+ *
4335
+ * 差异:没有真实的表单实例,因此 `$emit` 为空实现、`fields` 注册表始终为空
4336
+ * (该注册表在整个仓库中只写不读,不参与校验)。
4337
+ */
4338
+ var createHeadlessFormState = (options) => {
4339
+ const context = options.context ?? {};
4340
+ const state = {
4341
+ keyProp: options.keyProp ?? "__key",
4342
+ popperClass: options.popperClass ?? "",
4343
+ config: options.config,
4344
+ initValues: options.initValues ?? {},
4345
+ isCompare: false,
4346
+ lastValues: {},
4347
+ parentValues: options.parentValues ?? {},
4348
+ values: {},
4349
+ lastValuesProcessed: {},
4350
+ $emit: () => void 0,
4351
+ ...createFormStateBase()
4352
+ };
4353
+ return createFormStateProxy((0, vue.reactive)(state), () => context);
4354
+ };
4355
+ /**
4356
+ * 按 prop 路径从表单值中取字段值,路径中断时返回 `undefined`。
4357
+ *
4358
+ * 不复用 `@tmagic/utils` 的 `getValueByKeyPath`:后者在路径中断时抛错,
4359
+ * 而 Element Plus 的 FormItem 取值是宽松的(拿不到就是 `undefined`),此处与之对齐。
4360
+ */
4361
+ var getFieldValue = (prop, values) => {
4362
+ if (!prop) return void 0;
4363
+ return prop.split(".").reduce((acc, key) => {
4364
+ if (acc === null || typeof acc !== "object") return void 0;
4365
+ return acc[key];
4366
+ }, values);
4367
+ };
4368
+ /**
4369
+ * 校验单个字段,返回 async-validator 的 `fields` 错误映射(通过则返回 `null`)。
4370
+ *
4371
+ * 与 Element Plus 的 FormItem 逐字段校验保持一致:
4372
+ * 每个字段独立构造一个 `Schema({ [prop]: rules })`,以字段自身的值为 source,
4373
+ * 并开启 `firstFields` 只取每个字段的首条错误;`trigger` 是 FormItem 用来筛选规则的
4374
+ * 元信息,交给 async-validator 前必须剔除,否则会改变规则的匹配方式。
4375
+ */
4376
+ var validateField = async (field, values) => {
4377
+ const rules = field.rules.map(({ trigger, ...rule }) => rule);
4378
+ if (!rules.length) return null;
4379
+ const value = getFieldValue(field.prop, values);
4380
+ try {
4381
+ await new async_validator.default({ [field.prop]: rules }).validate({ [field.prop]: value }, { firstFields: true });
4382
+ return null;
4383
+ } catch (err) {
4384
+ if (err?.fields) return err.fields;
4385
+ return { [field.prop]: [{
4386
+ field: field.prop,
4387
+ message: err?.message ?? `${err}`
4388
+ }] };
3459
4389
  }
3460
- return `${[...itemPath, key].join(".")}`;
4390
+ };
4391
+ /**
4392
+ * 不挂载任何组件,纯逻辑地对「一份 config + 一份值」执行一次完整校验。
4393
+ *
4394
+ * 流程与渲染式校验一一对应,但不需要 DOM,也不会实例化任何字段组件:
4395
+ *
4396
+ * 1. 构造 headless `formState` 并挂上 `context` 读穿;
4397
+ * 2. `initValue` 初始化表单值(默认值、嵌套结构、`onInitValue` 等);
4398
+ * 3. `applyMountValueEffects` 执行字段登记的值初始化写入(与渲染式共用同一份登记表);
4399
+ * 4. 遍历 config 树收集所有带规则的字段(等价于渲染出的 FormItem 集合);
4400
+ * 5. 逐字段交给 async-validator 执行,汇总错误文案。
4401
+ *
4402
+ * @example
4403
+ * ```ts
4404
+ * const { error, values } = await validateValues({
4405
+ * config: [...],
4406
+ * initValues: { name: '' },
4407
+ * typeMatchValid: true,
4408
+ * });
4409
+ * ```
4410
+ */
4411
+ var validateValues = async (options) => {
4412
+ const { config, initValues = {}, typeMatchValid, useFieldTextInError = true } = options;
4413
+ const formState = createHeadlessFormState(options);
4414
+ const values = await initValue(formState, {
4415
+ initValues,
4416
+ config
4417
+ });
4418
+ formState.values = values;
4419
+ applyMountValueEffects(formState, config, values);
4420
+ const fields = collectValidatableFields(formState, config, values, Boolean(typeMatchValid));
4421
+ const invalidFields = {};
4422
+ for (const field of fields) {
4423
+ const fieldErrors = await validateField(field, values);
4424
+ if (fieldErrors) Object.assign(invalidFields, fieldErrors);
4425
+ }
4426
+ return {
4427
+ values,
4428
+ invalidFields,
4429
+ error: formatValidateError(invalidFields, {
4430
+ config,
4431
+ useFieldTextInError
4432
+ })
4433
+ };
4434
+ };
4435
+ //#endregion
4436
+ //#region packages/form/src/utils/submitHeadless.ts
4437
+ var throwIfAborted = (signal, fnName) => {
4438
+ if (signal?.aborted) throw signal.reason ?? /* @__PURE__ */ new Error(`${fnName} aborted`);
4439
+ };
4440
+ var throwIfDialog = (dialog, fnName) => {
4441
+ if (dialog) throw new Error(`[MForm] ${fnName}({ dialog: true }) is not available from @tmagic/form/headless. Import from @tmagic/form instead.`);
4442
+ };
4443
+ /**
4444
+ * `submitForm` 与 `validateForm` 共用的无渲染校验流程:中断检查 → `validateValues`。
4445
+ */
4446
+ var validateWithoutRender = async (fnName, options) => {
4447
+ const { signal, config, initValues, parentValues, keyProp, typeMatchValid, useFieldTextInError, context } = options;
4448
+ throwIfAborted(signal, fnName);
4449
+ const result = await validateValues({
4450
+ config,
4451
+ initValues,
4452
+ parentValues,
4453
+ keyProp,
4454
+ popperClass: options.popperClass,
4455
+ typeMatchValid,
4456
+ useFieldTextInError,
4457
+ context
4458
+ });
4459
+ throwIfAborted(signal, fnName);
4460
+ return result;
4461
+ };
4462
+ /**
4463
+ * 以命令式方式对一份「表单配置 + 值」做一次校验并取回表单值(无渲染)。
4464
+ *
4465
+ * `@tmagic/form/headless` 不支持 `dialog: true`。
4466
+ */
4467
+ var submitForm$1 = async (options) => {
4468
+ throwIfDialog(options.dialog, "submitForm");
4469
+ const validated = await validateWithoutRender("submitForm", options);
4470
+ if (validated.error) throw new Error(validated.error);
4471
+ const values = options.native ? validated.values : cloneDeep(validated.values);
4472
+ return options.returnChangeRecords ? {
4473
+ values,
4474
+ changeRecords: []
4475
+ } : values;
4476
+ };
4477
+ /**
4478
+ * 以命令式方式对一份「表单配置 + 值」做一次静默校验(无渲染)。
4479
+ *
4480
+ * `@tmagic/form/headless` 不支持 `dialog: true`。
4481
+ */
4482
+ var validateForm$1 = async (options) => {
4483
+ throwIfDialog(options.dialog, "validateForm");
4484
+ return (await validateWithoutRender("validateForm", options)).error;
3461
4485
  };
3462
4486
  //#endregion
3463
4487
  //#region packages/form/src/utils/useAddField.ts
@@ -3492,67 +4516,156 @@
3492
4516
  }
3493
4517
  });
3494
4518
  //#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;
3501
- };
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);
4519
+ //#region packages/form/src/utils/registerField.ts
4520
+ var extraComponents = /* @__PURE__ */ new Map();
4521
+ var builtInComponents = /* @__PURE__ */ new Map();
4522
+ var applyVueComponent = (type, component, app, builtIn, kind) => {
4523
+ const key = (0, _tmagic_utils.toLine)(type);
4524
+ if (builtIn) builtInComponents.set(key, component);
4525
+ else extraComponents.set(key, component);
4526
+ app?.component(`m-${kind}-${key}`, component);
3507
4527
  };
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
4528
  /**
3513
- * 可在静默校验模式下跳过渲染的内置叶子字段(默认集合)。
4529
+ * 把多份字段登记表按 type 浅合并。后一份只覆盖自己带了的 key,未出现的 key 保留前一份。
3514
4530
  *
3515
- * 这些字段只渲染一个输入控件,内部没有嵌套 FormItem,且不会依赖组件挂载修改 model;
3516
- * 字段值由初始化逻辑处理(包括 defaultValue),因此跳过不影响校验结果。
4531
+ * 安装插件时用:Node 侧只登记 `headless`,浏览器再补 `component` / `container`。
3517
4532
  *
3518
- * 业务侧可通过 `registerSilentLeafFieldTypes` 追加已审计的安全自定义字段。
4533
+ * @param tables - 待合并的登记表,`undefined` 会被跳过
4534
+ * @returns 合并后的登记表
3519
4535
  */
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();
4536
+ var mergeFieldOptions = (...tables) => {
4537
+ const result = {};
4538
+ for (const table of tables) {
4539
+ if (!table) continue;
4540
+ for (const [type, options] of Object.entries(table)) result[type] = {
4541
+ ...result[type],
4542
+ ...pickDefinedFieldOptions(options)
4543
+ };
4544
+ }
4545
+ return result;
4546
+ };
4547
+ var FIELD_OPTION_KEYS = [
4548
+ "component",
4549
+ "container",
4550
+ "effect",
4551
+ "walk",
4552
+ "innerConfig",
4553
+ "typeMatch"
4554
+ ];
4555
+ var pickDefinedFieldOptions = (options) => {
4556
+ if (!options) return {};
4557
+ const next = {};
4558
+ for (const key of FIELD_OPTION_KEYS) if (options[key] !== void 0) next[key] = options[key];
4559
+ return next;
4560
+ };
4561
+ var extraFieldOptions = /* @__PURE__ */ new Map();
4562
+ var builtInFieldOptions = /* @__PURE__ */ new Map();
4563
+ var removeFormComponent = (type) => {
4564
+ extraComponents.delete((0, _tmagic_utils.toLine)(type));
4565
+ };
4566
+ var clearFormComponents = () => {
4567
+ extraComponents.clear();
4568
+ };
3537
4569
  /**
3538
- * 注册可在静默校验中跳过渲染的叶子字段 type(追加到默认 `LEAF_FIELD_TYPES` 之上)。
4570
+ * 按字段 type 取已登记的渲染组件(`codeSelect` 与 `code-select` 等价)。
3539
4571
  *
3540
- * 仅应纳入「无嵌套 FormItem、无挂载值副作用」的字段;复合 / 动态子表单不可加入。
4572
+ * @param type - 字段 type
4573
+ * @returns 已登记的 Vue 组件;未登记则为 `undefined`
3541
4574
  */
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));
4575
+ var getField = (type) => {
4576
+ const key = (0, _tmagic_utils.toLine)(type);
4577
+ return extraComponents.get(key) ?? builtInComponents.get(key);
4578
+ };
4579
+ var registerFieldImpl = (type, options, app, builtIn) => {
4580
+ if (typeof type !== "string" || !type) return;
4581
+ const key = (0, _tmagic_utils.toLine)(type);
4582
+ const store = builtIn ? builtInFieldOptions : extraFieldOptions;
4583
+ const incoming = pickDefinedFieldOptions(options);
4584
+ const merged = {
4585
+ ...store.get(key),
4586
+ ...incoming
4587
+ };
4588
+ store.set(key, merged);
4589
+ 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.`);
4590
+ 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.`);
4591
+ if (merged.component) applyVueComponent(type, merged.component, app, builtIn, "fields");
4592
+ if (merged.container) applyVueComponent(type, merged.container, app, builtIn, "form");
4593
+ if (merged.typeMatch) registerTypeMatchRule(type, merged.typeMatch, builtIn);
4594
+ if (merged.walk) {
4595
+ registerContainerWalker(type, merged.walk, builtIn);
4596
+ if (!builtIn) {
4597
+ deleteLeafFieldType(type);
4598
+ deleteFieldInnerConfig(type);
4599
+ }
4600
+ return;
3546
4601
  }
4602
+ if (!builtIn) deleteContainerWalker(type);
4603
+ if (merged.innerConfig) {
4604
+ registerFieldInnerConfig(type, merged.innerConfig, builtIn);
4605
+ if (typeof merged.effect === "function") registerLeafFieldType(type, merged.effect, builtIn);
4606
+ else if (!builtIn) deleteLeafFieldType(type);
4607
+ return;
4608
+ }
4609
+ if (merged.container && !merged.component && typeof merged.effect !== "function") {
4610
+ if (!builtIn) deleteLeafFieldType(type);
4611
+ return;
4612
+ }
4613
+ if (!builtIn) deleteFieldInnerConfig(type);
4614
+ registerLeafFieldType(type, merged.effect, builtIn);
3547
4615
  };
3548
- /** 当前生效的静默叶子字段集合:内置默认 ∪ 已注册扩展 */
3549
- var getSilentLeafFieldTypes = () => {
3550
- if (extraSilentLeafFieldTypes.size === 0) return LEAF_FIELD_TYPES;
3551
- return /* @__PURE__ */ new Set([...LEAF_FIELD_TYPES, ...extraSilentLeafFieldTypes]);
4616
+ /**
4617
+ * 登记一个字段 type 在无渲染校验里的行为,以及可选的渲染组件。
4618
+ *
4619
+ * `type` 会按 Container 的规则归一化为中划线形式(`codeSelect` 与 `code-select` 等价)。
4620
+ * 对同一 type 重复登记按字段浅合并:后一次只覆盖自己传入的 key,未传入的 key 保留。
4621
+ * 传入 `app` 且带了 `component` / `container` 时,会同步 `app.component('m-fields-*'` / `'m-form-*')`。
4622
+ *
4623
+ * @param type - 字段 type
4624
+ * @param [options] - 登记项;省略或空对象视为叶子
4625
+ * @param [app] - Vue 应用;省略则不调用 `app.component`
4626
+ */
4627
+ var registerField = (type, options, app) => {
4628
+ registerFieldImpl(type, options, app, false);
3552
4629
  };
3553
- /** 清空业务侧追加的静默叶子字段(不影响内置默认集合;主要用于单测) */
3554
- var clearSilentLeafFieldTypes = () => {
3555
- extraSilentLeafFieldTypes.clear();
4630
+ /**
4631
+ * 批量登记字段。
4632
+ *
4633
+ * @param fields - type 到登记项的映射
4634
+ * @param [app] - Vue 应用;省略则不调用 `app.component`
4635
+ */
4636
+ var registerFields = (fields, app) => {
4637
+ for (const [type, options] of Object.entries(fields)) registerFieldImpl(type, options, app, false);
4638
+ };
4639
+ /**
4640
+ * 登记内置字段(`clearFields` / `unregisterField` 不会清掉)。
4641
+ *
4642
+ * @param fields - type 到登记项的映射
4643
+ * @param [app] - Vue 应用;省略则不调用 `app.component`
4644
+ */
4645
+ var registerBuiltInFields = (fields, app) => {
4646
+ for (const [type, options] of Object.entries(fields)) registerFieldImpl(type, options, app, true);
4647
+ };
4648
+ /**
4649
+ * 删除业务侧对指定 type 的登记(不影响内置;主要用于单测)。
4650
+ *
4651
+ * @param type - 字段 type
4652
+ */
4653
+ var unregisterField = (type) => {
4654
+ extraFieldOptions.delete((0, _tmagic_utils.toLine)(type));
4655
+ deleteLeafFieldType(type);
4656
+ deleteFieldInnerConfig(type);
4657
+ deleteTypeMatchRule(type);
4658
+ deleteContainerWalker(type);
4659
+ removeFormComponent(type);
4660
+ };
4661
+ /** 清空业务侧登记(不影响内置;主要用于单测)。 */
4662
+ var clearFields = () => {
4663
+ extraFieldOptions.clear();
4664
+ clearLeafFieldTypes();
4665
+ clearFieldInnerConfigs();
4666
+ clearTypeMatchRules();
4667
+ clearContainerWalkers();
4668
+ clearFormComponents();
3556
4669
  };
3557
4670
  //#endregion
3558
4671
  //#region packages/form/src/containers/FormLabel.vue?vue&type=script&setup=true&lang.ts
@@ -3612,7 +4725,7 @@
3612
4725
  };
3613
4726
  var _hoisted_3$7 = ["innerHTML"];
3614
4727
  var _hoisted_4$6 = ["innerHTML"];
3615
- var _hoisted_5$4 = ["innerHTML"];
4728
+ var _hoisted_5$5 = ["innerHTML"];
3616
4729
  var _hoisted_6$3 = ["innerHTML"];
3617
4730
  var _hoisted_7$2 = ["innerHTML"];
3618
4731
  var _hoisted_8$2 = ["innerHTML"];
@@ -3633,6 +4746,7 @@
3633
4746
  prop: { default: "" },
3634
4747
  disabled: { type: Boolean },
3635
4748
  labelWidth: {},
4749
+ labelPosition: {},
3636
4750
  expandMore: {
3637
4751
  type: Boolean,
3638
4752
  default: false
@@ -3668,20 +4782,8 @@
3668
4782
  return !isEqual(curValue, lastValue);
3669
4783
  });
3670
4784
  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
- });
4785
+ const itemProp = (0, vue.computed)(() => getItemProp(props.prop, name.value));
4786
+ const type = (0, vue.computed)(() => resolveItemType(mForm, props.config, props));
3685
4787
  const tagName = (0, vue.computed)(() => {
3686
4788
  if (type.value === "component" && props.config.component) return props.config.component;
3687
4789
  return getField(type.value || "container") || `m-${items.value ? "form" : "fields"}-${type.value}`;
@@ -3715,8 +4817,6 @@
3715
4817
  return new Set(DEFAULT_SELF_DIFF_FIELD_TYPES);
3716
4818
  });
3717
4819
  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
4820
  const disabled = (0, vue.computed)(() => props.disabled || filterFunction(mForm, props.config.disabled, props));
3721
4821
  const text = (0, vue.computed)(() => filterFunction(mForm, props.config.text, props));
3722
4822
  const tooltip = (0, vue.computed)(() => {
@@ -3748,12 +4848,13 @@
3748
4848
  const formItemProps = (0, vue.computed)(() => ({
3749
4849
  prop: itemProp.value,
3750
4850
  labelWidth: itemLabelWidth.value,
3751
- labelPosition: props.config.labelPosition,
4851
+ labelPosition: itemLabelPosition.value,
3752
4852
  rules: rule.value,
3753
4853
  extra: filterFunction(mForm, props.config.extra, props),
3754
4854
  extraTips: props.config.extraTips
3755
4855
  }));
3756
4856
  const itemLabelWidth = (0, vue.computed)(() => props.config.labelWidth ?? props.labelWidth);
4857
+ const itemLabelPosition = (0, vue.computed)(() => props.config.labelPosition ?? props.labelPosition);
3757
4858
  (0, vue.watchEffect)(() => {
3758
4859
  expand.value = props.expandMore;
3759
4860
  });
@@ -3782,13 +4883,7 @@
3782
4883
  };
3783
4884
  const onAddDiffCount = () => emit("addDiffCount");
3784
4885
  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
- };
4886
+ const isValidName$1 = () => isValidName(name.value);
3792
4887
  const createModelProxy = (target, setModelFn, pathPrefix = "") => {
3793
4888
  return new Proxy(target, {
3794
4889
  get: (obj, key) => {
@@ -3880,6 +4975,7 @@
3880
4975
  "step-active": __props.stepActive,
3881
4976
  "expand-more": expand.value,
3882
4977
  "label-width": itemLabelWidth.value,
4978
+ "label-position": itemLabelPosition.value,
3883
4979
  style: __props.config.fieldStyle,
3884
4980
  onChange: onChangeHandler,
3885
4981
  onAddDiffCount
@@ -3890,9 +4986,10 @@
3890
4986
  "step-active",
3891
4987
  "expand-more",
3892
4988
  "label-width",
4989
+ "label-position",
3893
4990
  "style"
3894
4991
  ])) : 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", {
4992
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
3896
4993
  config: __props.config,
3897
4994
  type: type.value,
3898
4995
  text: text.value,
@@ -3910,8 +5007,8 @@
3910
5007
  "use-label",
3911
5008
  "label-title",
3912
5009
  "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), {
5010
+ ]), __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)])]),
5011
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
3915
5012
  key: 0,
3916
5013
  placement: tooltip.value.placement
3917
5014
  }, {
@@ -3938,9 +5035,9 @@
3938
5035
  "model",
3939
5036
  "last-values",
3940
5037
  "is-compare"
3941
- ]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5038
+ ]))]),
3942
5039
  _: 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), {
5040
+ }, 16, ["class"]), __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
3944
5041
  key: 0,
3945
5042
  placement: "top"
3946
5043
  }, {
@@ -3960,7 +5057,7 @@
3960
5057
  "tmagic-form-hidden": `${itemLabelWidth.value}` === "0" || !text.value,
3961
5058
  "self-diff": true
3962
5059
  } }), {
3963
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
5060
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
3964
5061
  config: __props.config,
3965
5062
  type: type.value,
3966
5063
  text: text.value,
@@ -3978,12 +5075,12 @@
3978
5075
  "use-label",
3979
5076
  "label-title",
3980
5077
  "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), {
5078
+ ])])]),
5079
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
3983
5080
  key: 0,
3984
5081
  placement: tooltip.value.placement
3985
5082
  }, {
3986
- content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$4)]),
5083
+ content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$5)]),
3987
5084
  default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)(fieldsProps.value, {
3988
5085
  model: __props.model,
3989
5086
  "last-values": __props.lastValues,
@@ -4004,7 +5101,7 @@
4004
5101
  "model",
4005
5102
  "last-values",
4006
5103
  "is-compare"
4007
- ]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5104
+ ]))]),
4008
5105
  _: 3
4009
5106
  }, 16, ["class"])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
4010
5107
  (0, vue.createCommentVNode)(" 普通字段:渲染前后两份独立的组件用于对比 "),
@@ -4013,7 +5110,7 @@
4013
5110
  "tmagic-form-hidden": `${itemLabelWidth.value}` === "0" || !text.value,
4014
5111
  "show-before-diff": true
4015
5112
  } }), {
4016
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
5113
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
4017
5114
  config: __props.config,
4018
5115
  type: type.value,
4019
5116
  text: text.value,
@@ -4031,8 +5128,8 @@
4031
5128
  "use-label",
4032
5129
  "label-title",
4033
5130
  "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), {
5131
+ ])])]),
5132
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4036
5133
  key: 0,
4037
5134
  placement: tooltip.value.placement
4038
5135
  }, {
@@ -4045,10 +5142,10 @@
4045
5142
  }, 8, ["placement"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)({ key: 1 }, fieldsProps.value, {
4046
5143
  model: __props.lastValues,
4047
5144
  onChange: onChangeHandler
4048
- }), null, 16, ["model"]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5145
+ }), null, 16, ["model"]))]),
4049
5146
  _: 3
4050
5147
  }, 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), {
5148
+ __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4052
5149
  key: 0,
4053
5150
  placement: "top"
4054
5151
  }, {
@@ -4070,7 +5167,7 @@
4070
5167
  "show-after-diff": true
4071
5168
  }
4072
5169
  }), {
4073
- label: (0, vue.withCtx)(() => [shouldRenderLeafField.value ? (0, vue.renderSlot)(_ctx.$slots, "label", {
5170
+ label: (0, vue.withCtx)(() => [(0, vue.renderSlot)(_ctx.$slots, "label", {
4074
5171
  config: __props.config,
4075
5172
  type: type.value,
4076
5173
  text: text.value,
@@ -4088,8 +5185,8 @@
4088
5185
  "use-label",
4089
5186
  "label-title",
4090
5187
  "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), {
5188
+ ])])]),
5189
+ default: (0, vue.withCtx)(() => [tooltip.value.text ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4093
5190
  key: 0,
4094
5191
  placement: tooltip.value.placement
4095
5192
  }, {
@@ -4102,10 +5199,10 @@
4102
5199
  }, 8, ["placement"])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)({ key: 1 }, fieldsProps.value, {
4103
5200
  model: __props.model,
4104
5201
  onChange: onChangeHandler
4105
- }), null, 16, ["model"]))], 64)) : (0, vue.createCommentVNode)("v-if", true)]),
5202
+ }), null, 16, ["model"]))]),
4106
5203
  _: 3
4107
5204
  }, 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), {
5205
+ __props.config.tip && type.value === "checkbox" && !__props.config.useLabel ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
4109
5206
  key: 1,
4110
5207
  placement: "top"
4111
5208
  }, {
@@ -4120,11 +5217,11 @@
4120
5217
  _: 1
4121
5218
  })) : (0, vue.createCommentVNode)("v-if", true)
4122
5219
  ], 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) => {
5220
+ ], 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
5221
  return (0, vue.openBlock)(), (0, vue.createBlock)(_component_Container, {
4125
5222
  key: key(item),
4126
- model: isValidName() ? __props.model[name.value] : __props.model,
4127
- "last-values": isValidName() ? __props.lastValues[name.value] || {} : __props.lastValues,
5223
+ model: isValidName$1() ? __props.model[name.value] : __props.model,
5224
+ "last-values": isValidName$1() ? __props.lastValues[name.value] || {} : __props.lastValues,
4128
5225
  "is-compare": __props.isCompare,
4129
5226
  config: item,
4130
5227
  size: __props.size,
@@ -4132,6 +5229,7 @@
4132
5229
  "step-active": __props.stepActive,
4133
5230
  "expand-more": expand.value,
4134
5231
  "label-width": itemLabelWidth.value,
5232
+ "label-position": itemLabelPosition.value,
4135
5233
  prop: itemProp.value,
4136
5234
  onChange: onChangeHandler,
4137
5235
  onAddDiffCount
@@ -4149,6 +5247,7 @@
4149
5247
  "step-active",
4150
5248
  "expand-more",
4151
5249
  "label-width",
5250
+ "label-position",
4152
5251
  "prop"
4153
5252
  ]);
4154
5253
  }), 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 +5303,7 @@
4204
5303
  type: Boolean,
4205
5304
  default: true
4206
5305
  },
4207
- extendState: {},
5306
+ context: {},
4208
5307
  showDiff: {},
4209
5308
  selfDiffFieldTypes: {},
4210
5309
  theme: {}
@@ -4224,8 +5323,6 @@
4224
5323
  const initialized = (0, vue.ref)(false);
4225
5324
  const values = (0, vue.ref)({});
4226
5325
  const lastValuesProcessed = (0, vue.ref)({});
4227
- const fields = /* @__PURE__ */ new Map();
4228
- const requestFuc = getConfig("request");
4229
5326
  /**
4230
5327
  * 当前表单生效的主题名称:
4231
5328
  * - 优先用本组件自己的 `props.theme`;
@@ -4255,18 +5352,14 @@
4255
5352
  * 2. `values` / `lastValuesProcessed` 是 ref,Vue 的 `reactive` 会自动解包,因此每次
4256
5353
  * 访问 `formState.values` / `formState.lastValuesProcessed` 也都是当前 ref 值。
4257
5354
  *
4258
- * 3. `extendState` 注入的字段在下方的 `watchEffect` 中合并到 `formState`:
4259
- * - data 描述符(普通字段)通过 `formState[key] = value` 写入,走 reactive proxy 的
4260
- * set,触发依赖通知;`extendState` 同步段读到的响应式数据变化时会自动重跑,
4261
- * 把最新值刷进 formState。
4262
- * - accessor 描述符(`{ get stage() { return ... } }`)按原样写入,调用方可以控制
4263
- * 读时求值,每次读取都会重新执行 getter。
5355
+ * 3. 宿主业务上下文不再 merge 进 coreState,而是单独放在 `contextRef` 上,由读穿
5356
+ * Proxy 在 miss 时落到 context。核心字段结构性优先,`mForm.xxx` 永久兼容旧读法。
4264
5357
  *
4265
5358
  * 4. `popperClass` 会自动拼接 `themeClass`:调用方传入的 `popperClass` + 当前主题
4266
5359
  * 修饰类(含祖先 `<MEditor>` provide 的主题)。这样所有透传到 Element Plus 弹层
4267
5360
  * `popper-class` 的字段(Select / DateTime / Cascader 等)能自带主题作用域。
4268
5361
  */
4269
- const formState = (0, vue.reactive)({
5362
+ const coreState = (0, vue.reactive)({
4270
5363
  get keyProp() {
4271
5364
  return props.keyProp;
4272
5365
  },
@@ -4295,68 +5388,20 @@
4295
5388
  values,
4296
5389
  lastValuesProcessed,
4297
5390
  $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
- }
5391
+ ...createFormStateBase({
5392
+ $message: _tmagic_design.tMagicMessage,
5393
+ $messageBox: _tmagic_design.tMagicMessageBox
5394
+ })
4310
5395
  });
5396
+ const ancestorContext = (0, vue.inject)(FORM_CONTEXT_KEY, void 0);
4311
5397
  /**
4312
- * formState 的内置 key 快照(keyProp / values / $emit / fields / post 等)。
4313
- *
4314
- * 在 `extendState` 首次合并前捕获,`applyExtendState` 会据此禁止 `extendState`
4315
- * 覆盖这些已有字段(只能新增字段),避免表单核心状态被外部意外改写。
4316
- *
4317
- * 之所以在此处(effect 之外)捕获而不是在 `applyExtendState` 内动态取:
4318
- * `watchEffect` 会在依赖变化时重跑,若动态取,`extendState` 自己新增的字段在第二次
4319
- * 合并时也会被当成「已有 key」而拒绝刷新;这里只锁定内置字段即可规避该问题。
4320
- */
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 等)以普通字段形式返回时会被跳过并告警。
5398
+ * 宿主业务上下文:`props.context` 覆盖祖先注入的同名字段。
5399
+ * 嵌套表单(Link / FormBox / FormDialog)通过下面的 provide 自动继承。
4341
5400
  */
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
- });
5401
+ const contextRef = (0, vue.computed)(() => mergeFormContexts(ancestorContext?.value, props.context));
5402
+ const formState = createFormStateProxy(coreState, () => contextRef.value);
4359
5403
  (0, vue.provide)("mForm", formState);
5404
+ (0, vue.provide)(FORM_CONTEXT_KEY, contextRef);
4360
5405
  /**
4361
5406
  * 把生效主题(自身或祖先)再 provide 出去,供 form 子树内含 `Teleport` 的组件
4362
5407
  * (如 `TMagicPopover`)在传送目标上挂 `m-theme--<theme>` 类。
@@ -4373,14 +5418,24 @@
4373
5418
  }
4374
5419
  });
4375
5420
  const changeRecords = (0, vue.shallowRef)([]);
4376
- (0, vue.watch)([() => props.config, () => props.initValues], ([config], [preConfig]) => {
5421
+ /**
5422
+ * 两份配置的结构是否一致;函数一律视为相等。
5423
+ *
5424
+ * 宿主(如编辑器属性面板)往往在每次节点更新后整份重新生成配置,其中的
5425
+ * `display` / `options` / `onChange` 都是新闭包,深比较必然判不等。若据此把 `initialized`
5426
+ * 置 false,整棵表单会卸载重挂,滚动位置、展开态、输入焦点全部丢失。
5427
+ * 配置是响应式 prop,闭包换了照样生效,只有结构变化(增删字段、换组件类型)才需要重挂。
5428
+ */
5429
+ const isSameConfigShape = (config, preConfig) => isEqualWith(config, preConfig, (a, b) => typeof a === "function" && typeof b === "function" ? true : void 0);
5430
+ (0, vue.watch)([() => props.config, () => props.initValues], async ([config], [preConfig]) => {
4377
5431
  changeRecords.value = [];
4378
- if (!isEqual((0, vue.toRaw)(config), (0, vue.toRaw)(preConfig))) initialized.value = false;
5432
+ if (!isSameConfigShape((0, vue.toRaw)(config), (0, vue.toRaw)(preConfig))) initialized.value = false;
4379
5433
  initValue(formState, {
4380
5434
  initValues: props.initValues,
4381
5435
  config: props.config
4382
5436
  }).then((value) => {
4383
5437
  values.value = value;
5438
+ applyMountValueEffects(formState, props.config, values.value);
4384
5439
  initialized.value = !props.isCompare;
4385
5440
  if (props.validateOnInit) (0, vue.nextTick)(() => {
4386
5441
  tMagicFormRef.value?.validate();
@@ -4391,6 +5446,7 @@
4391
5446
  config: props.config
4392
5447
  }).then((value) => {
4393
5448
  lastValuesProcessed.value = value;
5449
+ applyMountValueEffects(formState, props.config, lastValuesProcessed.value);
4394
5450
  initialized.value = true;
4395
5451
  });
4396
5452
  }, { immediate: true });
@@ -4408,51 +5464,17 @@
4408
5464
  const submitHandler = (e) => {
4409
5465
  if (props.preventSubmitDefault) e.preventDefault();
4410
5466
  };
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
- };
5467
+ const getTextByName$1 = (name, config = props.config) => getTextByName(name, config);
4439
5468
  /**
4440
5469
  * 将校验返回的 invalidFields 汇总为可读的错误文案(多条以 `<br>` 拼接)。
4441
5470
  *
4442
- * 抽离为独立方法,供 `submitForm`(提交校验)与 `validate`(返回错误文案的校验)复用,
4443
- * 保证两种校验入口产出的错误文案格式完全一致。
5471
+ * 实现收口在 `utils/validateError`,供渲染式校验(本组件的 `submitForm` / `validate`)与
5472
+ * 无渲染校验(`validateValues`)共用,保证两条链路产出的错误文案格式完全一致。
4444
5473
  */
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
- };
5474
+ const formatValidateError$1 = (invalidFields) => formatValidateError(invalidFields, {
5475
+ config: props.config,
5476
+ useFieldTextInError: props.useFieldTextInError
5477
+ });
4456
5478
  __expose({
4457
5479
  values,
4458
5480
  lastValuesProcessed,
@@ -4472,7 +5494,7 @@
4472
5494
  return native ? values.value : cloneDeep((0, vue.toRaw)(values.value));
4473
5495
  } catch (invalidFields) {
4474
5496
  emit("error", invalidFields);
4475
- throw new Error(formatValidateError(invalidFields));
5497
+ throw new Error(formatValidateError$1(invalidFields));
4476
5498
  }
4477
5499
  },
4478
5500
  /**
@@ -4497,10 +5519,10 @@
4497
5519
  if (result !== true) throw result;
4498
5520
  return "";
4499
5521
  } catch (invalidFields) {
4500
- return formatValidateError(invalidFields);
5522
+ return formatValidateError$1(invalidFields);
4501
5523
  }
4502
5524
  },
4503
- getTextByName
5525
+ getTextByName: getTextByName$1
4504
5526
  });
4505
5527
  return (_ctx, _cache) => {
4506
5528
  return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicForm), {
@@ -4526,6 +5548,7 @@
4526
5548
  "last-values": lastValuesProcessed.value,
4527
5549
  "is-compare": __props.isCompare,
4528
5550
  "label-width": item.labelWidth || __props.labelWidth,
5551
+ "label-position": item.labelPosition || __props.labelPosition,
4529
5552
  "step-active": __props.stepActive,
4530
5553
  size: __props.size,
4531
5554
  onChange: changeHandler
@@ -4540,6 +5563,7 @@
4540
5563
  "last-values",
4541
5564
  "is-compare",
4542
5565
  "label-width",
5566
+ "label-position",
4543
5567
  "step-active",
4544
5568
  "size"
4545
5569
  ]);
@@ -4558,38 +5582,29 @@
4558
5582
  });
4559
5583
  //#endregion
4560
5584
  //#region packages/form/src/submitForm.ts
4561
- /** 未指定或传入非正数 timeout 时的兜底超时(毫秒),保证非 debug 挂载始终能被清理 */
4562
- var DEFAULT_MOUNT_TIMEOUT = 1e4;
4563
5585
  /**
4564
- * submitForm / validateForm 的公共脚手架:
5586
+ * `dialog: true` 时 submitForm / validateForm 的公共脚手架:
4565
5587
  *
4566
- * 创建临时容器 → 挂载一个 wrapper 组件(内含 MForm)→ 提供统一的 cleanup / timeout / appContext 继承 →
5588
+ * 创建临时容器 → 挂载一个 wrapper 组件(内含 MForm)→ 提供统一的 cleanup / appContext 继承 →
4567
5589
  * 由 `createWrapper` 决定「何时调用 MForm 的哪个方法、如何 resolve/reject」。
4568
5590
  *
4569
- * 调用方只需关注差异逻辑(调用 `submitForm` 还是 `validate`、结果如何包装、是否需要调试弹层),
4570
- * 容器创建、卸载、超时、上下文注入等模板代码在此统一收口。
5591
+ * 挂载出来的表单等待人工点击,因此没有超时兜底,靠「确定」/「取消」/`signal` 结束。
4571
5592
  */
4572
5593
  var mountFormInstance = (options) => {
4573
- const { formProps, appContext, timeout, timeoutMessage, hidden = true, skipTimeout = false, signal, createWrapper } = options;
5594
+ const { formProps, appContext, signal, createWrapper } = options;
4574
5595
  return new Promise((resolve, reject) => {
4575
5596
  if (signal?.aborted) {
4576
5597
  reject(signal.reason ?? /* @__PURE__ */ new Error("mountFormInstance aborted"));
4577
5598
  return;
4578
5599
  }
4579
5600
  let cleaned = false;
4580
- let timer = null;
4581
5601
  let onAbort = null;
4582
5602
  const instance = { app: null };
4583
5603
  const container = document.createElement("div");
4584
- if (hidden) container.style.display = "none";
4585
5604
  document.body.appendChild(container);
4586
5605
  const cleanup = () => {
4587
5606
  if (cleaned) return;
4588
5607
  cleaned = true;
4589
- if (timer) {
4590
- clearTimeout(timer);
4591
- timer = null;
4592
- }
4593
5608
  if (signal && onAbort) {
4594
5609
  signal.removeEventListener("abort", onAbort);
4595
5610
  onAbort = null;
@@ -4609,53 +5624,16 @@
4609
5624
  }
4610
5625
  try {
4611
5626
  const formRef = (0, vue.ref)(null);
4612
- const { extendState, ...restFormProps } = formProps;
4613
- const userWrapper = createWrapper({
5627
+ const wrapperComponent = createWrapper({
4614
5628
  formRef,
4615
- formProps: restFormProps,
5629
+ formProps,
4616
5630
  cleanup,
4617
5631
  resolve,
4618
5632
  reject
4619
5633
  });
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);
5634
+ const app = (0, vue.createApp)(wrapperComponent);
4651
5635
  instance.app = app;
4652
5636
  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
5637
  app.mount(container);
4660
5638
  } catch (err) {
4661
5639
  reject(err);
@@ -4664,12 +5642,12 @@
4664
5642
  });
4665
5643
  };
4666
5644
  /**
4667
- * 构造调试模式下的可见弹层 wrapper:以 fixed 遮罩居中渲染 MForm,提供「确定 / 取消」按钮与错误展示区。
5645
+ * 构造可见弹层 wrapper:以 fixed 遮罩居中渲染 MForm,提供「确定 / 取消」按钮与错误展示区。
4668
5646
  *
4669
- * submitForm 与 validateForm 的调试弹层 UI 完全一致,仅「确定」时调用的实例方法、错误处理与取消文案不同,
5647
+ * submitForm 与 validateForm 的弹层 UI 完全一致,仅「确定」时调用的实例方法、错误处理与取消文案不同,
4670
5648
  * 这些差异通过 `onConfirm` / `onCancel` 注入,弹层结构在此统一收口。
4671
5649
  */
4672
- var createDebugWrapper = (options) => {
5650
+ var createDialogWrapper = (options) => {
4673
5651
  const { formRef, formProps, title, name, onConfirm, onCancel } = options;
4674
5652
  const btnBase = {
4675
5653
  padding: "8px 20px",
@@ -4752,57 +5730,17 @@
4752
5730
  });
4753
5731
  };
4754
5732
  /**
4755
- * 以命令式方式调用 Form.vue 完成一次表单校验/提交。
5733
+ * 可见弹层提交:把表单渲染在弹层里,点击「确定」才调用挂载实例的 `submitForm`。
4756
5734
  *
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
- * ```
5735
+ * 用于需要用户填写/确认的场景;默认路径仍是无渲染校验(见 `submitForm` 的说明)。
4789
5736
  */
4790
- var submitForm = (options) => {
4791
- const { native, appContext, timeout = 1e4, returnChangeRecords, debug = false, signal, ...formProps } = options;
5737
+ var submitFormByDialogRender = (options) => {
5738
+ const { native, appContext, returnChangeRecords, signal, dialog, title, ...formProps } = options;
4792
5739
  return mountFormInstance({
4793
5740
  formProps,
4794
5741
  appContext,
4795
- timeout,
4796
5742
  signal,
4797
- hidden: !debug,
4798
- skipTimeout: debug,
4799
- timeoutMessage: `submitForm timeout after ${timeout}ms: form is not initialized.`,
4800
5743
  createWrapper: ({ formRef, formProps, cleanup, resolve, reject }) => {
4801
- /**
4802
- * 执行一次提交:nextTick 等待子组件渲染 → 快照 changeRecords → 调用实例 submitForm → resolve。
4803
- * 校验失败时交给 `onValidateError` 决定「保留弹层展示错误(debug)」还是「reject 并清理(普通)」,
4804
- * 从而让 debug 与普通模式共享同一份提交逻辑。
4805
- */
4806
5744
  const doSubmit = async (onValidateError) => {
4807
5745
  try {
4808
5746
  await (0, vue.nextTick)();
@@ -4817,39 +5755,19 @@
4817
5755
  onValidateError(err);
4818
5756
  }
4819
5757
  };
4820
- if (debug) return createDebugWrapper({
5758
+ return createDialogWrapper({
4821
5759
  formRef,
4822
5760
  formProps,
4823
5761
  name: "MFormSubmitWrapper",
4824
- title: "submitForm 调试",
5762
+ title: title ?? "submitForm",
4825
5763
  onConfirm: (setError) => doSubmit((err) => {
4826
5764
  setError(err instanceof Error ? err.message : String(err));
4827
5765
  }),
4828
5766
  onCancel: () => {
4829
- reject(/* @__PURE__ */ new Error("submitForm canceled in debug mode."));
5767
+ reject(/* @__PURE__ */ new Error("submitForm canceled."));
4830
5768
  cleanup();
4831
5769
  }
4832
5770
  });
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
5771
  }
4854
5772
  });
4855
5773
  };
@@ -4893,61 +5811,20 @@
4893
5811
  return cloned;
4894
5812
  };
4895
5813
  /**
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
- * }
4923
- *
4924
- * // 调试模式:可见地渲染表单,点击「确定」才校验,校验失败保留弹层可修正重试:
4925
- * const error = await validateForm({
4926
- * config: [...],
4927
- * initValues: { name: 'foo' },
4928
- * debug: true,
4929
- * });
4930
- * ```
5814
+ * 可见弹层校验:把表单渲染在弹层里,点击「确定」才调用挂载实例的 `validate`。
5815
+ *
5816
+ * 用于需要用户填写/确认的场景;默认路径仍是无渲染校验(见 `validateForm` 的说明)。
4931
5817
  */
4932
- var validateForm = (options) => {
4933
- const { appContext, timeout = 1e4, debug = false, config, signal, ...rest } = options;
5818
+ var validateFormByDialogRender = (options) => {
5819
+ const { appContext, config, signal, dialog, title, ...rest } = options;
4934
5820
  return mountFormInstance({
4935
5821
  formProps: {
4936
5822
  ...rest,
4937
5823
  config: stripTabItemsLazy(config)
4938
5824
  },
4939
5825
  appContext,
4940
- timeout,
4941
5826
  signal,
4942
- hidden: !debug,
4943
- skipTimeout: debug,
4944
- timeoutMessage: `validateForm timeout after ${timeout}ms: form is not initialized.`,
4945
5827
  createWrapper: ({ formRef, formProps, cleanup, resolve, reject }) => {
4946
- /**
4947
- * 执行一次校验:nextTick 等待子组件渲染 → 调用实例 validate → 通过则 resolve ''。
4948
- * 校验失败(返回非空错误文案)时交给 `onInvalid` 决定「静默 resolve 错误文案(普通)」
4949
- * 还是「在弹层展示错误并保留供重试(debug)」,从而让两种模式共享同一份校验逻辑。
4950
- */
4951
5828
  const doValidate = async (onInvalid) => {
4952
5829
  try {
4953
5830
  await (0, vue.nextTick)();
@@ -4962,42 +5839,40 @@
4962
5839
  cleanup();
4963
5840
  }
4964
5841
  };
4965
- if (debug) return createDebugWrapper({
5842
+ return createDialogWrapper({
4966
5843
  formRef,
4967
5844
  formProps,
4968
5845
  name: "MFormValidateWrapper",
4969
- title: "validateForm 调试",
5846
+ title: title ?? "validateForm",
4970
5847
  onConfirm: (setError) => doValidate((error) => {
4971
5848
  setError(error);
4972
5849
  }),
4973
5850
  onCancel: () => {
4974
- reject(/* @__PURE__ */ new Error("validateForm canceled in debug mode."));
5851
+ reject(/* @__PURE__ */ new Error("validateForm canceled."));
4975
5852
  cleanup();
4976
5853
  }
4977
5854
  });
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
5855
  }
4999
5856
  });
5000
5857
  };
5858
+ /**
5859
+ * 以命令式方式对一份「表单配置 + 值」做一次校验并取回表单值。
5860
+ *
5861
+ * 默认走无渲染实现(见 `@tmagic/form/headless`)。`dialog: true` 会把表单以弹层渲染出来。
5862
+ */
5863
+ var submitForm = async (options) => {
5864
+ if (options.dialog) return submitFormByDialogRender(options);
5865
+ return submitForm$1(options);
5866
+ };
5867
+ /**
5868
+ * 以命令式方式对一份「表单配置 + 值」做一次静默校验。
5869
+ *
5870
+ * 默认走无渲染实现。`dialog: true` 的处理详见 `submitForm`。
5871
+ */
5872
+ var validateForm = async (options) => {
5873
+ if (options.dialog) return validateFormByDialogRender(options);
5874
+ return validateForm$1(options);
5875
+ };
5001
5876
  //#endregion
5002
5877
  //#region packages/form/src/FormDialog.vue?vue&type=script&setup=true&lang.ts
5003
5878
  var _hoisted_1$13 = { style: { "min-height": "1px" } };
@@ -5050,7 +5925,7 @@
5050
5925
  type: Boolean,
5051
5926
  default: true
5052
5927
  },
5053
- extendState: {},
5928
+ context: {},
5054
5929
  theme: {}
5055
5930
  },
5056
5931
  emits: [
@@ -5208,7 +6083,7 @@
5208
6083
  "use-field-text-in-error": __props.useFieldTextInError,
5209
6084
  "type-match-valid": __props.typeMatchValid,
5210
6085
  "validate-on-init": __props.validateOnInit,
5211
- "extend-state": __props.extendState,
6086
+ context: __props.context,
5212
6087
  theme: effectiveTheme.value,
5213
6088
  onChange: changeHandler
5214
6089
  }, null, 8, [
@@ -5225,7 +6100,7 @@
5225
6100
  "use-field-text-in-error",
5226
6101
  "type-match-valid",
5227
6102
  "validate-on-init",
5228
- "extend-state",
6103
+ "context",
5229
6104
  "theme"
5230
6105
  ]), (0, vue.renderSlot)(_ctx.$slots, "default")], 4)) : (0, vue.createCommentVNode)("v-if", true)]),
5231
6106
  _: 3
@@ -5280,7 +6155,7 @@
5280
6155
  default: true
5281
6156
  },
5282
6157
  beforeClose: {},
5283
- extendState: {},
6158
+ context: {},
5284
6159
  theme: {}
5285
6160
  },
5286
6161
  emits: [
@@ -5431,7 +6306,7 @@
5431
6306
  "use-field-text-in-error": __props.useFieldTextInError,
5432
6307
  "type-match-valid": __props.typeMatchValid,
5433
6308
  "validate-on-init": __props.validateOnInit,
5434
- "extend-state": __props.extendState,
6309
+ context: __props.context,
5435
6310
  theme: effectiveTheme.value,
5436
6311
  onChange: changeHandler
5437
6312
  }, null, 8, [
@@ -5447,7 +6322,7 @@
5447
6322
  "use-field-text-in-error",
5448
6323
  "type-match-valid",
5449
6324
  "validate-on-init",
5450
- "extend-state",
6325
+ "context",
5451
6326
  "theme"
5452
6327
  ]), (0, vue.renderSlot)(_ctx.$slots, "default")], 512)) : (0, vue.createCommentVNode)("v-if", true)]),
5453
6328
  _: 3
@@ -5493,7 +6368,7 @@
5493
6368
  type: Boolean,
5494
6369
  default: true
5495
6370
  },
5496
- extendState: {}
6371
+ context: {}
5497
6372
  },
5498
6373
  emits: [
5499
6374
  "change",
@@ -5557,7 +6432,7 @@
5557
6432
  inline: __props.inline,
5558
6433
  "prevent-submit-default": __props.preventSubmitDefault,
5559
6434
  "use-field-text-in-error": __props.useFieldTextInError,
5560
- "extend-state": __props.extendState,
6435
+ context: __props.context,
5561
6436
  "type-match-valid": __props.typeMatchValid,
5562
6437
  "validate-on-init": __props.validateOnInit,
5563
6438
  onChange: changeHandler
@@ -5572,7 +6447,7 @@
5572
6447
  "inline",
5573
6448
  "prevent-submit-default",
5574
6449
  "use-field-text-in-error",
5575
- "extend-state",
6450
+ "context",
5576
6451
  "type-match-valid",
5577
6452
  "validate-on-init"
5578
6453
  ]), (0, vue.renderSlot)(_ctx.$slots, "default")]),
@@ -5603,7 +6478,7 @@
5603
6478
  var _hoisted_2$7 = ["innerHTML"];
5604
6479
  var _hoisted_3$6 = { key: 1 };
5605
6480
  var _hoisted_4$5 = ["innerHTML"];
5606
- var _hoisted_5$3 = ["innerHTML"];
6481
+ var _hoisted_5$4 = ["innerHTML"];
5607
6482
  var _hoisted_6$2 = {
5608
6483
  key: 2,
5609
6484
  style: { "display": "flex" }
@@ -5617,6 +6492,7 @@
5617
6492
  __name: "Fieldset",
5618
6493
  props: {
5619
6494
  labelWidth: {},
6495
+ labelPosition: {},
5620
6496
  prop: { default: "" },
5621
6497
  size: {},
5622
6498
  model: {},
@@ -5656,6 +6532,7 @@
5656
6532
  if (props.config.items) return props.config.labelWidth || props.labelWidth;
5657
6533
  return props.config.labelWidth || props.labelWidth || (props.config.text ? void 0 : "0");
5658
6534
  });
6535
+ const lPosition = (0, vue.computed)(() => props.config.labelPosition || props.labelPosition);
5659
6536
  const valueChangeHandler = (value) => {
5660
6537
  emit("change", value, { modifyKey: checkboxName.value });
5661
6538
  };
@@ -5692,7 +6569,7 @@
5692
6569
  key: 0,
5693
6570
  innerHTML: __props.config.extra,
5694
6571
  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) => {
6572
+ }, 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
6573
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
5697
6574
  key: key(item, index),
5698
6575
  model: name.value ? __props.model[name.value] : __props.model,
@@ -5703,6 +6580,7 @@
5703
6580
  prop: __props.prop,
5704
6581
  disabled: __props.disabled,
5705
6582
  labelWidth: lWidth.value,
6583
+ labelPosition: lPosition.value,
5706
6584
  size: __props.size,
5707
6585
  onChange: changeHandler,
5708
6586
  onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
@@ -5715,6 +6593,7 @@
5715
6593
  "prop",
5716
6594
  "disabled",
5717
6595
  "labelWidth",
6596
+ "labelPosition",
5718
6597
  "size"
5719
6598
  ]);
5720
6599
  }), 128))]), (0, vue.createElementVNode)("img", {
@@ -5730,6 +6609,7 @@
5730
6609
  config: item,
5731
6610
  prop: __props.prop,
5732
6611
  labelWidth: lWidth.value,
6612
+ labelPosition: lPosition.value,
5733
6613
  size: __props.size,
5734
6614
  disabled: __props.disabled,
5735
6615
  onChange: changeHandler,
@@ -5742,6 +6622,7 @@
5742
6622
  "config",
5743
6623
  "prop",
5744
6624
  "labelWidth",
6625
+ "labelPosition",
5745
6626
  "size",
5746
6627
  "disabled"
5747
6628
  ]);
@@ -5761,6 +6642,7 @@
5761
6642
  config: {},
5762
6643
  name: {},
5763
6644
  labelWidth: {},
6645
+ labelPosition: {},
5764
6646
  prop: {},
5765
6647
  size: {},
5766
6648
  disabled: { type: Boolean }
@@ -5794,6 +6676,7 @@
5794
6676
  size: __props.size,
5795
6677
  disabled: __props.disabled,
5796
6678
  "label-width": __props.config.labelWidth || __props.labelWidth,
6679
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5797
6680
  onChange: changeHandler,
5798
6681
  onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
5799
6682
  }, null, 8, [
@@ -5804,7 +6687,8 @@
5804
6687
  "prop",
5805
6688
  "size",
5806
6689
  "disabled",
5807
- "label-width"
6690
+ "label-width",
6691
+ "label-position"
5808
6692
  ]);
5809
6693
  }), 128))], 4);
5810
6694
  };
@@ -5823,7 +6707,7 @@
5823
6707
  style: { "display": "flex" }
5824
6708
  };
5825
6709
  var _hoisted_4$4 = { style: { "flex": "1" } };
5826
- var _hoisted_5$2 = ["src"];
6710
+ var _hoisted_5$3 = ["src"];
5827
6711
  //#endregion
5828
6712
  //#region packages/form/src/containers/Panel.vue
5829
6713
  var Panel_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -5836,6 +6720,7 @@
5836
6720
  config: {},
5837
6721
  name: {},
5838
6722
  labelWidth: {},
6723
+ labelPosition: {},
5839
6724
  prop: {},
5840
6725
  size: {},
5841
6726
  disabled: { type: Boolean },
@@ -5898,6 +6783,7 @@
5898
6783
  size: __props.size,
5899
6784
  disabled: __props.disabled,
5900
6785
  "label-width": __props.config.labelWidth || __props.labelWidth,
6786
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5901
6787
  onChange: changeHandler,
5902
6788
  onAddDiffCount: _cache[2] || (_cache[2] = ($event) => onAddDiffCount())
5903
6789
  }, null, 8, [
@@ -5908,12 +6794,13 @@
5908
6794
  "prop",
5909
6795
  "size",
5910
6796
  "disabled",
5911
- "label-width"
6797
+ "label-width",
6798
+ "label-position"
5912
6799
  ]);
5913
6800
  }), 128))]), (0, vue.createElementVNode)("img", {
5914
6801
  class: "m-form-schematic",
5915
6802
  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) => {
6803
+ }, null, 8, _hoisted_5$3)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
5917
6804
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
5918
6805
  key: item[(0, vue.unref)(mForm)?.keyProp || "__key"] ?? index,
5919
6806
  config: item,
@@ -5924,6 +6811,7 @@
5924
6811
  size: __props.size,
5925
6812
  disabled: __props.disabled,
5926
6813
  "label-width": __props.config.labelWidth || __props.labelWidth,
6814
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5927
6815
  onChange: changeHandler,
5928
6816
  onAddDiffCount: _cache[3] || (_cache[3] = ($event) => onAddDiffCount())
5929
6817
  }, null, 8, [
@@ -5934,7 +6822,8 @@
5934
6822
  "prop",
5935
6823
  "size",
5936
6824
  "disabled",
5937
- "label-width"
6825
+ "label-width",
6826
+ "label-position"
5938
6827
  ]);
5939
6828
  }), 128))])]),
5940
6829
  _: 3
@@ -5953,6 +6842,7 @@
5953
6842
  isCompare: { type: Boolean },
5954
6843
  config: {},
5955
6844
  labelWidth: {},
6845
+ labelPosition: {},
5956
6846
  expandMore: { type: Boolean },
5957
6847
  span: {},
5958
6848
  size: {},
@@ -5977,6 +6867,7 @@
5977
6867
  config: __props.config,
5978
6868
  prop: __props.prop,
5979
6869
  "label-width": __props.config.labelWidth || __props.labelWidth,
6870
+ "label-position": __props.config.labelPosition || __props.labelPosition,
5980
6871
  "expand-more": __props.expandMore,
5981
6872
  size: __props.size,
5982
6873
  disabled: __props.disabled,
@@ -5990,6 +6881,7 @@
5990
6881
  "config",
5991
6882
  "prop",
5992
6883
  "label-width",
6884
+ "label-position",
5993
6885
  "expand-more",
5994
6886
  "size",
5995
6887
  "disabled"
@@ -6011,6 +6903,7 @@
6011
6903
  config: {},
6012
6904
  name: {},
6013
6905
  labelWidth: {},
6906
+ labelPosition: {},
6014
6907
  prop: {},
6015
6908
  size: {},
6016
6909
  expandMore: { type: Boolean },
@@ -6033,6 +6926,7 @@
6033
6926
  span: col.span || __props.config.span || 24 / __props.config.items.length,
6034
6927
  config: col,
6035
6928
  labelWidth: __props.config.labelWidth || __props.labelWidth,
6929
+ labelPosition: __props.config.labelPosition || __props.labelPosition,
6036
6930
  expandMore: __props.expandMore,
6037
6931
  model: __props.name ? __props.model[__props.name] : __props.model,
6038
6932
  lastValues: __props.name ? __props.lastValues[__props.name] : __props.lastValues,
@@ -6046,6 +6940,7 @@
6046
6940
  "span",
6047
6941
  "config",
6048
6942
  "labelWidth",
6943
+ "labelPosition",
6049
6944
  "expandMore",
6050
6945
  "model",
6051
6946
  "lastValues",
@@ -6076,6 +6971,7 @@
6076
6971
  name: {},
6077
6972
  size: {},
6078
6973
  labelWidth: {},
6974
+ labelPosition: {},
6079
6975
  prop: { default: "" },
6080
6976
  expandMore: { type: Boolean },
6081
6977
  disabled: { type: Boolean }
@@ -6152,12 +7048,15 @@
6152
7048
  prop: props.prop,
6153
7049
  config: props.config
6154
7050
  });
7051
+ const tabsModel = props.model[props.name];
7052
+ if (Array.isArray(tabsModel) && Array.isArray(props.config.items)) for (const tab of tabsModel) applyMountValueEffects(mForm, props.config.items, tab);
6155
7053
  emit("change", props.model[props.name]);
6156
7054
  } else {
6157
7055
  const newObj = await initValue(mForm, {
6158
7056
  config: props.config.items,
6159
7057
  initValues: {}
6160
7058
  });
7059
+ applyMountValueEffects(mForm, props.config.items, newObj);
6161
7060
  newObj.title = `标签${tabs.value.length + 1}`;
6162
7061
  props.model[props.name].push(newObj);
6163
7062
  emit("change", props.model[props.name], { changeRecords: [{
@@ -6227,6 +7126,7 @@
6227
7126
  prop: __props.config.dynamic ? `${__props.prop}${__props.prop ? "." : ""}${String(tabIndex)}` : tab.name ? `${__props.prop}${__props.prop ? "." : ""}${tab.name}` : __props.prop,
6228
7127
  size: __props.size,
6229
7128
  "label-width": tab.labelWidth || __props.labelWidth,
7129
+ "label-position": tab.labelPosition || __props.labelPosition,
6230
7130
  "expand-more": __props.expandMore,
6231
7131
  onChange: changeHandler,
6232
7132
  onAddDiffCount: ($event) => onAddDiffCount(Number(tabIndex))
@@ -6239,6 +7139,7 @@
6239
7139
  "prop",
6240
7140
  "size",
6241
7141
  "label-width",
7142
+ "label-position",
6242
7143
  "expand-more",
6243
7144
  "onAddDiffCount"
6244
7145
  ]);
@@ -6254,12 +7155,14 @@
6254
7155
  //#endregion
6255
7156
  //#region packages/form/src/containers/GroupListItem.vue?vue&type=script&setup=true&lang.ts
6256
7157
  var _hoisted_1$9 = { class: "m-fields-group-list-item-header" };
6257
- var _hoisted_2$5 = ["innerHTML"];
6258
- var _hoisted_3$4 = { style: {
7158
+ var _hoisted_2$5 = { class: "m-fields-group-list-item-title" };
7159
+ var _hoisted_3$4 = ["innerHTML"];
7160
+ var _hoisted_4$3 = { class: "m-fields-group-list-item-actions" };
7161
+ var _hoisted_5$2 = { style: {
6259
7162
  "text-align": "right",
6260
7163
  "margin-top": "20px"
6261
7164
  } };
6262
- var _hoisted_4$3 = ["innerHTML"];
7165
+ var _hoisted_6$1 = ["innerHTML"];
6263
7166
  //#endregion
6264
7167
  //#region packages/form/src/containers/GroupListItem.vue
6265
7168
  var GroupListItem_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -6272,6 +7175,7 @@
6272
7175
  groupModel: {},
6273
7176
  config: {},
6274
7177
  labelWidth: {},
7178
+ labelPosition: {},
6275
7179
  prop: {},
6276
7180
  size: {},
6277
7181
  index: {},
@@ -6290,13 +7194,8 @@
6290
7194
  const mForm = (0, vue.inject)("mForm");
6291
7195
  const defaultExpandQuantity = props.config.defaultExpandQuantity ?? 7;
6292
7196
  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
- }));
7197
+ const rowConfig = (0, vue.computed)(() => getGroupListRowConfig(props.config, props.index, mForm?.keyProp));
7198
+ const rowProp = (0, vue.computed)(() => appendProp(props.prop, props.index));
6300
7199
  const title = (0, vue.computed)(() => {
6301
7200
  if (props.config.titleKey && props.model[props.config.titleKey]) {
6302
7201
  const { titlePrefix } = props.config;
@@ -6356,101 +7255,112 @@
6356
7255
  })]),
6357
7256
  _: 1
6358
7257
  }, 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), {
7258
+ (0, vue.createElementVNode)("div", _hoisted_2$5, [(0, vue.renderSlot)(_ctx.$slots, "title", {
7259
+ model: __props.model,
7260
+ index: __props.index,
7261
+ lastValues: __props.lastValues,
7262
+ prop: rowProp.value,
7263
+ title: title.value
7264
+ }, () => [(0, vue.createElementVNode)("span", { innerHTML: title.value }, null, 8, _hoisted_3$4)])]),
7265
+ (0, vue.createElementVNode)("div", _hoisted_4$3, [
7266
+ copyable.value && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicButton), {
6362
7267
  key: 0,
7268
+ link: "",
6363
7269
  size: "default",
7270
+ type: "primary",
7271
+ icon: (0, vue.unref)(_element_plus_icons_vue.DocumentCopy),
7272
+ disabled: __props.disabled,
7273
+ onClick: copyHandler
7274
+ }, {
7275
+ default: (0, vue.withCtx)(() => [..._cache[6] || (_cache[6] = [(0, vue.createTextVNode)("复制", -1)])]),
7276
+ _: 1
7277
+ }, 8, ["icon", "disabled"])) : (0, vue.createCommentVNode)("v-if", true),
7278
+ 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
7279
  link: "",
6365
- class: "delete-button",
6366
- icon: (0, vue.unref)(_element_plus_icons_vue.Delete),
7280
+ size: "default",
6367
7281
  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), {
7282
+ icon: (0, vue.unref)(_element_plus_icons_vue.Top),
7283
+ onClick: _cache[0] || (_cache[0] = ($event) => changeOrder(-1))
7284
+ }, {
7285
+ default: (0, vue.withCtx)(() => [..._cache[7] || (_cache[7] = [(0, vue.createTextVNode)("上移", -1)])]),
7286
+ _: 1
7287
+ }, 8, ["disabled", "icon"]), [[vue.vShow, __props.index !== 0]]), (0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6411
7288
  link: "",
6412
- size: "small",
6413
- type: "primary",
6414
- icon: (0, vue.unref)(_element_plus_icons_vue.Position),
7289
+ size: "default",
6415
7290
  disabled: __props.disabled,
6416
- onClick: _cache[2] || (_cache[2] = ($event) => moveSpecifyLocationVisible.value = true)
7291
+ icon: (0, vue.unref)(_element_plus_icons_vue.Bottom),
7292
+ onClick: _cache[1] || (_cache[1] = ($event) => changeOrder(1))
6417
7293
  }, {
6418
- default: (0, vue.withCtx)(() => [..._cache[9] || (_cache[9] = [(0, vue.createTextVNode)("移动至", -1)])]),
7294
+ default: (0, vue.withCtx)(() => [..._cache[8] || (_cache[8] = [(0, vue.createTextVNode)("下移", -1)])]),
6419
7295
  _: 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)
7296
+ }, 8, ["disabled", "icon"]), [[vue.vShow, __props.index !== length.value - 1]])], 64)) : (0, vue.createCommentVNode)("v-if", true),
7297
+ __props.config.moveSpecifyLocation && !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicPopover), {
7298
+ key: 2,
7299
+ trigger: "click",
7300
+ placement: "top",
7301
+ width: "200",
7302
+ visible: moveSpecifyLocationVisible.value
6436
7303
  }, {
6437
- default: (0, vue.withCtx)(() => [..._cache[12] || (_cache[12] = [(0, vue.createTextVNode)("取消", -1)])]),
7304
+ reference: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7305
+ link: "",
7306
+ size: "small",
7307
+ type: "primary",
7308
+ icon: (0, vue.unref)(_element_plus_icons_vue.Position),
7309
+ disabled: __props.disabled,
7310
+ onClick: _cache[2] || (_cache[2] = ($event) => moveSpecifyLocationVisible.value = true)
7311
+ }, {
7312
+ default: (0, vue.withCtx)(() => [..._cache[9] || (_cache[9] = [(0, vue.createTextVNode)("移动至", -1)])]),
7313
+ _: 1
7314
+ }, 8, ["icon", "disabled"])]),
7315
+ default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("div", null, [
7316
+ _cache[10] || (_cache[10] = (0, vue.createTextVNode)(" 第", -1)),
7317
+ (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicInputNumber), {
7318
+ style: { "margin": "0 5px" },
7319
+ modelValue: moveSpecifyLocationIndex.value,
7320
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => moveSpecifyLocationIndex.value = $event),
7321
+ size: "small",
7322
+ min: 1,
7323
+ disabled: __props.disabled
7324
+ }, null, 8, ["modelValue", "disabled"]),
7325
+ _cache[11] || (_cache[11] = (0, vue.createTextVNode)("行 ", -1))
7326
+ ]), (0, vue.createElementVNode)("div", _hoisted_5$2, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7327
+ size: "small",
7328
+ text: "",
7329
+ onClick: _cache[4] || (_cache[4] = ($event) => moveSpecifyLocationVisible.value = false)
7330
+ }, {
7331
+ default: (0, vue.withCtx)(() => [..._cache[12] || (_cache[12] = [(0, vue.createTextVNode)("取消", -1)])]),
7332
+ _: 1
7333
+ }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7334
+ size: "small",
7335
+ type: "primary",
7336
+ onClick: moveSpecifyLocationHandler
7337
+ }, {
7338
+ default: (0, vue.withCtx)(() => [..._cache[13] || (_cache[13] = [(0, vue.createTextVNode)("确认", -1)])]),
7339
+ _: 1
7340
+ })])])]),
6438
7341
  _: 1
6439
- }), (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
6440
- size: "small",
6441
- type: "primary",
6442
- onClick: moveSpecifyLocationHandler
7342
+ }, 8, ["visible"])) : (0, vue.createCommentVNode)("v-if", true),
7343
+ (0, vue.createCommentVNode)(" v-if 必须挂在 Tooltip 上:子节点为 false 时 ElOnlyChild 会报 no valid child node found "),
7344
+ !__props.isCompare ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
7345
+ key: 3,
7346
+ content: `删除 ${title.value}`
6443
7347
  }, {
6444
- default: (0, vue.withCtx)(() => [..._cache[13] || (_cache[13] = [(0, vue.createTextVNode)("确认", -1)])]),
7348
+ default: (0, vue.withCtx)(() => [(0, vue.withDirectives)((0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7349
+ size: "default",
7350
+ link: "",
7351
+ class: "delete-button",
7352
+ icon: (0, vue.unref)(_element_plus_icons_vue.Delete),
7353
+ disabled: __props.disabled,
7354
+ onClick: removeHandler
7355
+ }, null, 8, ["icon", "disabled"]), [[vue.vShow, showDelete.value]])]),
6445
7356
  _: 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)
7357
+ }, 8, ["content"])) : (0, vue.createCommentVNode)("v-if", true),
7358
+ itemExtra.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
7359
+ key: 4,
7360
+ innerHTML: itemExtra.value,
7361
+ class: "m-form-tip"
7362
+ }, null, 8, _hoisted_6$1)) : (0, vue.createCommentVNode)("v-if", true)
7363
+ ])
6454
7364
  ])]),
6455
7365
  default: (0, vue.withCtx)(() => [expand.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
6456
7366
  key: 0,
@@ -6459,7 +7369,8 @@
6459
7369
  lastValues: __props.lastValues,
6460
7370
  "is-compare": __props.isCompare,
6461
7371
  labelWidth: __props.labelWidth,
6462
- prop: `${__props.prop}${__props.prop ? "." : ""}${String(__props.index)}`,
7372
+ labelPosition: __props.labelPosition,
7373
+ prop: rowProp.value,
6463
7374
  size: __props.size,
6464
7375
  disabled: __props.disabled,
6465
7376
  onChange: changeHandler,
@@ -6470,11 +7381,12 @@
6470
7381
  "lastValues",
6471
7382
  "is-compare",
6472
7383
  "labelWidth",
7384
+ "labelPosition",
6473
7385
  "prop",
6474
7386
  "size",
6475
7387
  "disabled"
6476
7388
  ])) : (0, vue.createCommentVNode)("v-if", true)]),
6477
- _: 1
7389
+ _: 3
6478
7390
  }, 8, ["flat", "body-style"]);
6479
7391
  };
6480
7392
  }
@@ -6488,11 +7400,7 @@
6488
7400
  class: "el-table__empty-block"
6489
7401
  };
6490
7402
  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: {
7403
+ var _hoisted_5$1 = { style: {
6496
7404
  "display": "flex",
6497
7405
  "justify-content": "flex-end",
6498
7406
  "flex": "1"
@@ -6509,6 +7417,7 @@
6509
7417
  config: {},
6510
7418
  name: {},
6511
7419
  labelWidth: {},
7420
+ labelPosition: {},
6512
7421
  prop: {},
6513
7422
  size: {},
6514
7423
  disabled: { type: Boolean },
@@ -6537,7 +7446,23 @@
6537
7446
  emit("change", props.model[props.name]);
6538
7447
  };
6539
7448
  const onAddDiffCount = () => emit("addDiffCount");
6540
- const getLastValues = (item, index) => item?.[index] || {};
7449
+ const asList = (value) => Array.isArray(value) ? value : [];
7450
+ const currentList = (0, vue.computed)(() => asList(props.model[props.name]));
7451
+ /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
7452
+ const displayItems = (0, vue.computed)(() => {
7453
+ const current = currentList.value;
7454
+ if (!props.isCompare) return current.map((item, index) => ({
7455
+ item: item ?? {},
7456
+ last: {},
7457
+ index
7458
+ }));
7459
+ const last = asList(props.lastValues?.[props.name]);
7460
+ return Array.from({ length: Math.max(current.length, last.length) }, (_, index) => ({
7461
+ item: current[index] ?? {},
7462
+ last: last[index] ?? {},
7463
+ index
7464
+ }));
7465
+ });
6541
7466
  return (_ctx, _cache) => {
6542
7467
  return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$8, [
6543
7468
  __props.config.extra ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
@@ -6545,25 +7470,30 @@
6545
7470
  innerHTML: __props.config.extra,
6546
7471
  style: { "color": "rgba(0, 0, 0, 0.45)" }
6547
7472
  }, 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) => {
7473
+ !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
7474
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupListItem_default, {
6550
- key: index,
6551
- model: item,
6552
- lastValues: getLastValues(__props.lastValues?.[__props.name], Number(index)),
7475
+ key: entry.index,
7476
+ model: entry.item,
7477
+ lastValues: entry.last,
6553
7478
  "is-compare": __props.isCompare,
6554
7479
  config: __props.config,
6555
7480
  prop: __props.prop,
6556
- index: Number(index),
7481
+ index: entry.index,
6557
7482
  "label-width": __props.labelWidth,
7483
+ "label-position": __props.labelPosition,
6558
7484
  size: __props.size,
6559
7485
  disabled: __props.disabled,
6560
- "group-model": __props.model[__props.name],
7486
+ "group-model": currentList.value,
6561
7487
  onRemoveItem: removeHandler,
6562
7488
  onCopyItem: copyHandler,
6563
7489
  onSwapItem: swapHandler,
6564
7490
  onChange: changeHandler,
6565
7491
  onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
6566
- }, null, 8, [
7492
+ }, (0, vue.createSlots)({ _: 2 }, [_ctx.$slots.title ? {
7493
+ name: "title",
7494
+ fn: (0, vue.withCtx)((slotProps) => [(0, vue.renderSlot)(_ctx.$slots, "title", (0, vue.mergeProps)({ ref_for: true }, slotProps))]),
7495
+ key: "0"
7496
+ } : void 0]), 1032, [
6567
7497
  "model",
6568
7498
  "lastValues",
6569
7499
  "is-compare",
@@ -6571,12 +7501,16 @@
6571
7501
  "prop",
6572
7502
  "index",
6573
7503
  "label-width",
7504
+ "label-position",
6574
7505
  "size",
6575
7506
  "disabled",
6576
7507
  "group-model"
6577
7508
  ]);
6578
7509
  }), 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)
7510
+ !__props.isCompare && (_ctx.$slots["toggle-button"] || _ctx.$slots["add-button"]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7511
+ key: 3,
7512
+ class: (0, vue.normalizeClass)(["m-fields-group-list-footer", { "is-sticky-full": Boolean(__props.config.addButtonConfig?.sticky) }])
7513
+ }, [(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
7514
  ]);
6581
7515
  };
6582
7516
  }
@@ -6946,15 +7880,7 @@
6946
7880
  if (typeof props.config.selection === "function") return props.config.selection(mForm, { model: props.model[modelName.value] });
6947
7881
  return props.config.selection;
6948
7882
  });
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
- };
7883
+ const getProp = (index) => appendProp(props.prop, index + currentPage.value * pageSize.value);
6958
7884
  const changeHandler = (v, eventData) => {
6959
7885
  emit("change", props.model, eventData);
6960
7886
  };
@@ -7033,7 +7959,7 @@
7033
7959
  },
7034
7960
  cell: ({ $index }) => (0, vue.h)("span", $index + 1 + currentPage.value * pageSize.value)
7035
7961
  });
7036
- for (const column of props.config.items) if (column.type !== "hidden" && display$1(column.display)) {
7962
+ for (const column of props.config.items) if (isTableColumnRendered(column, display$1)) {
7037
7963
  const titleTipValue = titleTip(column.titleTip);
7038
7964
  columns.push({
7039
7965
  props: {
@@ -7049,7 +7975,7 @@
7049
7975
  disabled: props.disabled,
7050
7976
  prop: getProp($index),
7051
7977
  rules: column.rules,
7052
- config: makeConfig(column, row),
7978
+ config: makeTableColumnConfig(column, row),
7053
7979
  model: row,
7054
7980
  lastValues: lastData.value[$index],
7055
7981
  isCompare: props.isCompare,
@@ -7081,7 +8007,7 @@
7081
8007
  } };
7082
8008
  var _hoisted_3$2 = { style: { "display": "flex" } };
7083
8009
  var _hoisted_4$1 = {
7084
- key: 1,
8010
+ key: 2,
7085
8011
  class: "bottom",
7086
8012
  style: { "text-align": "right" }
7087
8013
  };
@@ -7148,12 +8074,14 @@
7148
8074
  style: { "color": "rgba(0, 0, 0, 0.45)" },
7149
8075
  innerHTML: __props.config.extra
7150
8076
  }, null, 8, _hoisted_1$7)) : (0, vue.createCommentVNode)("v-if", true),
7151
- (0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
8077
+ (0, vue.createCommentVNode)(" v-if 必须挂在 Tooltip 上:表格未就绪时 ElOnlyChild 会报 no valid child node found "),
8078
+ __props.model[modelName.value] ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTooltip), {
8079
+ key: 1,
7152
8080
  content: "拖拽可排序",
7153
8081
  placement: "left-start",
7154
8082
  disabled: __props.config.dropSort !== true || __props.config.dropSortHandle
7155
8083
  }, {
7156
- default: (0, vue.withCtx)(() => [__props.model[modelName.value] ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTable), {
8084
+ default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicTable), {
7157
8085
  ref: "tMagicTable",
7158
8086
  style: { "width": "100%" },
7159
8087
  "show-header": "",
@@ -7175,9 +8103,9 @@
7175
8103
  "max-height",
7176
8104
  "onSelect",
7177
8105
  "empty-text"
7178
- ])) : (0, vue.createCommentVNode)("v-if", true)]),
8106
+ ]))]),
7179
8107
  _: 1
7180
- }, 8, ["disabled"]),
8108
+ }, 8, ["disabled"])) : (0, vue.createCommentVNode)("v-if", true),
7181
8109
  (0, vue.renderSlot)(_ctx.$slots, "default"),
7182
8110
  (0, vue.createElementVNode)("div", _hoisted_2$3, [(0, vue.createElementVNode)("div", _hoisted_3$2, [
7183
8111
  !(0, vue.unref)(isFullscreen) ? (0, vue.renderSlot)(_ctx.$slots, "toggle-button", {}, void 0, void 0, 0) : (0, vue.createCommentVNode)("v-if", true),
@@ -7256,39 +8184,46 @@
7256
8184
  const addable = (0, vue.computed)(() => {
7257
8185
  const modelName = props.name || props.config.name || "";
7258
8186
  if (!modelName) return false;
7259
- if (!props.model[modelName]?.length) return true;
7260
- if (typeof props.config.addable === "function") return props.config.addable(mForm, {
8187
+ if (typeof props.config.addable === "function") return Boolean(props.config.addable(mForm, {
7261
8188
  model: props.model[modelName],
7262
8189
  formValue: mForm?.values,
7263
8190
  prop: props.prop,
7264
8191
  config: props.config
7265
- });
7266
- return typeof props.config.addable === "undefined" ? true : props.config.addable;
8192
+ }));
8193
+ if (!props.model[modelName]?.length) return true;
8194
+ return typeof props.config.addable === "undefined" ? true : Boolean(props.config.addable);
7267
8195
  });
8196
+ /**
8197
+ * 新增一项,返回新增后列表应有的长度;没有新增(超上限 / 被 beforeAddRow 拦下 / enum 用尽)返回 null。
8198
+ *
8199
+ * 只抛 change,不直接改 `props.model`:写回由 `MForm` 按 changeRecords 的 propPath 完成。
8200
+ * 返回长度是给调用方用的——新项要等写回后才出现在 DOM 里,靠它才能判断等到了没有。
8201
+ */
7268
8202
  const newHandler = async (row) => {
7269
- const modelName = props.name || props.config.name || "";
7270
- if (props.config.max && props.model[modelName].length >= props.config.max) {
8203
+ const modelName = `${props.name || props.config.name || ""}`;
8204
+ const list = Array.isArray(props.model[modelName]) ? props.model[modelName] : [];
8205
+ if (props.config.max && list.length >= props.config.max) {
7271
8206
  _tmagic_design.tMagicMessage.error(`最多新增配置不能超过${props.config.max}条`);
7272
- return;
8207
+ return null;
7273
8208
  }
7274
8209
  if (typeof props.config.beforeAddRow === "function") {
7275
8210
  if (!await props.config.beforeAddRow(mForm, {
7276
- model: props.model[modelName],
8211
+ model: list,
7277
8212
  formValue: mForm?.values,
7278
8213
  prop: props.prop
7279
- })) return;
8214
+ })) return null;
7280
8215
  }
7281
8216
  const columns = props.config.items;
7282
8217
  const enumValues = props.config.enum || [];
7283
8218
  let enumV = [];
7284
- const { length } = props.model[modelName];
8219
+ const { length } = list;
7285
8220
  const key = props.config.key || "id";
7286
8221
  let inputs = {};
7287
8222
  if (enumValues.length) {
7288
- if (length >= enumValues.length) return;
8223
+ if (length >= enumValues.length) return null;
7289
8224
  enumV = enumValues.filter((item) => {
7290
8225
  let i = 0;
7291
- for (; i < length; i++) if (item[key] === props.model[modelName][i][key]) break;
8226
+ for (; i < length; i++) if (item[key] === list[i][key]) break;
7292
8227
  return i === length;
7293
8228
  });
7294
8229
  if (enumV.length > 0) inputs = enumV[0];
@@ -7297,7 +8232,7 @@
7297
8232
  });
7298
8233
  else {
7299
8234
  if (typeof props.config.defaultAdd === "function") inputs = await props.config.defaultAdd(mForm, {
7300
- model: props.model[modelName],
8235
+ model: list,
7301
8236
  prop: props.prop,
7302
8237
  formValue: mForm?.values
7303
8238
  });
@@ -7307,11 +8242,13 @@
7307
8242
  initValues: inputs
7308
8243
  });
7309
8244
  }
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}`,
8245
+ applyMountValueEffects(mForm, columns, inputs);
8246
+ if (props.sortKey && length) inputs[props.sortKey] = list[length - 1][props.sortKey] - 1;
8247
+ emit("change", [...list, inputs], { changeRecords: [{
8248
+ propPath: `${props.prop}.${length}`,
7313
8249
  value: inputs
7314
8250
  }] });
8251
+ return length + 1;
7315
8252
  };
7316
8253
  return {
7317
8254
  addable,
@@ -7319,6 +8256,76 @@
7319
8256
  };
7320
8257
  };
7321
8258
  //#endregion
8259
+ //#region packages/form/src/containers/table-group-list/useScrollLastItemIntoView.ts
8260
+ var nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
8261
+ /** 滚动要用的列表根与最后一项;组件已卸载(宿主重建了整棵表单)时返回 null */
8262
+ var resolveScrollTarget = (listRef) => {
8263
+ const inst = listRef.value;
8264
+ const root = inst instanceof HTMLElement ? inst : inst?.$el;
8265
+ if (!(root instanceof HTMLElement)) return null;
8266
+ const items = root.querySelectorAll(":scope > .m-fields-group-list-item");
8267
+ const last = items[items.length - 1];
8268
+ return {
8269
+ root,
8270
+ last: last instanceof HTMLElement ? last : null,
8271
+ count: items.length
8272
+ };
8273
+ };
8274
+ /** 吸顶标题自身的高度:滚动后新项的标题不能被上一层标题压住 */
8275
+ var getStickyHeaderOffset = (item) => {
8276
+ const header = item.querySelector(":scope > .el-card__header, :scope > .t-card__header");
8277
+ if (!(header instanceof HTMLElement)) return 0;
8278
+ return Number.parseFloat(getComputedStyle(header).top) || 0;
8279
+ };
8280
+ /** 吸底「新增」按钮盖住的高度;嵌套列表的 footer 还用 bottom 给外层按钮留了位 */
8281
+ var getStickyFooterOffset = (root) => {
8282
+ const footer = root.querySelector(":scope > .m-fields-group-list-footer.is-sticky-full");
8283
+ if (!(footer instanceof HTMLElement)) return 0;
8284
+ return footer.getBoundingClientRect().height + (Number.parseFloat(getComputedStyle(footer).bottom) || 0);
8285
+ };
8286
+ /**
8287
+ * 等列表渲染出至少 `expectedCount` 项、且最后一项的 DOM 节点连续两帧不变再返回。
8288
+ *
8289
+ * 新增只抛 change,写回要经过宿主(如编辑器属性面板)的异步校验与表单值重建,
8290
+ * 新项不一定当帧就出现;期间宿主还可能自己再追加项,末尾节点会换掉。
8291
+ * 只判「节点不变」会滚到旧的最后一项上,所以先等够数量再等稳定。
8292
+ */
8293
+ var waitForStableTarget = async (listRef, expectedCount, timeout = 600) => {
8294
+ const deadline = Date.now() + timeout;
8295
+ let previous = null;
8296
+ while (Date.now() < deadline) {
8297
+ const target = resolveScrollTarget(listRef);
8298
+ if (!target) return null;
8299
+ if (target.count >= expectedCount && target.last && target.last === previous) return target;
8300
+ previous = target.last;
8301
+ await nextFrame();
8302
+ }
8303
+ return resolveScrollTarget(listRef);
8304
+ };
8305
+ /**
8306
+ * 新增后把最后一项滚进视口,避开吸顶标题与吸底按钮。
8307
+ *
8308
+ * `enabled` 为 false(未开配置、或不在 group-list 形态)时直接返回。
8309
+ */
8310
+ var useScrollLastItemIntoView = (listRef, enabled) => {
8311
+ const scrollLastItemIntoView = async (expectedCount) => {
8312
+ if (!enabled()) return;
8313
+ await (0, vue.nextTick)();
8314
+ const target = await waitForStableTarget(listRef, expectedCount);
8315
+ if (!target?.last) return;
8316
+ const { root, last } = target;
8317
+ const top = getStickyHeaderOffset(last);
8318
+ const bottom = getStickyFooterOffset(root);
8319
+ last.style.scrollMarginTop = top ? `${top}px` : "";
8320
+ last.style.scrollMarginBottom = bottom ? `${bottom}px` : "";
8321
+ last.scrollIntoView({
8322
+ behavior: "auto",
8323
+ block: "nearest"
8324
+ });
8325
+ };
8326
+ return { scrollLastItemIntoView };
8327
+ };
8328
+ //#endregion
7322
8329
  //#region packages/form/src/containers/table-group-list/TableGroupList.vue
7323
8330
  var TableGroupList_default = /* @__PURE__ */ (0, vue.defineComponent)({
7324
8331
  name: "MFormTableGroupList",
@@ -7332,6 +8339,7 @@
7332
8339
  name: {},
7333
8340
  prop: {},
7334
8341
  labelWidth: {},
8342
+ labelPosition: {},
7335
8343
  disabled: { type: Boolean },
7336
8344
  size: {},
7337
8345
  enableToggleMode: { type: Boolean },
@@ -7348,63 +8356,47 @@
7348
8356
  const props = __props;
7349
8357
  const emit = __emit;
7350
8358
  const { addable, newHandler } = useAdd(props, emit);
7351
- const isGroupListType = (type) => type === "groupList" || type === "group-list";
7352
8359
  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
- });
8360
+ const tableConfig = (0, vue.computed)(() => toTableConfig(props.config));
8361
+ const groupListConfig = (0, vue.computed)(() => toGroupListConfig(props.config));
7391
8362
  const currentConfig = (0, vue.computed)(() => displayMode.value === "table" ? tableConfig.value : groupListConfig.value);
7392
8363
  const addButtonSize = (0, vue.computed)(() => {
8364
+ if (currentConfig.value.addButtonConfig?.sticky) return "default";
7393
8365
  if (displayMode.value === "table") return "small";
7394
8366
  return props.config.enableToggleMode !== false ? "small" : "default";
7395
8367
  });
8368
+ const addButtonProps = (0, vue.computed)(() => {
8369
+ const custom = currentConfig.value.addButtonConfig?.props;
8370
+ if (custom) return {
8371
+ type: "primary",
8372
+ ...custom
8373
+ };
8374
+ if (displayMode.value === "table") return {
8375
+ type: "primary",
8376
+ plain: true
8377
+ };
8378
+ return {
8379
+ type: "primary",
8380
+ text: true
8381
+ };
8382
+ });
7396
8383
  const toggleDisplayMode = () => {
7397
8384
  displayMode.value = displayMode.value === "table" ? "groupList" : "table";
7398
8385
  };
8386
+ const tableGroupListRef = (0, vue.useTemplateRef)("tableGroupList");
8387
+ const { scrollLastItemIntoView } = useScrollLastItemIntoView(tableGroupListRef, () => Boolean(currentConfig.value.scrollLastItemIntoView) && displayMode.value === "groupList");
8388
+ const handleAdd = async () => {
8389
+ const expectedCount = await newHandler();
8390
+ if (expectedCount !== null) await scrollLastItemIntoView(expectedCount);
8391
+ };
7399
8392
  const onChange = (v, eventData) => emit("change", v, eventData);
7400
8393
  const onSelect = (...args) => emit("select", ...args);
7401
8394
  const onAddDiffCount = () => emit("addDiffCount");
7402
- const onAdd = (rows) => {
7403
- rows.forEach((row) => {
7404
- newHandler(row);
7405
- });
8395
+ const onAdd = async (rows) => {
8396
+ let expectedCount = null;
8397
+ for (const row of rows) expectedCount = await newHandler(row) ?? expectedCount;
8398
+ if (expectedCount !== null) await scrollLastItemIntoView(expectedCount);
7406
8399
  };
7407
- const tableGroupListRef = (0, vue.useTemplateRef)("tableGroupList");
7408
8400
  __expose({ toggleRowSelection: (row, selected) => tableGroupListRef.value?.toggleRowSelection?.(row, selected) });
7409
8401
  return (_ctx, _cache) => {
7410
8402
  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 +8409,7 @@
7417
8409
  "last-values": __props.lastValues,
7418
8410
  prop: __props.prop,
7419
8411
  "label-width": __props.labelWidth,
8412
+ "label-position": __props.labelPosition,
7420
8413
  "show-index": __props.showIndex,
7421
8414
  "sort-key": __props.sortKey,
7422
8415
  sort: __props.sort,
@@ -7424,39 +8417,43 @@
7424
8417
  onSelect,
7425
8418
  onAddDiffCount,
7426
8419
  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, [
8420
+ }), (0, vue.createSlots)({ _: 2 }, [
8421
+ __props.config.enableToggleMode || __props.enableToggleMode ? {
8422
+ name: "toggle-button",
8423
+ fn: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
8424
+ icon: (0, vue.unref)(_element_plus_icons_vue.Grid),
8425
+ size: "small",
8426
+ onClick: toggleDisplayMode
8427
+ }, {
8428
+ default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(displayMode.value === "table" ? "展开配置" : "切换为表格"), 1)]),
8429
+ _: 1
8430
+ }, 8, ["icon"])]),
8431
+ key: "0"
8432
+ } : void 0,
8433
+ (0, vue.unref)(addable) ? {
8434
+ name: "add-button",
8435
+ fn: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), (0, vue.mergeProps)({
8436
+ class: displayMode.value === "table" ? "m-form-table-add-button" : "",
8437
+ size: addButtonSize.value,
8438
+ icon: (0, vue.unref)(_element_plus_icons_vue.Plus),
8439
+ disabled: __props.disabled
8440
+ }, addButtonProps.value, { onClick: handleAdd }), {
8441
+ default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(currentConfig.value.addButtonConfig?.text || (displayMode.value === "table" ? `新增一行${groupListConfig.value.titlePrefix || ""}` : `新增${groupListConfig.value.titlePrefix || ""}`)), 1)]),
8442
+ _: 1
8443
+ }, 16, [
8444
+ "class",
8445
+ "size",
8446
+ "icon",
8447
+ "disabled"
8448
+ ])]),
8449
+ key: "1"
8450
+ } : void 0,
8451
+ _ctx.$slots.title ? {
8452
+ name: "title",
8453
+ fn: (0, vue.withCtx)((slotProps) => [(0, vue.renderSlot)(_ctx.$slots, "title", (0, vue.normalizeProps)((0, vue.guardReactiveProps)(slotProps)))]),
8454
+ key: "2"
8455
+ } : void 0
8456
+ ]), 1040, [
7460
8457
  "model",
7461
8458
  "name",
7462
8459
  "config",
@@ -7466,6 +8463,7 @@
7466
8463
  "last-values",
7467
8464
  "prop",
7468
8465
  "label-width",
8466
+ "label-position",
7469
8467
  "show-index",
7470
8468
  "sort-key",
7471
8469
  "sort"
@@ -7784,13 +8782,13 @@
7784
8782
  }
7785
8783
  });
7786
8784
  //#endregion
7787
- //#region packages/form/src/fields/NumberRange.vue?vue&type=script&setup=true&lang.ts
8785
+ //#region packages/form/src/fields/NumberRange/Index.vue?vue&type=script&setup=true&lang.ts
7788
8786
  var _hoisted_1$5 = { class: "m-fields-number-range" };
7789
8787
  //#endregion
7790
- //#region packages/form/src/fields/NumberRange.vue
7791
- var NumberRange_default = /* @__PURE__ */ (0, vue.defineComponent)({
8788
+ //#region packages/form/src/fields/NumberRange/Index.vue
8789
+ var Index_default$5 = /* @__PURE__ */ (0, vue.defineComponent)({
7792
8790
  name: "MFormNumberRange",
7793
- __name: "NumberRange",
8791
+ __name: "Index",
7794
8792
  props: {
7795
8793
  config: {},
7796
8794
  model: {},
@@ -7820,7 +8818,6 @@
7820
8818
  deep: true
7821
8819
  });
7822
8820
  useAddField(props.prop);
7823
- if (!Array.isArray(props.model[props.name])) props.model[props.name] = [];
7824
8821
  const minChangeHandler = (v) => {
7825
8822
  emit("change", [Number(v), props.model[props.name][1]]);
7826
8823
  };
@@ -7920,10 +8917,10 @@
7920
8917
  }
7921
8918
  });
7922
8919
  //#endregion
7923
- //#region packages/form/src/fields/Date.vue
7924
- var Date_default = /* @__PURE__ */ (0, vue.defineComponent)({
8920
+ //#region packages/form/src/fields/Date/Index.vue
8921
+ var Index_default$1 = /* @__PURE__ */ (0, vue.defineComponent)({
7925
8922
  name: "MFormDate",
7926
- __name: "Date",
8923
+ __name: "Index",
7927
8924
  props: {
7928
8925
  config: {},
7929
8926
  model: {},
@@ -7944,7 +8941,6 @@
7944
8941
  const props = __props;
7945
8942
  const emit = __emit;
7946
8943
  useAddField(props.prop);
7947
- props.model[props.name] = datetimeFormatter(props.model[props.name], "", props.config.valueFormat || "YYYY/MM/DD");
7948
8944
  const changeHandler = (v) => {
7949
8945
  emit("change", v);
7950
8946
  };
@@ -7970,10 +8966,10 @@
7970
8966
  }
7971
8967
  });
7972
8968
  //#endregion
7973
- //#region packages/form/src/fields/DateTime.vue
7974
- var DateTime_default = /* @__PURE__ */ (0, vue.defineComponent)({
8969
+ //#region packages/form/src/fields/DateTime/Index.vue
8970
+ var Index_default$2 = /* @__PURE__ */ (0, vue.defineComponent)({
7975
8971
  name: "MFormDateTime",
7976
- __name: "DateTime",
8972
+ __name: "Index",
7977
8973
  props: {
7978
8974
  config: {},
7979
8975
  model: {},
@@ -7994,9 +8990,6 @@
7994
8990
  const props = __props;
7995
8991
  const emit = __emit;
7996
8992
  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
8993
  const changeHandler = (v) => {
8001
8994
  emit("change", v);
8002
8995
  };
@@ -8426,10 +9419,10 @@
8426
9419
  }
8427
9420
  });
8428
9421
  //#endregion
8429
- //#region packages/form/src/fields/CheckboxGroup.vue
8430
- var CheckboxGroup_default = /* @__PURE__ */ (0, vue.defineComponent)({
9422
+ //#region packages/form/src/fields/CheckboxGroup/Index.vue
9423
+ var Index_default = /* @__PURE__ */ (0, vue.defineComponent)({
8431
9424
  name: "MFormCheckGroup",
8432
- __name: "CheckboxGroup",
9425
+ __name: "Index",
8433
9426
  props: {
8434
9427
  config: {},
8435
9428
  model: {},
@@ -8450,7 +9443,6 @@
8450
9443
  const props = __props;
8451
9444
  const emit = __emit;
8452
9445
  useAddField(props.prop);
8453
- if (props.model && !props.model[props.name]) props.model[props.name] = [];
8454
9446
  const changeHandler = (v) => {
8455
9447
  emit("change", v);
8456
9448
  };
@@ -8571,13 +9563,13 @@
8571
9563
  }
8572
9564
  });
8573
9565
  //#endregion
8574
- //#region packages/form/src/fields/Display.vue?vue&type=script&setup=true&lang.ts
9566
+ //#region packages/form/src/fields/Display/Index.vue?vue&type=script&setup=true&lang.ts
8575
9567
  var _hoisted_1$3 = { key: 0 };
8576
9568
  //#endregion
8577
- //#region packages/form/src/fields/Display.vue
8578
- var Display_default = /* @__PURE__ */ (0, vue.defineComponent)({
9569
+ //#region packages/form/src/fields/Display/Index.vue
9570
+ var Index_default$3 = /* @__PURE__ */ (0, vue.defineComponent)({
8579
9571
  name: "MFormDisplay",
8580
- __name: "Display",
9572
+ __name: "Index",
8581
9573
  props: {
8582
9574
  config: {},
8583
9575
  model: {},
@@ -8596,7 +9588,6 @@
8596
9588
  setup(__props) {
8597
9589
  const props = __props;
8598
9590
  const mForm = (0, vue.inject)("mForm");
8599
- if (props.config.initValue && props.model) props.model[props.name] = props.config.initValue;
8600
9591
  const text = (0, vue.computed)(() => {
8601
9592
  if (props.config.displayText) return filterFunction(mForm, props.config.displayText, props);
8602
9593
  return props.model[props.name];
@@ -8927,7 +9918,10 @@
8927
9918
  prop: props.prop,
8928
9919
  postOptions
8929
9920
  });
8930
- let initData = (0, _tmagic_utils.getValueByKeyPath)(initRoot || root, res);
9921
+ let initData;
9922
+ try {
9923
+ initData = (0, _tmagic_utils.getValueByKeyPath)(initRoot || root, res);
9924
+ } catch {}
8931
9925
  if (initData) {
8932
9926
  if (!Array.isArray(initData)) initData = [initData];
8933
9927
  if (typeof option.item === "function") options = option.item(initData);
@@ -8958,8 +9952,12 @@
8958
9952
  const v = props.model[props.name];
8959
9953
  if (Array.isArray(v) ? !v.length : typeof v === "undefined") return;
8960
9954
  if (typeof v === "undefined" || hasOption(v)) return;
8961
- const data = await getInitOption();
8962
- setOptions(data);
9955
+ try {
9956
+ const data = await getInitOption();
9957
+ setOptions(data);
9958
+ } catch {
9959
+ setOptions([]);
9960
+ }
8963
9961
  };
8964
9962
  (0, vue.watch)(() => props.model?.[props.name], () => {
8965
9963
  initOption();
@@ -9054,7 +10052,7 @@
9054
10052
  }), 128)) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(options.value, (option) => {
9055
10053
  return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)((0, vue.unref)(optionComponent)?.component || "el-option"), (0, vue.mergeProps)({
9056
10054
  class: "tmagic-design-option",
9057
- key: __props.config.valueKey ? option.value[__props.config.valueKey] : option.value
10055
+ key: __props.config.valueKey ? option.value?.[__props.config.valueKey] : option.value
9058
10056
  }, { ref_for: true }, (0, vue.unref)(optionComponent)?.props({
9059
10057
  label: option.text,
9060
10058
  value: option.value,
@@ -9204,13 +10202,50 @@
9204
10202
  }
9205
10203
  });
9206
10204
  //#endregion
9207
- //#region packages/form/src/fields/DynamicField.vue?vue&type=script&setup=true&lang.ts
10205
+ //#region packages/form/src/fields/DynamicField/effect.ts
10206
+ /**
10207
+ * 遍历动态字段列表,按「原值为空且声明了 defaultValue 则取默认值」求出每个字段当前的值。
10208
+ *
10209
+ * `isDefaultApplied` 为真表示该值来自 `defaultValue`,需要写回表单值
10210
+ * (组件通过 emit change 写回,初始化写入直接写 `values`)。
10211
+ */
10212
+ var eachDynamicField = (fields, model, onField) => {
10213
+ for (const field of fields) {
10214
+ if (typeof field !== "object" || field?.name === void 0) continue;
10215
+ let value = model?.[field.name] || "";
10216
+ let isDefaultApplied = false;
10217
+ if (!value && field.defaultValue !== void 0) {
10218
+ value = field.defaultValue;
10219
+ isDefaultApplied = true;
10220
+ }
10221
+ onField(field, value, isDefaultApplied);
10222
+ }
10223
+ };
10224
+ /**
10225
+ * 同步 `returnFields` 的 defaultValue 按 prop 写入值根对象。
10226
+ *
10227
+ * 该组件读取的是同级 model,但写入走 Container 的 modifyKey 分支,落在 `${prop}.${key}`,
10228
+ * 这里保持与渲染一致(含这层不对称),避免两条链路产出不同的值。
10229
+ * 异步 returnFields 由组件挂载后的 watch 负责,此处不等待。
10230
+ */
10231
+ var effect$5 = ({ config, model, prop, values }) => {
10232
+ const { returnFields, dynamicKey } = config;
10233
+ if (typeof returnFields !== "function" || !model) return;
10234
+ if (model[dynamicKey] === "") return;
10235
+ const result = returnFields(config, model, getConfig("request"));
10236
+ if (!result || typeof result.then === "function" || !Array.isArray(result)) return;
10237
+ eachDynamicField(result, model, (field, value, isDefaultApplied) => {
10238
+ if (isDefaultApplied) (0, _tmagic_utils.setValueByKeyPath)(`${prop}.${field.name}`, value, values || model);
10239
+ });
10240
+ };
10241
+ //#endregion
10242
+ //#region packages/form/src/fields/DynamicField/Index.vue?vue&type=script&setup=true&lang.ts
9208
10243
  var _hoisted_1 = { class: "m-fields-dynamic-field" };
9209
10244
  //#endregion
9210
- //#region packages/form/src/fields/DynamicField.vue
9211
- var DynamicField_default = /* @__PURE__ */ (0, vue.defineComponent)({
10245
+ //#region packages/form/src/fields/DynamicField/Index.vue
10246
+ var Index_default$4 = /* @__PURE__ */ (0, vue.defineComponent)({
9212
10247
  name: "MFormDynamicField",
9213
- __name: "DynamicField",
10248
+ __name: "Index",
9214
10249
  props: {
9215
10250
  config: {},
9216
10251
  model: {},
@@ -9248,15 +10283,10 @@
9248
10283
  const fields = await props.config.returnFields(props.config, props.model, request);
9249
10284
  fieldMap.value = {};
9250
10285
  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 || "";
10286
+ eachDynamicField(fields, props.model, (field, value, isDefaultApplied) => {
10287
+ if (isDefaultApplied) emit("change", value, { modifyKey: field.name });
10288
+ fieldMap.value[field.name] = value;
10289
+ fieldLabelMap.value[field.name] = field.label || "";
9260
10290
  });
9261
10291
  };
9262
10292
  const unwatch = (0, vue.watch)(() => props.model?.[props.config.dynamicKey], (val) => {
@@ -9295,146 +10325,245 @@
9295
10325
  }
9296
10326
  });
9297
10327
  //#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
- };
10328
+ //#region packages/form/src/fields/CheckboxGroup/effect.ts
10329
+ /** 空值初始化为空数组。 */
10330
+ var effect$4 = ({ config, model }) => {
10331
+ const { name } = config;
10332
+ if (model && !model[name]) model[name] = [];
10333
+ };
10334
+ //#endregion
10335
+ //#region packages/form/src/fields/Date/effect.ts
10336
+ /** 按 `valueFormat` 归一化日期值。 */
10337
+ var effect$3 = ({ config, model }) => {
10338
+ if (!model) return;
10339
+ const { name, valueFormat } = config;
10340
+ model[name] = datetimeFormatter(model[name], "", valueFormat || "YYYY/MM/DD");
10341
+ };
10342
+ //#endregion
10343
+ //#region packages/form/src/fields/DateTime/effect.ts
10344
+ /** 按 `valueFormat` 归一化日期时间值,空值与非法值统一为空字符串。 */
10345
+ var effect$2 = ({ config, model }) => {
10346
+ if (!model) return;
10347
+ const { name, valueFormat } = config;
10348
+ const value = model[name]?.toString();
10349
+ if (!value || value === "Invalid Date") {
10350
+ model[name] = "";
10351
+ return;
9376
10352
  }
9377
- });
10353
+ model[name] = datetimeFormatter(model[name], "", valueFormat || "YYYY/MM/DD HH:mm:ss");
10354
+ };
10355
+ //#endregion
10356
+ //#region packages/form/src/fields/Display/effect.ts
10357
+ /** 把 `initValue` 写入 model。 */
10358
+ var effect$1 = ({ config, model }) => {
10359
+ const { initValue, name } = config;
10360
+ if (initValue && model) model[name] = initValue;
10361
+ };
10362
+ //#endregion
10363
+ //#region packages/form/src/fields/NumberRange/effect.ts
10364
+ /** 值不是数组时修正为空数组。 */
10365
+ var effect = ({ config, model }) => {
10366
+ const { name } = config;
10367
+ if (model && !Array.isArray(model[name])) model[name] = [];
10368
+ };
10369
+ //#endregion
10370
+ //#region packages/form/src/utils/builtInFields.ts
10371
+ /**
10372
+ * 内置字段的无渲染登记表(不含 Vue 组件)。
10373
+ *
10374
+ * 本模块只导出数据,不登记。调用方自行 `registerBuiltInFields(builtInFields)`,
10375
+ * 或 `app.use(MagicForm)`(install 里会登记叠上 Vue 组件后的表)。
10376
+ */
10377
+ var builtInFields = {
10378
+ text: {},
10379
+ "img-upload": {},
10380
+ number: {},
10381
+ "number-range": { effect },
10382
+ textarea: {},
10383
+ hidden: {},
10384
+ date: { effect: effect$3 },
10385
+ datetime: { effect: effect$2 },
10386
+ daterange: {},
10387
+ timerange: {},
10388
+ time: {},
10389
+ checkbox: {},
10390
+ switch: {},
10391
+ "color-picker": {},
10392
+ "checkbox-group": { effect: effect$4 },
10393
+ "radio-group": {},
10394
+ display: { effect: effect$1 },
10395
+ link: {},
10396
+ select: {},
10397
+ cascader: {},
10398
+ "dynamic-field": { effect: effect$5 },
10399
+ component: {},
10400
+ tab: { walk: expandTab },
10401
+ row: { walk: expandRow },
10402
+ "flex-layout": { walk: expandRow },
10403
+ fieldset: { walk: expandFieldset },
10404
+ panel: { walk: expandPanel },
10405
+ step: { walk: expandStep },
10406
+ table: { walk: expandTableGroupList },
10407
+ "group-list": { walk: expandTableGroupList },
10408
+ "table-group-list": { walk: expandTableGroupList }
10409
+ };
9378
10410
  //#endregion
9379
10411
  //#region packages/form/src/plugin.ts
10412
+ var builtInFieldVue = {
10413
+ text: { component: Text_default },
10414
+ "img-upload": { component: Text_default },
10415
+ number: { component: Number_default },
10416
+ "number-range": { component: Index_default$5 },
10417
+ textarea: { component: Textarea_default },
10418
+ hidden: { component: Hidden_default },
10419
+ date: { component: Index_default$1 },
10420
+ datetime: { component: Index_default$2 },
10421
+ daterange: { component: Daterange_default },
10422
+ timerange: { component: Timerange_default },
10423
+ time: { component: Time_default },
10424
+ checkbox: { component: Checkbox_default },
10425
+ switch: { component: Switch_default },
10426
+ "color-picker": { component: ColorPicker_default },
10427
+ "checkbox-group": { component: Index_default },
10428
+ "radio-group": { component: RadioGroup_default },
10429
+ display: { component: Index_default$3 },
10430
+ link: { component: Link_default },
10431
+ select: { component: Select_default },
10432
+ cascader: { component: Cascader_default },
10433
+ "dynamic-field": { component: Index_default$4 },
10434
+ container: { container: Container_default },
10435
+ tab: { container: Tabs_default },
10436
+ row: { container: Row_default },
10437
+ "flex-layout": { container: FlexLayout_default },
10438
+ fieldset: { container: Fieldset_default },
10439
+ panel: { container: Panel_default },
10440
+ step: { container: /* @__PURE__ */ (0, vue.defineComponent)({
10441
+ name: "MFormStep",
10442
+ __name: "Step",
10443
+ props: {
10444
+ model: {},
10445
+ name: {},
10446
+ lastValues: {},
10447
+ isCompare: { type: Boolean },
10448
+ config: {},
10449
+ stepActive: { default: 1 },
10450
+ labelWidth: {},
10451
+ labelPosition: {},
10452
+ size: {},
10453
+ disabled: { type: Boolean }
10454
+ },
10455
+ emits: ["change", "addDiffCount"],
10456
+ setup(__props, { emit: __emit }) {
10457
+ const props = __props;
10458
+ const emit = __emit;
10459
+ const mForm = (0, vue.inject)("mForm");
10460
+ const active = (0, vue.ref)(1);
10461
+ (0, vue.watchEffect)(() => {
10462
+ active.value = props.stepActive;
10463
+ });
10464
+ const stepClick = (index) => {
10465
+ active.value = index + 1;
10466
+ mForm?.$emit("update:stepActive", active.value);
10467
+ };
10468
+ const changeHandler = (v, eventData) => {
10469
+ emit("change", props.model, eventData);
10470
+ };
10471
+ const onAddDiffCount = () => emit("addDiffCount");
10472
+ return (_ctx, _cache) => {
10473
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", null, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicSteps), {
10474
+ active: active.value,
10475
+ "align-center": "",
10476
+ space: __props.config.space
10477
+ }, {
10478
+ default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (item, index) => {
10479
+ return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_tmagic_design.TMagicStep), {
10480
+ key: item.__key,
10481
+ title: item.title,
10482
+ active: active.value,
10483
+ onClick: ($event) => stepClick(index)
10484
+ }, null, 8, [
10485
+ "title",
10486
+ "active",
10487
+ "onClick"
10488
+ ]);
10489
+ }), 128))]),
10490
+ _: 1
10491
+ }, 8, ["active", "space"]), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.config.items, (step, index) => {
10492
+ 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) => {
10493
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [item ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
10494
+ key: item[(0, vue.unref)(mForm)?.keyProp || "__key"],
10495
+ config: item,
10496
+ model: step.name ? __props.model[step.name] : __props.model,
10497
+ lastValues: step.name ? __props.lastValues[step.name] : __props.lastValues,
10498
+ "is-compare": __props.isCompare,
10499
+ prop: `${step.name}`,
10500
+ size: __props.size,
10501
+ disabled: __props.disabled,
10502
+ "label-width": __props.config.labelWidth || __props.labelWidth,
10503
+ "label-position": __props.config.labelPosition || __props.labelPosition,
10504
+ onChange: changeHandler,
10505
+ onAddDiffCount: _cache[0] || (_cache[0] = ($event) => onAddDiffCount())
10506
+ }, null, 8, [
10507
+ "config",
10508
+ "model",
10509
+ "lastValues",
10510
+ "is-compare",
10511
+ "prop",
10512
+ "size",
10513
+ "disabled",
10514
+ "label-width",
10515
+ "label-position"
10516
+ ])), [[vue.vShow, active.value - 1 === index]]) : (0, vue.createCommentVNode)("v-if", true)], 64);
10517
+ }), 256))], 64);
10518
+ }), 256))]);
10519
+ };
10520
+ }
10521
+ }) },
10522
+ table: { container: TableGroupList_default },
10523
+ "group-list": { container: TableGroupList_default },
10524
+ "table-group-list": { container: TableGroupList_default }
10525
+ };
9380
10526
  var defaultInstallOpt = {};
9381
- var plugin_default = { install(app, opt = {}) {
9382
- const option = Object.assign(defaultInstallOpt, opt);
10527
+ var plugin_default = {
10528
+ /**
10529
+ * 安装 `@tmagic/form`:登记内置字段、挂载 `m-form` / `m-form-dialog` / `m-form-drawer`。
10530
+ *
10531
+ * @param app - Vue 应用
10532
+ * @param [opt] - 安装选项
10533
+ */
10534
+ install(app, opt = {}) {
10535
+ const option = {
10536
+ ...defaultInstallOpt,
10537
+ ...opt
10538
+ };
9383
10539
  app.config.globalProperties.$MAGIC_FORM = option;
9384
10540
  setConfig(option);
9385
- if (option.typeMatchRules) registerTypeMatchRules(option.typeMatchRules);
10541
+ registerBuiltInFields(builtInFields);
10542
+ registerBuiltInFields(builtInFieldVue, app);
10543
+ if (option.fields) registerFields(option.fields, app);
9386
10544
  app.component("m-form", Form_default);
9387
10545
  app.component("m-form-dialog", FormDialog_default);
9388
10546
  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
10547
  } };
9420
10548
  //#endregion
9421
10549
  //#region packages/form/src/index.ts
9422
10550
  var createForm = (config) => config;
9423
10551
  //#endregion
10552
+ exports.FORM_CONTEXT_KEY = FORM_CONTEXT_KEY;
9424
10553
  exports.FORM_DIFF_CONFIG_KEY = FORM_DIFF_CONFIG_KEY;
9425
- exports.FORM_SILENT_MODE_KEY = FORM_SILENT_MODE_KEY;
9426
10554
  exports.FORM_TYPE_MATCH_VALID_KEY = FORM_TYPE_MATCH_VALID_KEY;
10555
+ exports.FieldInnerConfigError = FieldInnerConfigError;
9427
10556
  exports.MAX_SUGGESTION_OPTIONS = MAX_SUGGESTION_OPTIONS;
9428
10557
  exports.MCascader = Cascader_default;
9429
10558
  exports.MCheckbox = Checkbox_default;
9430
- exports.MCheckboxGroup = CheckboxGroup_default;
10559
+ exports.MCheckboxGroup = Index_default;
9431
10560
  exports.MColorPicker = ColorPicker_default;
9432
10561
  exports.MContainer = Container_default;
9433
- exports.MDate = Date_default;
9434
- exports.MDateTime = DateTime_default;
10562
+ exports.MDate = Index_default$1;
10563
+ exports.MDateTime = Index_default$2;
9435
10564
  exports.MDaterange = Daterange_default;
9436
- exports.MDisplay = Display_default;
9437
- exports.MDynamicField = DynamicField_default;
10565
+ exports.MDisplay = Index_default$3;
10566
+ exports.MDynamicField = Index_default$4;
9438
10567
  exports.MFieldset = Fieldset_default;
9439
10568
  exports.MFlexLayout = FlexLayout_default;
9440
10569
  exports.MForm = Form_default;
@@ -9445,7 +10574,7 @@
9445
10574
  exports.MHidden = Hidden_default;
9446
10575
  exports.MLink = Link_default;
9447
10576
  exports.MNumber = Number_default;
9448
- exports.MNumberRange = NumberRange_default;
10577
+ exports.MNumberRange = Index_default$5;
9449
10578
  exports.MPanel = Panel_default;
9450
10579
  exports.MRadioGroup = RadioGroup_default;
9451
10580
  exports.MRow = Row_default;
@@ -9459,37 +10588,52 @@
9459
10588
  exports.MTime = Time_default;
9460
10589
  exports.MTimerange = Timerange_default;
9461
10590
  exports.adaptFormValidator = adaptFormValidator;
9462
- exports.applyExtendState = applyExtendState;
9463
- exports.clearSilentLeafFieldTypes = clearSilentLeafFieldTypes;
10591
+ exports.appendProp = appendProp;
10592
+ exports.applyMountValueEffects = applyMountValueEffects;
10593
+ exports.builtInFields = builtInFields;
10594
+ exports.clearFields = clearFields;
9464
10595
  exports.clearTypeMatchRules = clearTypeMatchRules;
10596
+ exports.collectValidatableFields = collectValidatableFields;
9465
10597
  exports.createForm = createForm;
10598
+ exports.createFormStateBase = createFormStateBase;
10599
+ exports.createFormStateProxy = createFormStateProxy;
10600
+ exports.createHeadlessFormState = createHeadlessFormState;
9466
10601
  exports.createObjectProp = createObjectProp;
9467
10602
  exports.createValues = createValues;
9468
10603
  exports.datetimeFormatter = datetimeFormatter;
9469
10604
  exports.default = plugin_default;
9470
- exports.deleteFormField = deleteField;
9471
10605
  exports.deleteTypeMatchRule = deleteTypeMatchRule;
9472
10606
  exports.display = display;
9473
10607
  exports.filterFunction = filterFunction;
10608
+ exports.formatValidateError = formatValidateError;
9474
10609
  exports.getDataByPage = getDataByPage;
9475
10610
  exports.getFormField = getField;
10611
+ exports.getItemProp = getItemProp;
10612
+ exports.getNativeRules = getNativeRules;
9476
10613
  exports.getRules = getRules;
9477
- exports.getSilentLeafFieldTypes = getSilentLeafFieldTypes;
10614
+ exports.getTextByName = getTextByName;
9478
10615
  exports.getTypeMatchRule = getTypeMatchRule;
9479
10616
  exports.initValue = initValue;
10617
+ exports.isFieldInnerConfigError = isFieldInnerConfigError;
10618
+ exports.isLeafFieldType = isLeafFieldType;
10619
+ exports.isValidName = isValidName;
10620
+ exports.mergeFieldOptions = mergeFieldOptions;
10621
+ exports.mergeFormContexts = mergeFormContexts;
9480
10622
  exports.optionSuggestion = optionSuggestion;
9481
- exports.registerFormField = registerField;
9482
- exports.registerSilentLeafFieldTypes = registerSilentLeafFieldTypes;
9483
- exports.registerTypeMatchRule = registerTypeMatchRule;
9484
- exports.registerTypeMatchRules = registerTypeMatchRules;
10623
+ exports.registerBuiltInFields = registerBuiltInFields;
10624
+ exports.registerField = registerField;
10625
+ exports.registerFields = registerFields;
10626
+ exports.resolveItemType = resolveItemType;
9485
10627
  exports.sortArray = sortArray;
9486
10628
  exports.sortChange = sortChange;
9487
10629
  exports.stringifyExampleValue = stringifyExampleValue;
9488
10630
  exports.stripTabItemsLazy = stripTabItemsLazy;
9489
10631
  exports.submitForm = submitForm;
10632
+ exports.unregisterField = unregisterField;
9490
10633
  exports.useAddField = useAddField;
9491
10634
  exports.validateForm = validateForm;
9492
10635
  exports.validateTypeMatch = validateTypeMatch;
10636
+ exports.validateValues = validateValues;
9493
10637
  var _tmagic_form_schema = require("@tmagic/form-schema");
9494
10638
  Object.keys(_tmagic_form_schema).forEach(function(k) {
9495
10639
  if (k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {