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

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.
@@ -91,8 +91,10 @@
91
91
  var unmasked = true;
92
92
  } catch (e) {}
93
93
  var result = nativeObjectToString$1.call(value);
94
- if (unmasked) if (isOwn) value[symToStringTag$1] = tag;
95
- else delete value[symToStringTag$1];
94
+ if (unmasked) {
95
+ if (isOwn) value[symToStringTag$1] = tag;
96
+ else delete value[symToStringTag$1];
97
+ }
96
98
  return result;
97
99
  }
98
100
  //#endregion
@@ -2049,6 +2051,14 @@
2049
2051
  var extraTypeMatchRules = /* @__PURE__ */ new Map();
2050
2052
  var builtInTypeMatchRules = /* @__PURE__ */ new Map();
2051
2053
  var isPromise = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
2054
+ /**
2055
+ * 同步校验无法等待 Promise。调用方会丢掉返回值,这里挂上空 handler,避免变成未捕获 rejection。
2056
+ */
2057
+ var ignorePromise = (value) => {
2058
+ if (!isPromise(value)) return false;
2059
+ value.then(() => {}, () => {});
2060
+ return true;
2061
+ };
2052
2062
  /** 注册或覆盖某个字段 type 的 typeMatch 校验规则。`builtIn` 登记不受 `clearTypeMatchRules` / `deleteTypeMatchRule` 影响。 */
2053
2063
  var registerTypeMatchRule = (type, validator, builtIn = false) => {
2054
2064
  (builtIn ? builtInTypeMatchRules : extraTypeMatchRules).set((0, _tmagic_utils.toLine)(type), validator);
@@ -2066,16 +2076,20 @@
2066
2076
  };
2067
2077
  /** 本地解析配置函数,避免与 form.ts 循环依赖 */
2068
2078
  var resolveConfig = (mForm, config, props) => {
2069
- if (typeof config === "function") return config(mForm, {
2070
- values: (0, vue.readonly)(mForm?.initValues || {}),
2071
- model: (0, vue.readonly)(props.model),
2072
- parent: (0, vue.readonly)(mForm?.parentValues || {}),
2073
- formValue: (0, vue.readonly)(mForm?.values || props.model),
2074
- prop: props.prop,
2075
- config: props.config,
2076
- index: props.index,
2077
- getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
2078
- });
2079
+ if (typeof config === "function") {
2080
+ const formValue = (0, vue.readonly)(mForm?.values || props.model);
2081
+ return config(mForm, {
2082
+ values: (0, vue.readonly)(mForm?.initValues || {}),
2083
+ model: (0, vue.readonly)(props.model),
2084
+ parent: (0, vue.readonly)(mForm?.parentValues || {}),
2085
+ formValue,
2086
+ formValues: formValue,
2087
+ prop: props.prop,
2088
+ config: props.config,
2089
+ index: props.index,
2090
+ getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
2091
+ });
2092
+ }
2079
2093
  return config;
2080
2094
  };
2081
2095
  var SKIP_TYPES = /* @__PURE__ */ new Set([
@@ -2167,7 +2181,7 @@
2167
2181
  const { defaultValue } = props.config || {};
2168
2182
  if (typeof defaultValue === "undefined" || defaultValue === "undefined") return void 0;
2169
2183
  const resolvedDefaultValue = resolveConfig(mForm, defaultValue, props);
2170
- if (isPromise(resolvedDefaultValue)) return;
2184
+ if (ignorePromise(resolvedDefaultValue)) return;
2171
2185
  return resolvedDefaultValue;
2172
2186
  };
2173
2187
  var isNumberValue = (value) => typeof value === "number" && !Number.isNaN(value);
@@ -2244,9 +2258,21 @@
2244
2258
  });
2245
2259
  return values;
2246
2260
  };
2247
- var resolveOptions = (props) => {
2248
- const { options } = props.config || {};
2249
- return Array.isArray(options) ? options : [];
2261
+ /**
2262
+ * 解析字段 options:静态数组原样返回;函数型与 `display` / `type` 一样当场求值。
2263
+ *
2264
+ * 函数返回 Promise、非数组或抛错时当作「还没有可选项」,交由调用方按空 options 跳过枚举。
2265
+ * `remote` / `allowCreate` 仍由 `validateSelectValue` 单独放行,不在这里区分。
2266
+ */
2267
+ var resolveOptions = (mForm, props) => {
2268
+ let resolved;
2269
+ try {
2270
+ resolved = resolveConfig(mForm, props.config?.options, props);
2271
+ } catch {
2272
+ return [];
2273
+ }
2274
+ if (ignorePromise(resolved) || !Array.isArray(resolved)) return [];
2275
+ return resolved;
2250
2276
  };
2251
2277
  var includesOptionValue = (optionValues, value) => optionValues.some((item) => Object.is(item, value));
2252
2278
  var collectCascaderLeafValues = (options, result = []) => {
@@ -2308,19 +2334,18 @@
2308
2334
  if (typeof value === "object") return defaultMessage(message, `${value} 类型不合法`, optionExampleSuggestion(optionValues, "\"文本内容\" 或 123", false));
2309
2335
  return;
2310
2336
  }
2311
- const isStaticOptions = Array.isArray(config.options);
2312
2337
  if (config.multiple) {
2313
2338
  if (!Array.isArray(value)) return defaultMessage(message, `${value} 类型应为数组`, optionExampleSuggestion(optionValues, "[\"选项1\", \"选项2\"]", true));
2314
- if (isStaticOptions && value.some((item) => !includesOptionValue(optionValues, item))) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
2339
+ if (value.some((item) => !includesOptionValue(optionValues, item))) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
2315
2340
  return;
2316
2341
  }
2317
- if (isStaticOptions && !includesOptionValue(optionValues, value)) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
2342
+ if (!includesOptionValue(optionValues, value)) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
2318
2343
  };
2319
2344
  var validateCascaderValue = (value, config, props, mForm, message) => {
2320
- const options = resolveOptions(props);
2345
+ const options = resolveOptions(mForm, props);
2321
2346
  if (!options.length) return;
2322
2347
  const valueSeparator = resolveConfig(mForm, config.valueSeparator, props);
2323
- if (isPromise(valueSeparator)) return;
2348
+ if (ignorePromise(valueSeparator)) return;
2324
2349
  const emitPath = config.emitPath !== false;
2325
2350
  const multiple = Boolean(config.multiple);
2326
2351
  const cascaderExample = (fallbackExample) => cascaderExampleSuggestion(options, config, valueSeparator, fallbackExample);
@@ -2377,15 +2402,15 @@
2377
2402
  if (!Object.is(value, activeValue) && !Object.is(value, inactiveValue)) return defaultMessage(message, `${value} 不在合法开关值中`, toggleSuggestion(activeValue, inactiveValue));
2378
2403
  return;
2379
2404
  }
2380
- if (fieldType === "select") return validateSelectValue(value, config, flattenSelectOptions(resolveOptions(props)), message);
2405
+ if (fieldType === "select") return validateSelectValue(value, config, flattenSelectOptions(resolveOptions(mForm, props)), message);
2381
2406
  if (fieldType === "radio-group") {
2382
- const optionValues = flattenSelectOptions(resolveOptions(props));
2407
+ const optionValues = flattenSelectOptions(resolveOptions(mForm, props));
2383
2408
  if (optionValues.length === 0) return;
2384
2409
  if (!includesOptionValue(optionValues, value)) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
2385
2410
  return;
2386
2411
  }
2387
2412
  if (fieldType === "checkbox-group") {
2388
- const optionValues = flattenSelectOptions(resolveOptions(props));
2413
+ const optionValues = flattenSelectOptions(resolveOptions(mForm, props));
2389
2414
  if (optionValues.length === 0) return;
2390
2415
  if (!Array.isArray(value)) return defaultMessage(message, `${value} 类型应为数组`, optionExampleSuggestion(optionValues, "[\"选项1\", \"选项2\"]", true));
2391
2416
  if (value.some((item) => !includesOptionValue(optionValues, item))) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
@@ -2412,8 +2437,14 @@
2412
2437
  if (isEmptyValue(value) || isEmptyArray(value)) return;
2413
2438
  if (!props.config?.name) return;
2414
2439
  const rawFieldType = "type" in (props.config || {}) ? props.config.type : "";
2415
- if (typeof rawFieldType !== "string" || !rawFieldType) return;
2416
- const fieldType = (0, _tmagic_utils.toLine)(rawFieldType);
2440
+ let resolvedType;
2441
+ try {
2442
+ resolvedType = resolveConfig(mForm, rawFieldType, props);
2443
+ } catch {
2444
+ return;
2445
+ }
2446
+ if (ignorePromise(resolvedType) || typeof resolvedType !== "string" || !resolvedType) return;
2447
+ const fieldType = (0, _tmagic_utils.toLine)(resolvedType);
2417
2448
  const customValidator = getTypeMatchRule(fieldType);
2418
2449
  if (customValidator) return customValidator(value, {
2419
2450
  fieldType,
@@ -2820,16 +2851,20 @@
2820
2851
  return type?.replace(/([A-Z])/g, "-$1").toLowerCase() || (config?.items ? "" : "text");
2821
2852
  };
2822
2853
  var filterFunction = (mForm, config, props) => {
2823
- if (typeof config === "function") return config(mForm, {
2824
- values: (0, vue.readonly)(mForm?.initValues || {}),
2825
- model: (0, vue.readonly)(props.model),
2826
- parent: (0, vue.readonly)(mForm?.parentValues || {}),
2827
- formValue: (0, vue.readonly)(mForm?.values || props.model),
2828
- prop: props.prop,
2829
- config: props.config,
2830
- index: props.index,
2831
- getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
2832
- });
2854
+ if (typeof config === "function") {
2855
+ const formValue = (0, vue.readonly)(mForm?.values || props.model);
2856
+ return config(mForm, {
2857
+ values: (0, vue.readonly)(mForm?.initValues || {}),
2858
+ model: (0, vue.readonly)(props.model),
2859
+ parent: (0, vue.readonly)(mForm?.parentValues || {}),
2860
+ formValue,
2861
+ formValues: formValue,
2862
+ prop: props.prop,
2863
+ config: props.config,
2864
+ index: props.index,
2865
+ getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
2866
+ });
2867
+ }
2833
2868
  return config;
2834
2869
  };
2835
2870
  var display = function(mForm, config, props) {
@@ -97,8 +97,10 @@
97
97
  var unmasked = true;
98
98
  } catch (e) {}
99
99
  var result = nativeObjectToString$1.call(value);
100
- if (unmasked) if (isOwn) value[symToStringTag$1] = tag;
101
- else delete value[symToStringTag$1];
100
+ if (unmasked) {
101
+ if (isOwn) value[symToStringTag$1] = tag;
102
+ else delete value[symToStringTag$1];
103
+ }
102
104
  return result;
103
105
  }
104
106
  //#endregion
@@ -2903,6 +2905,14 @@
2903
2905
  var extraTypeMatchRules = /* @__PURE__ */ new Map();
2904
2906
  var builtInTypeMatchRules = /* @__PURE__ */ new Map();
2905
2907
  var isPromise = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
2908
+ /**
2909
+ * 同步校验无法等待 Promise。调用方会丢掉返回值,这里挂上空 handler,避免变成未捕获 rejection。
2910
+ */
2911
+ var ignorePromise = (value) => {
2912
+ if (!isPromise(value)) return false;
2913
+ value.then(() => {}, () => {});
2914
+ return true;
2915
+ };
2906
2916
  /** 注册或覆盖某个字段 type 的 typeMatch 校验规则。`builtIn` 登记不受 `clearTypeMatchRules` / `deleteTypeMatchRule` 影响。 */
2907
2917
  var registerTypeMatchRule = (type, validator, builtIn = false) => {
2908
2918
  (builtIn ? builtInTypeMatchRules : extraTypeMatchRules).set((0, _tmagic_utils.toLine)(type), validator);
@@ -2920,16 +2930,20 @@
2920
2930
  };
2921
2931
  /** 本地解析配置函数,避免与 form.ts 循环依赖 */
2922
2932
  var resolveConfig = (mForm, config, props) => {
2923
- if (typeof config === "function") return config(mForm, {
2924
- values: (0, vue.readonly)(mForm?.initValues || {}),
2925
- model: (0, vue.readonly)(props.model),
2926
- parent: (0, vue.readonly)(mForm?.parentValues || {}),
2927
- formValue: (0, vue.readonly)(mForm?.values || props.model),
2928
- prop: props.prop,
2929
- config: props.config,
2930
- index: props.index,
2931
- getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
2932
- });
2933
+ if (typeof config === "function") {
2934
+ const formValue = (0, vue.readonly)(mForm?.values || props.model);
2935
+ return config(mForm, {
2936
+ values: (0, vue.readonly)(mForm?.initValues || {}),
2937
+ model: (0, vue.readonly)(props.model),
2938
+ parent: (0, vue.readonly)(mForm?.parentValues || {}),
2939
+ formValue,
2940
+ formValues: formValue,
2941
+ prop: props.prop,
2942
+ config: props.config,
2943
+ index: props.index,
2944
+ getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
2945
+ });
2946
+ }
2933
2947
  return config;
2934
2948
  };
2935
2949
  var SKIP_TYPES = /* @__PURE__ */ new Set([
@@ -3021,7 +3035,7 @@
3021
3035
  const { defaultValue } = props.config || {};
3022
3036
  if (typeof defaultValue === "undefined" || defaultValue === "undefined") return void 0;
3023
3037
  const resolvedDefaultValue = resolveConfig(mForm, defaultValue, props);
3024
- if (isPromise(resolvedDefaultValue)) return;
3038
+ if (ignorePromise(resolvedDefaultValue)) return;
3025
3039
  return resolvedDefaultValue;
3026
3040
  };
3027
3041
  var isNumberValue = (value) => typeof value === "number" && !Number.isNaN(value);
@@ -3098,9 +3112,21 @@
3098
3112
  });
3099
3113
  return values;
3100
3114
  };
3101
- var resolveOptions = (props) => {
3102
- const { options } = props.config || {};
3103
- return Array.isArray(options) ? options : [];
3115
+ /**
3116
+ * 解析字段 options:静态数组原样返回;函数型与 `display` / `type` 一样当场求值。
3117
+ *
3118
+ * 函数返回 Promise、非数组或抛错时当作「还没有可选项」,交由调用方按空 options 跳过枚举。
3119
+ * `remote` / `allowCreate` 仍由 `validateSelectValue` 单独放行,不在这里区分。
3120
+ */
3121
+ var resolveOptions = (mForm, props) => {
3122
+ let resolved;
3123
+ try {
3124
+ resolved = resolveConfig(mForm, props.config?.options, props);
3125
+ } catch {
3126
+ return [];
3127
+ }
3128
+ if (ignorePromise(resolved) || !Array.isArray(resolved)) return [];
3129
+ return resolved;
3104
3130
  };
3105
3131
  var includesOptionValue = (optionValues, value) => optionValues.some((item) => Object.is(item, value));
3106
3132
  var collectCascaderLeafValues = (options, result = []) => {
@@ -3162,19 +3188,18 @@
3162
3188
  if (typeof value === "object") return defaultMessage(message, `${value} 类型不合法`, optionExampleSuggestion(optionValues, "\"文本内容\" 或 123", false));
3163
3189
  return;
3164
3190
  }
3165
- const isStaticOptions = Array.isArray(config.options);
3166
3191
  if (config.multiple) {
3167
3192
  if (!Array.isArray(value)) return defaultMessage(message, `${value} 类型应为数组`, optionExampleSuggestion(optionValues, "[\"选项1\", \"选项2\"]", true));
3168
- if (isStaticOptions && value.some((item) => !includesOptionValue(optionValues, item))) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
3193
+ if (value.some((item) => !includesOptionValue(optionValues, item))) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
3169
3194
  return;
3170
3195
  }
3171
- if (isStaticOptions && !includesOptionValue(optionValues, value)) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
3196
+ if (!includesOptionValue(optionValues, value)) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
3172
3197
  };
3173
3198
  var validateCascaderValue = (value, config, props, mForm, message) => {
3174
- const options = resolveOptions(props);
3199
+ const options = resolveOptions(mForm, props);
3175
3200
  if (!options.length) return;
3176
3201
  const valueSeparator = resolveConfig(mForm, config.valueSeparator, props);
3177
- if (isPromise(valueSeparator)) return;
3202
+ if (ignorePromise(valueSeparator)) return;
3178
3203
  const emitPath = config.emitPath !== false;
3179
3204
  const multiple = Boolean(config.multiple);
3180
3205
  const cascaderExample = (fallbackExample) => cascaderExampleSuggestion(options, config, valueSeparator, fallbackExample);
@@ -3231,15 +3256,15 @@
3231
3256
  if (!Object.is(value, activeValue) && !Object.is(value, inactiveValue)) return defaultMessage(message, `${value} 不在合法开关值中`, toggleSuggestion(activeValue, inactiveValue));
3232
3257
  return;
3233
3258
  }
3234
- if (fieldType === "select") return validateSelectValue(value, config, flattenSelectOptions(resolveOptions(props)), message);
3259
+ if (fieldType === "select") return validateSelectValue(value, config, flattenSelectOptions(resolveOptions(mForm, props)), message);
3235
3260
  if (fieldType === "radio-group") {
3236
- const optionValues = flattenSelectOptions(resolveOptions(props));
3261
+ const optionValues = flattenSelectOptions(resolveOptions(mForm, props));
3237
3262
  if (optionValues.length === 0) return;
3238
3263
  if (!includesOptionValue(optionValues, value)) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
3239
3264
  return;
3240
3265
  }
3241
3266
  if (fieldType === "checkbox-group") {
3242
- const optionValues = flattenSelectOptions(resolveOptions(props));
3267
+ const optionValues = flattenSelectOptions(resolveOptions(mForm, props));
3243
3268
  if (optionValues.length === 0) return;
3244
3269
  if (!Array.isArray(value)) return defaultMessage(message, `${value} 类型应为数组`, optionExampleSuggestion(optionValues, "[\"选项1\", \"选项2\"]", true));
3245
3270
  if (value.some((item) => !includesOptionValue(optionValues, item))) return defaultMessage(message, `${value} 不在可选项中`, optionSuggestion(optionValues));
@@ -3266,8 +3291,14 @@
3266
3291
  if (isEmptyValue(value) || isEmptyArray(value)) return;
3267
3292
  if (!props.config?.name) return;
3268
3293
  const rawFieldType = "type" in (props.config || {}) ? props.config.type : "";
3269
- if (typeof rawFieldType !== "string" || !rawFieldType) return;
3270
- const fieldType = (0, _tmagic_utils.toLine)(rawFieldType);
3294
+ let resolvedType;
3295
+ try {
3296
+ resolvedType = resolveConfig(mForm, rawFieldType, props);
3297
+ } catch {
3298
+ return;
3299
+ }
3300
+ if (ignorePromise(resolvedType) || typeof resolvedType !== "string" || !resolvedType) return;
3301
+ const fieldType = (0, _tmagic_utils.toLine)(resolvedType);
3271
3302
  const customValidator = getTypeMatchRule(fieldType);
3272
3303
  if (customValidator) return customValidator(value, {
3273
3304
  fieldType,
@@ -3674,16 +3705,20 @@
3674
3705
  return type?.replace(/([A-Z])/g, "-$1").toLowerCase() || (config?.items ? "" : "text");
3675
3706
  };
3676
3707
  var filterFunction = (mForm, config, props) => {
3677
- if (typeof config === "function") return config(mForm, {
3678
- values: (0, vue.readonly)(mForm?.initValues || {}),
3679
- model: (0, vue.readonly)(props.model),
3680
- parent: (0, vue.readonly)(mForm?.parentValues || {}),
3681
- formValue: (0, vue.readonly)(mForm?.values || props.model),
3682
- prop: props.prop,
3683
- config: props.config,
3684
- index: props.index,
3685
- getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
3686
- });
3708
+ if (typeof config === "function") {
3709
+ const formValue = (0, vue.readonly)(mForm?.values || props.model);
3710
+ return config(mForm, {
3711
+ values: (0, vue.readonly)(mForm?.initValues || {}),
3712
+ model: (0, vue.readonly)(props.model),
3713
+ parent: (0, vue.readonly)(mForm?.parentValues || {}),
3714
+ formValue,
3715
+ formValues: formValue,
3716
+ prop: props.prop,
3717
+ config: props.config,
3718
+ index: props.index,
3719
+ getFormValue: (prop) => (0, _tmagic_utils.getValueByKeyPath)(prop, mForm?.values || props.model)
3720
+ });
3721
+ }
3687
3722
  return config;
3688
3723
  };
3689
3724
  var display = function(mForm, config, props) {
@@ -3795,6 +3830,24 @@
3795
3830
  */
3796
3831
  /** group-list 形态的 type(兼容驼峰与中划线两种写法) */
3797
3832
  var isGroupListType = (type) => type === "groupList" || type === "group-list";
3833
+ /**
3834
+ * 归一成带单位的 CSS 长度。
3835
+ *
3836
+ * 配置常常来自 JSON 或表单输入,`'48'` 这类无单位数字串很常见;直接写进 CSS 变量会让
3837
+ * 嵌套层的 `calc()` 整体失效、吸顶静默失灵,所以纯数字一律补 `px`。
3838
+ */
3839
+ var toCssLength = (height) => {
3840
+ const value = `${height ?? ""}`.trim();
3841
+ if (!value) return void 0;
3842
+ if ((0, _tmagic_utils.isNumber)(value)) return `${value}px`;
3843
+ return typeof height === "number" ? void 0 : value;
3844
+ };
3845
+ /** header.sticky 开启时把 height 写成 CSS 变量,给嵌套吸顶偏移用 */
3846
+ var getGroupListHeaderVars = (header) => {
3847
+ if (!header?.sticky) return void 0;
3848
+ const height = toCssLength(header.height);
3849
+ return height ? { "--m-group-list-header-height": height } : void 0;
3850
+ };
3798
3851
  /** 按 label 文案长度估算 label 宽度(中文按 20px、其他按 8px,最小 80px) */
3799
3852
  var calcLabelWidth = (label) => {
3800
3853
  if (!label) return "0px";
@@ -4725,7 +4778,7 @@
4725
4778
  };
4726
4779
  var _hoisted_3$7 = ["innerHTML"];
4727
4780
  var _hoisted_4$6 = ["innerHTML"];
4728
- var _hoisted_5$5 = ["innerHTML"];
4781
+ var _hoisted_5$4 = ["innerHTML"];
4729
4782
  var _hoisted_6$3 = ["innerHTML"];
4730
4783
  var _hoisted_7$2 = ["innerHTML"];
4731
4784
  var _hoisted_8$2 = ["innerHTML"];
@@ -5080,7 +5133,7 @@
5080
5133
  key: 0,
5081
5134
  placement: tooltip.value.placement
5082
5135
  }, {
5083
- content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$5)]),
5136
+ content: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { innerHTML: tooltip.value.text }, null, 8, _hoisted_5$4)]),
5084
5137
  default: (0, vue.withCtx)(() => [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(tagName.value), (0, vue.mergeProps)(fieldsProps.value, {
5085
5138
  model: __props.model,
5086
5139
  "last-values": __props.lastValues,
@@ -6478,7 +6531,7 @@
6478
6531
  var _hoisted_2$7 = ["innerHTML"];
6479
6532
  var _hoisted_3$6 = { key: 1 };
6480
6533
  var _hoisted_4$5 = ["innerHTML"];
6481
- var _hoisted_5$4 = ["innerHTML"];
6534
+ var _hoisted_5$3 = ["innerHTML"];
6482
6535
  var _hoisted_6$2 = {
6483
6536
  key: 2,
6484
6537
  style: { "display": "flex" }
@@ -6569,7 +6622,7 @@
6569
6622
  key: 0,
6570
6623
  innerHTML: __props.config.extra,
6571
6624
  class: "m-form-tip"
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) => {
6625
+ }, 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) => {
6573
6626
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
6574
6627
  key: key(item, index),
6575
6628
  model: name.value ? __props.model[name.value] : __props.model,
@@ -6707,7 +6760,7 @@
6707
6760
  style: { "display": "flex" }
6708
6761
  };
6709
6762
  var _hoisted_4$4 = { style: { "flex": "1" } };
6710
- var _hoisted_5$3 = ["src"];
6763
+ var _hoisted_5$2 = ["src"];
6711
6764
  //#endregion
6712
6765
  //#region packages/form/src/containers/Panel.vue
6713
6766
  var Panel_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -6800,7 +6853,7 @@
6800
6853
  }), 128))]), (0, vue.createElementVNode)("img", {
6801
6854
  class: "m-form-schematic",
6802
6855
  src: __props.config.schematic
6803
- }, null, 8, _hoisted_5$3)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
6856
+ }, null, 8, _hoisted_5$2)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, (0, vue.renderList)(items.value, (item, index) => {
6804
6857
  return (0, vue.openBlock)(), (0, vue.createBlock)(Container_default, {
6805
6858
  key: item[(0, vue.unref)(mForm)?.keyProp || "__key"] ?? index,
6806
6859
  config: item,
@@ -7158,7 +7211,7 @@
7158
7211
  var _hoisted_2$5 = { class: "m-fields-group-list-item-title" };
7159
7212
  var _hoisted_3$4 = ["innerHTML"];
7160
7213
  var _hoisted_4$3 = { class: "m-fields-group-list-item-actions" };
7161
- var _hoisted_5$2 = { style: {
7214
+ var _hoisted_5$1 = { style: {
7162
7215
  "text-align": "right",
7163
7216
  "margin-top": "20px"
7164
7217
  } };
@@ -7323,7 +7376,7 @@
7323
7376
  disabled: __props.disabled
7324
7377
  }, null, 8, ["modelValue", "disabled"]),
7325
7378
  _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), {
7379
+ ]), (0, vue.createElementVNode)("div", _hoisted_5$1, [(0, vue.createVNode)((0, vue.unref)(_tmagic_design.TMagicButton), {
7327
7380
  size: "small",
7328
7381
  text: "",
7329
7382
  onClick: _cache[4] || (_cache[4] = ($event) => moveSpecifyLocationVisible.value = false)
@@ -7393,14 +7446,13 @@
7393
7446
  });
7394
7447
  //#endregion
7395
7448
  //#region packages/form/src/containers/GroupList.vue?vue&type=script&setup=true&lang.ts
7396
- var _hoisted_1$8 = { class: "m-fields-group-list" };
7397
- var _hoisted_2$4 = ["innerHTML"];
7398
- var _hoisted_3$3 = {
7449
+ var _hoisted_1$8 = ["innerHTML"];
7450
+ var _hoisted_2$4 = {
7399
7451
  key: 1,
7400
7452
  class: "el-table__empty-block"
7401
7453
  };
7402
- var _hoisted_4$2 = { class: "el-table__empty-text t-table__empty" };
7403
- var _hoisted_5$1 = { style: {
7454
+ var _hoisted_3$3 = { class: "el-table__empty-text t-table__empty" };
7455
+ var _hoisted_4$2 = { style: {
7404
7456
  "display": "flex",
7405
7457
  "justify-content": "flex-end",
7406
7458
  "flex": "1"
@@ -7447,6 +7499,14 @@
7447
7499
  };
7448
7500
  const onAddDiffCount = () => emit("addDiffCount");
7449
7501
  const asList = (value) => Array.isArray(value) ? value : [];
7502
+ const headerVars = (0, vue.computed)(() => getGroupListHeaderVars(props.config.header));
7503
+ /**
7504
+ * 取 `$slots` 而不是 `useSlots()`:宿主切换 `addable` 时插槽会增删,
7505
+ * computed 缓存不随插槽变化失效,只有在渲染期读才拿得到最新的。
7506
+ */
7507
+ const hasFooter = (slots) => !props.isCompare && Boolean(slots["toggle-button"] || slots["add-button"]);
7508
+ /** 只有真的渲染出吸底 footer,内层列表才需要为它让位 */
7509
+ const isFooterSticky = (slots) => hasFooter(slots) && Boolean(props.config.addButtonConfig?.sticky);
7450
7510
  const currentList = (0, vue.computed)(() => asList(props.model[props.name]));
7451
7511
  /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */
7452
7512
  const displayItems = (0, vue.computed)(() => {
@@ -7464,13 +7524,19 @@
7464
7524
  }));
7465
7525
  });
7466
7526
  return (_ctx, _cache) => {
7467
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$8, [
7527
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7528
+ class: (0, vue.normalizeClass)(["m-fields-group-list", {
7529
+ "is-header-sticky": Boolean(__props.config.header?.sticky),
7530
+ "is-footer-sticky": isFooterSticky(_ctx.$slots)
7531
+ }]),
7532
+ style: (0, vue.normalizeStyle)(headerVars.value)
7533
+ }, [
7468
7534
  __props.config.extra ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7469
7535
  key: 0,
7470
7536
  innerHTML: __props.config.extra,
7471
7537
  style: { "color": "rgba(0, 0, 0, 0.45)" }
7472
- }, null, 8, _hoisted_2$4)) : (0, vue.createCommentVNode)("v-if", true),
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) => {
7538
+ }, null, 8, _hoisted_1$8)) : (0, vue.createCommentVNode)("v-if", true),
7539
+ !displayItems.value.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$4, [(0, vue.createElementVNode)("span", _hoisted_3$3, "暂无" + (0, vue.toDisplayString)(__props.config.titlePrefix || "") + "数据", 1)])) : ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, { key: 2 }, (0, vue.renderList)(displayItems.value, (entry) => {
7474
7540
  return (0, vue.openBlock)(), (0, vue.createBlock)(GroupListItem_default, {
7475
7541
  key: entry.index,
7476
7542
  model: entry.item,
@@ -7507,11 +7573,11 @@
7507
7573
  "group-model"
7508
7574
  ]);
7509
7575
  }), 128)),
7510
- !__props.isCompare && (_ctx.$slots["toggle-button"] || _ctx.$slots["add-button"]) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7576
+ hasFooter(_ctx.$slots) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
7511
7577
  key: 3,
7512
7578
  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)
7514
- ]);
7579
+ }, [(0, vue.renderSlot)(_ctx.$slots, "toggle-button"), (0, vue.createElementVNode)("div", _hoisted_4$2, [(0, vue.renderSlot)(_ctx.$slots, "add-button")])], 2)) : (0, vue.createCommentVNode)("v-if", true)
7580
+ ], 6);
7515
7581
  };
7516
7582
  }
7517
7583
  });
@@ -8590,14 +8656,17 @@
8590
8656
  const ctrl = navigator.platform.match("Mac") ? $event.metaKey : $event.ctrlKey;
8591
8657
  const shift = $event.shiftKey;
8592
8658
  const alt = $event.altKey;
8593
- if (arrowUp) if (ctrl) num += 100;
8594
- else if (alt) num = (num * 1e4 + 1e3) / 1e4;
8595
- else if (shift) num = num + 10;
8596
- else num += 1;
8597
- else if (arrowDown) if (ctrl) num -= 100;
8598
- else if (alt) num = (num * 1e4 - 1e3) / 1e4;
8599
- else if (shift) num -= 10;
8600
- else num -= 1;
8659
+ if (arrowUp) {
8660
+ if (ctrl) num += 100;
8661
+ else if (alt) num = (num * 1e4 + 1e3) / 1e4;
8662
+ else if (shift) num = num + 10;
8663
+ else num += 1;
8664
+ } else if (arrowDown) {
8665
+ if (ctrl) num -= 100;
8666
+ else if (alt) num = (num * 1e4 - 1e3) / 1e4;
8667
+ else if (shift) num -= 10;
8668
+ else num -= 1;
8669
+ }
8601
8670
  props.model[props.name] = `${num}${unit || ""}`;
8602
8671
  emit("change", props.model[props.name]);
8603
8672
  };
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.0-beta.27",
2
+ "version": "1.8.0-beta.29",
3
3
  "name": "@tmagic/form",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -46,21 +46,21 @@
46
46
  "@element-plus/icons-vue": "^2.3.2",
47
47
  "@popperjs/core": "^2.11.8",
48
48
  "async-validator": "^4.2.5",
49
- "dayjs": "^1.11.21",
49
+ "dayjs": "^1.11.23",
50
50
  "lodash-es": "^4.18.1",
51
51
  "sortablejs": "^1.15.7"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/lodash-es": "^4.17.12",
55
55
  "@types/sortablejs": "^1.15.9",
56
- "@vue/test-utils": "^2.4.11"
56
+ "@vue/test-utils": "^2.5.1"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "typescript": "^6.0.3",
60
- "vue": "^3.5.41",
61
- "@tmagic/design": "1.8.0-beta.27",
62
- "@tmagic/form-schema": "1.8.0-beta.27",
63
- "@tmagic/utils": "1.8.0-beta.27"
60
+ "vue": "^3.5.43",
61
+ "@tmagic/design": "1.8.0-beta.29",
62
+ "@tmagic/utils": "1.8.0-beta.29",
63
+ "@tmagic/form-schema": "1.8.0-beta.29"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "typescript": {
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <div class="m-fields-group-list">
2
+ <div
3
+ class="m-fields-group-list"
4
+ :class="{
5
+ 'is-header-sticky': Boolean(config.header?.sticky),
6
+ 'is-footer-sticky': isFooterSticky($slots),
7
+ }"
8
+ :style="headerVars"
9
+ >
3
10
  <div v-if="config.extra" v-html="config.extra" style="color: rgba(0, 0, 0, 0.45)"></div>
4
11
  <div v-if="!displayItems.length" class="el-table__empty-block">
5
12
  <span class="el-table__empty-text t-table__empty">暂无{{ config.titlePrefix || '' }}数据</span>
@@ -34,7 +41,7 @@
34
41
  <div
35
42
  class="m-fields-group-list-footer"
36
43
  :class="{ 'is-sticky-full': Boolean(config.addButtonConfig?.sticky) }"
37
- v-if="!isCompare && ($slots['toggle-button'] || $slots['add-button'])"
44
+ v-if="hasFooter($slots)"
38
45
  >
39
46
  <slot name="toggle-button"></slot>
40
47
  <div style="display: flex; justify-content: flex-end; flex: 1">
@@ -49,6 +56,7 @@ import { computed } from 'vue';
49
56
  import { cloneDeep } from 'lodash-es';
50
57
 
51
58
  import type { ContainerChangeEventData, GroupListConfig } from '../schema';
59
+ import { getGroupListHeaderVars } from '../utils/tableGroupList';
52
60
 
53
61
  import MFieldsGroupListItem from './GroupListItem.vue';
54
62
 
@@ -104,6 +112,19 @@ const onAddDiffCount = () => emit('addDiffCount');
104
112
 
105
113
  const asList = (value: unknown): any[] => (Array.isArray(value) ? value : []);
106
114
 
115
+ const headerVars = computed(() => getGroupListHeaderVars(props.config.header));
116
+
117
+ /**
118
+ * 取 `$slots` 而不是 `useSlots()`:宿主切换 `addable` 时插槽会增删,
119
+ * computed 缓存不随插槽变化失效,只有在渲染期读才拿得到最新的。
120
+ */
121
+ const hasFooter = (slots: Record<string, unknown>) =>
122
+ !props.isCompare && Boolean(slots['toggle-button'] || slots['add-button']);
123
+
124
+ /** 只有真的渲染出吸底 footer,内层列表才需要为它让位 */
125
+ const isFooterSticky = (slots: Record<string, unknown>) =>
126
+ hasFooter(slots) && Boolean(props.config.addButtonConfig?.sticky);
127
+
107
128
  const currentList = computed(() => asList(props.model[props.name]));
108
129
 
109
130
  /** 对比时按当前/历史较长一侧对齐,已删除的项也能渲染出来 */