@formily-design/formily-antd 1.0.21 → 1.0.22

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.
@@ -1,24 +1,103 @@
1
1
  import { Cascader as FormilyCascader } from '@formily/antd-v5';
2
- import { createBehavior, createResource } from '@formily-design/core';
3
- import { createFieldSchema } from '../Field';
2
+ import { VALUE_MAPPING_CARDINALITY_MULTIPLE, VALUE_MAPPING_SCHEMA_KEY, VALUE_MAPPING_SCOPE_RELATIVE, VALUE_MAPPING_SCOPE_SOURCE, VALUE_MAPPING_TARGET_FIELDS, VALUE_MAPPING_TARGET_OBJECT, createBehavior, createResource, } from '@formily-design/core';
3
+ import { createFieldSchema } from '../Field/shared';
4
+ import { createValueMappingGroup } from '../Field/valueMapping';
4
5
  import { AllSchemas } from '../../schemas';
5
6
  import { AllLocales } from '../../locales';
7
+ // 这些路径同时被 value-mapping 公共 reaction 和映射位置设置项读取,避免组件配置各自维护协议路径。
8
+ const valueMappingFieldsPath = `${VALUE_MAPPING_SCHEMA_KEY}.target.fields`;
9
+ const valueMappingTargetTypePath = `${VALUE_MAPPING_SCHEMA_KEY}.target.type`;
10
+ const multiplePath = 'x-component-props.multiple';
11
+ /**
12
+ * Cascader 复用 tuple adapter;字段数量由动态 ArrayItems 管理,target 则根据 multiple
13
+ * 和映射位置设置在运行时解析,从而让公共 value-mapping helper 不依赖 Cascader 属性。
14
+ */
15
+ const ValueMappingGroup = createValueMappingGroup({
16
+ adapter: 'tuple',
17
+ fields: {
18
+ type: 'dynamic',
19
+ path: valueMappingFieldsPath,
20
+ token: 'fieldPath',
21
+ },
22
+ cardinality: { path: multiplePath },
23
+ target: {
24
+ resolve: ({ form, cardinality }) => {
25
+ // 多选业务值必须是对象数组,因此强制使用当前字段 object/source。
26
+ if (cardinality === VALUE_MAPPING_CARDINALITY_MULTIPLE) {
27
+ return {
28
+ type: VALUE_MAPPING_TARGET_OBJECT,
29
+ scope: VALUE_MAPPING_SCOPE_SOURCE,
30
+ };
31
+ }
32
+ // 单选允许在当前字段对象和同级字段之间切换,默认沿用当前字段。
33
+ return form.getValuesIn(valueMappingTargetTypePath) ===
34
+ VALUE_MAPPING_TARGET_FIELDS
35
+ ? {
36
+ type: VALUE_MAPPING_TARGET_FIELDS,
37
+ scope: VALUE_MAPPING_SCOPE_RELATIVE,
38
+ includeSource: false,
39
+ }
40
+ : {
41
+ type: VALUE_MAPPING_TARGET_OBJECT,
42
+ scope: VALUE_MAPPING_SCOPE_SOURCE,
43
+ };
44
+ },
45
+ },
46
+ settingsProperties: {
47
+ // 映射位置是组件专属的设置 UI,最终只影响 target.type;scope/includeSource 由 resolver 固定。
48
+ [valueMappingTargetTypePath]: {
49
+ type: 'string',
50
+ title: '{{$node.getMessage("settings.value-mapping-group.mappingPosition")}}',
51
+ enum: [
52
+ {
53
+ value: VALUE_MAPPING_TARGET_OBJECT,
54
+ label: '{{$node.getMessage("settings.value-mapping-group.currentField")}}',
55
+ },
56
+ {
57
+ value: VALUE_MAPPING_TARGET_FIELDS,
58
+ label: '{{$node.getMessage("settings.value-mapping-group.siblingFields")}}',
59
+ },
60
+ ],
61
+ 'x-decorator': 'FormItem',
62
+ 'x-decorator-props': {
63
+ tooltip: '{{$node.getMessage("settings.value-mapping-group.multipleOnlyCurrent")}}',
64
+ },
65
+ 'x-component': 'Radio.Group',
66
+ 'x-component-props': {
67
+ optionType: 'button',
68
+ defaultValue: VALUE_MAPPING_TARGET_OBJECT,
69
+ },
70
+ 'x-reactions': (field) => {
71
+ const fields = field.form.getValuesIn(valueMappingFieldsPath);
72
+ const multiple = field.form.getValuesIn(multiplePath);
73
+ // 没有字段行时不显示位置选项;multiple 开启后位置只能是当前字段。
74
+ field.visible = Array.isArray(fields) && fields.length > 0;
75
+ field.disabled = multiple === true;
76
+ },
77
+ },
78
+ },
79
+ });
6
80
  export const Cascader = FormilyCascader;
81
+ // 将 Cascader 注册为可选中的设计器行为,并挂载专属属性面板配置。
7
82
  Cascader.Behavior = createBehavior({
8
83
  name: 'Cascader',
9
84
  extends: ['Field'],
10
85
  selector: (node) => node.props['x-component'] === 'Cascader',
11
86
  designerProps: {
12
- propsSchema: createFieldSchema(AllSchemas.Cascader),
87
+ propsSchema: createFieldSchema(AllSchemas.Cascader, undefined, {
88
+ 'value-mapping-group': ValueMappingGroup,
89
+ }),
13
90
  },
14
91
  designerLocales: AllLocales.Cascader,
15
92
  });
93
+ // 将 Cascader 注册到资源面板,拖入画布时生成默认 Field Schema。
16
94
  Cascader.Resource = createResource({
17
95
  icon: 'CascaderSource',
18
96
  elements: [
19
97
  {
20
98
  componentName: 'Field',
21
99
  props: {
100
+ type: 'array',
22
101
  title: 'Cascader',
23
102
  'x-decorator': 'FormItem',
24
103
  'x-component': 'Cascader',
@@ -1,46 +1,26 @@
1
1
  import { DatePicker as FormilyDatePicker } from '@formily/antd-v5';
2
2
  import { createBehavior, createResource } from '@formily-design/core';
3
- import { VALUE_MAPPING_SCHEMA_KEY } from '@formily-design/core';
4
3
  import { createFieldSchema } from '../Field/shared';
5
- import { createValueMappingSettingsReaction } from '../Field/valueMapping';
4
+ import { createValueMappingGroup } from '../Field/valueMapping';
6
5
  import { AllSchemas } from '../../schemas';
7
6
  import { AllLocales } from '../../locales';
8
- const valueMappingTargetPaths = [
9
- `${VALUE_MAPPING_SCHEMA_KEY}.targets.0.path`,
10
- `${VALUE_MAPPING_SCHEMA_KEY}.targets.1.path`,
11
- ];
12
- const createMappingPathSchema = (titleToken) => ({
13
- type: 'string',
14
- title: `{{$node.getMessage("settings.value-mapping-group.${titleToken}")}}`,
15
- 'x-decorator': 'FormItem',
16
- 'x-decorator-props': {
17
- // SettingsForm 默认不展示 feedback;映射路径需要即时呈现输入校验结果。
18
- feedbackLayout: 'loose',
7
+ const ValueMappingGroup = createValueMappingGroup({
8
+ adapter: 'tuple',
9
+ fields: {
10
+ type: 'fixed',
11
+ paths: [
12
+ 'x-value-mapping.target.fields.0.path',
13
+ 'x-value-mapping.target.fields.1.path',
14
+ ],
15
+ tokens: ['startPath', 'endPath'],
19
16
  },
20
- 'x-component': 'Input',
21
- 'x-component-props': {
22
- allowClear: true,
17
+ cardinality: 'single',
18
+ target: {
19
+ type: 'fields',
20
+ scope: 'relative',
21
+ includeSource: false,
23
22
  },
24
23
  });
25
- const ValueMappingGroup = {
26
- type: 'void',
27
- 'x-component': 'CollapseItem',
28
- 'x-component-props': { defaultExpand: false },
29
- // 分组只挂一个 reaction,避免协议同步与反馈校验的两个 autorun 产生竞态。
30
- 'x-reactions': [
31
- createValueMappingSettingsReaction({
32
- adapter: 'tuple',
33
- targetPaths: valueMappingTargetPaths,
34
- scope: 'relative',
35
- submitSource: false,
36
- getMessage: (error, scope) => { var _a; return (_a = scope.$node) === null || _a === void 0 ? void 0 : _a.getMessage(`settings.value-mapping-group.${error}`); },
37
- }),
38
- ],
39
- properties: {
40
- [valueMappingTargetPaths[0]]: createMappingPathSchema('startPath'),
41
- [valueMappingTargetPaths[1]]: createMappingPathSchema('endPath'),
42
- },
43
- };
44
24
  export const DatePicker = FormilyDatePicker;
45
25
  DatePicker.Behavior = createBehavior({
46
26
  name: 'DatePicker',
@@ -1,45 +1,78 @@
1
- import type { IScopeContext, SchemaReaction } from '@formily/json-schema';
2
- import { VALUE_MAPPING_RELATIVE_SCOPE } from '@formily-design/core';
3
- import type { ValueMappingTargetValidationError } from '@formily-design/core';
1
+ import type { ISchema, IScopeContext, SchemaReaction } from '@formily/json-schema';
2
+ import type { Form } from '@formily/core';
3
+ import { VALUE_MAPPING_TARGET_FIELDS, VALUE_MAPPING_TARGET_OBJECT, VALUE_MAPPING_SCOPE_RELATIVE, VALUE_MAPPING_SCOPE_SOURCE } from '@formily-design/core';
4
+ import type { ValueMappingCardinality, ValueMappingTargetValidationError } from '@formily-design/core';
4
5
  export type { ValueMappingTargetValidationError } from '@formily-design/core';
6
+ /** 固定字段由组件属性直接提供;动态字段由设置器中的 ArrayItems 提供。 */
7
+ export type ValueMappingSettingsFieldSource = {
8
+ type: 'fixed';
9
+ paths: string[];
10
+ tokens: string[];
11
+ } | {
12
+ type: 'dynamic';
13
+ path: string;
14
+ token: string;
15
+ };
16
+ export type ValueMappingSettingsCardinality =
17
+ /** 固定 cardinality 适合 RangePicker 等属性已知的组件。 */
18
+ ValueMappingCardinality
19
+ /** 动态 cardinality 从组件属性路径读取,供 Cascader.multiple 等属性联动。 */
20
+ | {
21
+ path: string;
22
+ };
23
+ /** 设置器侧只描述 target 的选择项,具体 fields 由 reaction 在运行时补全。 */
24
+ export type ValueMappingSettingsTarget = {
25
+ type: typeof VALUE_MAPPING_TARGET_FIELDS;
26
+ scope: typeof VALUE_MAPPING_SCOPE_RELATIVE;
27
+ includeSource: boolean;
28
+ } | {
29
+ type: typeof VALUE_MAPPING_TARGET_OBJECT;
30
+ scope: typeof VALUE_MAPPING_SCOPE_SOURCE;
31
+ };
32
+ /** 根据当前设置表单状态解析 target,使公共 helper 不需要识别具体组件。 */
33
+ export type ValueMappingSettingsTargetResolver = {
34
+ resolve(context: {
35
+ form: Form;
36
+ cardinality: ValueMappingCardinality;
37
+ }): ValueMappingSettingsTarget;
38
+ };
39
+ export type ValueMappingSettingsTargetSource = ValueMappingSettingsTarget | ValueMappingSettingsTargetResolver;
5
40
  export interface ICreateValueMappingSettingsReactionOptions {
6
- /**
7
- * 映射运行时使用的 adapter 名称,例如 tuple。
8
- */
41
+ /** adapter 名称写入协议,转换实现由 designer runtime 注册。 */
9
42
  adapter: string;
10
- /**
11
- * 设置表单中 target path 输入框的完整数据路径。
12
- *
13
- * 注意:这里配置的是输入框在设置表单中的路径,而不是用户最终填写的
14
- * startDate、address.provinceCode 等业务字段路径。
15
- */
16
- targetPaths: string[];
17
- scope?: typeof VALUE_MAPPING_RELATIVE_SCOPE;
18
- submitSource?: boolean;
19
- /**
20
- * 根据校验错误码返回当前语言的提示文案。scope 会透传 Formily
21
- * reaction 的作用域,因此设置器可以使用 `$node.getMessage` 读取节点语言。
22
- */
43
+ /** 映射字段的来源及设置器字段路径。 */
44
+ fields: ValueMappingSettingsFieldSource;
45
+ /** 固定值或组件属性驱动的 cardinality。 */
46
+ cardinality: ValueMappingSettingsCardinality;
47
+ /** 静态 target 或依赖 form/cardinality 的 target resolver。 */
48
+ target: ValueMappingSettingsTargetSource;
49
+ /** 将协议校验错误转换为当前语言的设置器文案。 */
23
50
  getMessage: (error: ValueMappingTargetValidationError, scope: IScopeContext) => string | undefined;
24
51
  }
52
+ export interface IValueMappingGroupOptions extends Omit<ICreateValueMappingSettingsReactionOptions, 'getMessage'> {
53
+ /** value-mapping 分组使用的 locale 命名空间。 */
54
+ localeKey?: string;
55
+ /** 由具体组件追加的配置项,例如 Cascader 的映射位置单选框。 */
56
+ settingsProperties?: Record<string, ISchema>;
57
+ }
58
+ export declare const validateValueMappingTargetPaths: (paths: unknown[], allowEmpty?: boolean) => ValueMappingTargetValidationError | undefined;
25
59
  /**
26
- * 校验一组手动输入的映射 target 路径。
27
- *
28
- * 空数组表示尚未启用映射;一旦任意路径有值,所有 target 都必须填写、合法且唯一。
29
- * 该方法与具体组件数量无关,日期范围、省市区等配置界面均可复用。
60
+ * 创建统一处理协议同步、动态字段和校验反馈的设置 reaction。
61
+ * reaction 先读取设置状态,再决定删除协议、反馈错误或写入 canonical v1 协议。
30
62
  */
31
- export declare const validateValueMappingTargetPaths: (paths: unknown[]) => ValueMappingTargetValidationError | undefined;
63
+ export declare const createValueMappingSettingsReaction: ({ adapter, fields, cardinality, target, getMessage, }: ICreateValueMappingSettingsReactionOptions) => SchemaReaction;
32
64
  /**
33
- * 创建设置表单使用的 value-mapping 协议与校验 reaction。
34
- *
35
- * 协议和反馈必须由一个 autorun 编排。若分别挂两个 x-reactions,它们对同一
36
- * 次 onInput 的调度顺序不稳定:协议 reaction 规范化数字路径时会再次写值,
37
- * 校验 reaction 可能拿到中间快照,导致残留错误或覆盖刚写入的配置。本 reaction
38
- * 先读取一次 targetValues,再同步反馈和协议,保证二者始终对应同一组输入。
39
- *
40
- * target 全空时先同步清除反馈,让界面立即恢复;父级协议延迟到当前 onInput/batch
41
- * 结束后的 Promise 微任务再删除。微任务执行前会重新读取最新 target:若用户已快速
42
- * 重新输入则跳过删除,避免旧 cleanup 误删。按 Form 隔离的 WeakSet 会合并同一 Form
43
- * 的重复 cleanup,同时不会影响其它设置 Form。
65
+ * 创建组件属性面板中的新 v1 value-mapping 分组。
66
+ * 具体组件可通过 settingsProperties 注入额外设置,但协议同步仍由公共 reaction 负责。
44
67
  */
45
- export declare const createValueMappingSettingsReaction: ({ adapter, targetPaths, scope, submitSource, getMessage, }: ICreateValueMappingSettingsReactionOptions) => SchemaReaction;
68
+ export declare const createValueMappingGroup: ({ localeKey, settingsProperties, ...options }: IValueMappingGroupOptions) => {
69
+ type: string;
70
+ 'x-component': string;
71
+ 'x-component-props': {
72
+ defaultExpand: boolean;
73
+ };
74
+ 'x-reactions': SchemaReaction[];
75
+ properties: {
76
+ [x: string]: any;
77
+ };
78
+ };
@@ -1,105 +1,145 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { untracked } from '@formily/reactive';
2
- import { VALUE_MAPPING_RELATIVE_SCOPE, VALUE_MAPPING_SCHEMA_KEY, VALUE_MAPPING_VERSION, validateValueMappingTargetPaths as validateSharedTargetPaths, } from '@formily-design/core';
3
- // 一个 Form 只允许排队一个 cleanup;WeakSet 不持有 Form 强引用,也不会串扰其它 Form。
13
+ import { VALUE_MAPPING_CARDINALITY_MULTIPLE, VALUE_MAPPING_CARDINALITY_SINGLE, VALUE_MAPPING_SCHEMA_KEY, VALUE_MAPPING_TARGET_FIELDS, VALUE_MAPPING_TARGET_OBJECT, VALUE_MAPPING_VERSION, validateValueMappingFieldPaths, } from '@formily-design/core';
14
+ /** 防止清空字段时 reaction 的同步更新与异步清理互相重复。 */
4
15
  const pendingMappingCleanups = new WeakSet();
16
+ /** 只有输入过非空文本才显示“不完整”字段的校验反馈。 */
5
17
  const hasInputText = (value) => typeof value === 'string' && Boolean(value.trim());
6
- /**
7
- * 校验一组手动输入的映射 target 路径。
8
- *
9
- * 空数组表示尚未启用映射;一旦任意路径有值,所有 target 都必须填写、合法且唯一。
10
- * 该方法与具体组件数量无关,日期范围、省市区等配置界面均可复用。
11
- */
12
- export const validateValueMappingTargetPaths = (paths) => validateSharedTargetPaths(paths, { allowEmpty: true });
13
- /** 将同一份 target 快照规范化为运行时协议。 */
14
- const syncValueMappingProtocol = (form, targetValues, adapter, scope, submitSource) => {
15
- const normalizedTargets = targetValues.map((path) => ({
18
+ export const validateValueMappingTargetPaths = (paths, allowEmpty = true) =>
19
+ // 设置器允许固定字段全部为空来表达删除配置;动态字段则由调用方传入对应规则。
20
+ validateValueMappingFieldPaths(paths, { allowEmpty });
21
+ /** 将静态值或组件属性中的 truthy 值统一映射为协议 cardinality。 */
22
+ const getCardinality = (form, cardinality) => {
23
+ if (typeof cardinality === 'string')
24
+ return cardinality;
25
+ return form.getValuesIn(cardinality.path)
26
+ ? VALUE_MAPPING_CARDINALITY_MULTIPLE
27
+ : VALUE_MAPPING_CARDINALITY_SINGLE;
28
+ };
29
+ /** 在写入协议前解析静态 target 或组件提供的动态 target。 */
30
+ const resolveTarget = (form, cardinality, target) => {
31
+ if ('resolve' in target) {
32
+ return target.resolve({ form, cardinality });
33
+ }
34
+ return target;
35
+ };
36
+ /** 读取设置器当前字段值,并同时返回用于反馈错误的字段路径。 */
37
+ const getFieldSettings = (form, fields) => {
38
+ if (fields.type === 'fixed') {
39
+ // 通过 query 优先读取已注册字段值,兼容设置器字段尚未注册时的 form 值。
40
+ return {
41
+ values: fields.paths.map((path) => {
42
+ let fieldValue;
43
+ form.query(path).take((field) => {
44
+ fieldValue = field === null || field === void 0 ? void 0 : field.value;
45
+ });
46
+ return fieldValue === undefined ? form.getValuesIn(path) : fieldValue;
47
+ }),
48
+ paths: fields.paths,
49
+ dynamic: false,
50
+ };
51
+ }
52
+ // 动态字段的每一行都映射到协议中的一个 fields 项,路径顺序必须保持一致。
53
+ const current = form.getValuesIn(fields.path);
54
+ const values = Array.isArray(current) ? current : [];
55
+ return {
56
+ values: values.map((item) => item === null || item === void 0 ? void 0 : item.path),
57
+ paths: values.map((_, index) => `${fields.path}.${index}.path`),
58
+ dynamic: true,
59
+ };
60
+ };
61
+ const syncValueMappingProtocol = (form, values, adapter, cardinality, target, fields) => {
62
+ // 协议只保存规范化后的 path;设置器仍保留同一顺序,便于 ArrayItems 增删和排序。
63
+ const normalizedFields = values.map((path) => ({
16
64
  path: typeof path === 'string' ? path.trim() : '',
17
65
  }));
18
- const currentTargets = form.getValuesIn(`${VALUE_MAPPING_SCHEMA_KEY}.targets`);
19
- const targetsChanged = !Array.isArray(currentTargets) ||
20
- currentTargets.length !== normalizedTargets.length ||
21
- normalizedTargets.some((target, index) => { var _a; return ((_a = currentTargets[index]) === null || _a === void 0 ? void 0 : _a.path) !== target.path; });
22
- // FormPath 的数字点路径默认可能生成对象,这里统一规范化为运行时协议要求的数组。
23
- if (targetsChanged) {
24
- form.setValuesIn(`${VALUE_MAPPING_SCHEMA_KEY}.targets`, normalizedTargets);
25
- }
26
- const setProtocolValue = (path, value) => {
27
- if (form.getValuesIn(path) !== value) {
28
- form.setValuesIn(path, value);
66
+ const targetValue = target.type === VALUE_MAPPING_TARGET_FIELDS
67
+ ? {
68
+ type: VALUE_MAPPING_TARGET_FIELDS,
69
+ scope: target.scope,
70
+ fields: normalizedFields,
71
+ includeSource: target.includeSource === true,
29
72
  }
73
+ : {
74
+ type: VALUE_MAPPING_TARGET_OBJECT,
75
+ scope: target.scope,
76
+ fields: normalizedFields,
77
+ };
78
+ const next = {
79
+ version: VALUE_MAPPING_VERSION,
80
+ adapter,
81
+ cardinality,
82
+ target: targetValue,
30
83
  };
31
- setProtocolValue(`${VALUE_MAPPING_SCHEMA_KEY}.version`, VALUE_MAPPING_VERSION);
32
- setProtocolValue(`${VALUE_MAPPING_SCHEMA_KEY}.adapter`, adapter);
33
- setProtocolValue(`${VALUE_MAPPING_SCHEMA_KEY}.scope`, scope);
34
- setProtocolValue(`${VALUE_MAPPING_SCHEMA_KEY}.submitSource`, submitSource);
84
+ const current = form.getValuesIn(VALUE_MAPPING_SCHEMA_KEY);
85
+ // 避免 reaction 在每次运行时重复写入,减少设置表单的二次 reaction。
86
+ if (JSON.stringify(current) !== JSON.stringify(next)) {
87
+ form.setValuesIn(VALUE_MAPPING_SCHEMA_KEY, next);
88
+ }
89
+ // 动态 ArrayItems 需要保持用户对字段顺序的修改;协议本身与设置数组共用同一顺序。
90
+ if (fields.type === 'dynamic') {
91
+ const currentFields = form.getValuesIn(fields.path);
92
+ if (JSON.stringify(currentFields) !== JSON.stringify(normalizedFields)) {
93
+ form.setValuesIn(fields.path, normalizedFields);
94
+ }
95
+ }
35
96
  };
36
- /** 同步 target Field 的错误反馈;它不读取 feedback,因此 feedback 只是 reaction 输出。 */
37
- const syncValueMappingErrors = (form, targetPaths, targetValues, error, message) => {
38
- targetPaths.forEach((path, index) => {
39
- form.query(path).take((targetField) => {
40
- if (!(targetField === null || targetField === void 0 ? void 0 : targetField.setSelfErrors))
97
+ const syncValueMappingErrors = (form, paths, values, error, message, dynamic) => {
98
+ // 动态行需要立即提示空项;固定字段只有在用户输入过时才提示 incomplete。
99
+ paths.forEach((path, index) => {
100
+ form.query(path).take((field) => {
101
+ if (!(field === null || field === void 0 ? void 0 : field.setSelfErrors))
41
102
  return;
42
- const hasInput = hasInputText(targetValues[index]);
43
- const shouldShowError = Boolean(error && (error !== 'incomplete' || hasInput));
44
- targetField.setSelfErrors(shouldShowError && message ? [message] : []);
103
+ const hasInput = hasInputText(values[index]);
104
+ const show = Boolean(error && (dynamic || error !== 'incomplete' || hasInput));
105
+ field.setSelfErrors(show && message ? [message] : []);
45
106
  });
46
107
  });
47
108
  };
109
+ const shouldDeleteMapping = (values, fields) => {
110
+ // 动态字段“零行”与固定字段“所有值为空”都表示移除完整协议。
111
+ if (fields.type === 'dynamic') {
112
+ return values.length === 0;
113
+ }
114
+ return values.every((value) => !hasInputText(value));
115
+ };
48
116
  /**
49
- * 读取一次 target 快照。主 reaction 和延迟 cleanup 必须使用同一口径:优先读
50
- * 已注册 Field value,未注册的动态路径才从 Form values 兜底,避免 cleanup
51
- * 看到的值与主 reaction 不一致。
52
- */
53
- const readValueMappingTargetValues = (form, targetPaths) => targetPaths.map((path) => {
54
- let hasField = false;
55
- let fieldValue;
56
- // 使用 take callback 兼容 Formily Query 和设置表单测试/动态节点实现。
57
- form.query(path).take((targetField) => {
58
- hasField = Boolean(targetField);
59
- fieldValue = targetField === null || targetField === void 0 ? void 0 : targetField.value;
60
- });
61
- const fallbackValue = form.getValuesIn(path);
62
- return hasField && fieldValue !== undefined ? fieldValue : fallbackValue;
63
- });
64
- /**
65
- * 创建设置表单使用的 value-mapping 协议与校验 reaction。
66
- *
67
- * 协议和反馈必须由一个 autorun 编排。若分别挂两个 x-reactions,它们对同一
68
- * 次 onInput 的调度顺序不稳定:协议 reaction 规范化数字路径时会再次写值,
69
- * 校验 reaction 可能拿到中间快照,导致残留错误或覆盖刚写入的配置。本 reaction
70
- * 先读取一次 targetValues,再同步反馈和协议,保证二者始终对应同一组输入。
71
- *
72
- * target 全空时先同步清除反馈,让界面立即恢复;父级协议延迟到当前 onInput/batch
73
- * 结束后的 Promise 微任务再删除。微任务执行前会重新读取最新 target:若用户已快速
74
- * 重新输入则跳过删除,避免旧 cleanup 误删。按 Form 隔离的 WeakSet 会合并同一 Form
75
- * 的重复 cleanup,同时不会影响其它设置 Form。
117
+ * 创建统一处理协议同步、动态字段和校验反馈的设置 reaction
118
+ * reaction 先读取设置状态,再决定删除协议、反馈错误或写入 canonical v1 协议。
76
119
  */
77
- export const createValueMappingSettingsReaction = ({ adapter, targetPaths, scope = VALUE_MAPPING_RELATIVE_SCOPE, submitSource = false, getMessage, }) => {
120
+ export const createValueMappingSettingsReaction = ({ adapter, fields, cardinality, target, getMessage, }) => {
78
121
  return (_field, reactionScope) => {
79
122
  const form = reactionScope === null || reactionScope === void 0 ? void 0 : reactionScope.$form;
80
123
  if (!form)
81
124
  return;
82
- // 优先读取已注册 Field value computed,让依赖绑定在稳定的 Field 上;协议
83
- // 首次从数字路径对象规范化成数组后,后续另一个 Input 仍能触发同一 autorun。
84
- // 未挂载的 target 则退回 Form values,兼容动态设置 Schema。
85
- const targetValues = readValueMappingTargetValues(form, targetPaths);
86
- const error = validateValueMappingTargetPaths(targetValues);
125
+ const settings = getFieldSettings(form, fields);
126
+ const error = validateValueMappingFieldPaths(settings.values, {
127
+ allowEmpty: fields.type === 'fixed',
128
+ });
87
129
  const message = error ? getMessage(error, reactionScope) : undefined;
88
- const hasTarget = targetValues.some(hasInputText);
89
- if (!hasTarget) {
90
- // 反馈清理同步执行;只有协议确实存在时才排队父对象删除。
91
- untracked(() => {
92
- syncValueMappingErrors(form, targetPaths, targetValues, error, message);
93
- });
130
+ const empty = shouldDeleteMapping(settings.values, fields);
131
+ untracked(() => {
132
+ syncValueMappingErrors(form, settings.paths, settings.values, error, message, settings.dynamic);
133
+ });
134
+ if (empty) {
135
+ // 清空操作可能触发多次 reaction,放到微任务中读取最新状态后再删除协议。
94
136
  if (form.getValuesIn(VALUE_MAPPING_SCHEMA_KEY) !== undefined &&
95
137
  !pendingMappingCleanups.has(form)) {
96
138
  pendingMappingCleanups.add(form);
97
139
  Promise.resolve().then(() => {
98
140
  pendingMappingCleanups.delete(form);
99
- // 清空与重新输入可能发生在同一批次或连续微任务中,因此这里必须重新读值。
100
- const latestTargetValues = readValueMappingTargetValues(form, targetPaths);
101
- const latestHasTarget = latestTargetValues.some(hasInputText);
102
- if (!latestHasTarget &&
141
+ const latest = getFieldSettings(form, fields);
142
+ if (shouldDeleteMapping(latest.values, fields) &&
103
143
  form.getValuesIn(VALUE_MAPPING_SCHEMA_KEY) !== undefined) {
104
144
  form.deleteValuesIn(VALUE_MAPPING_SCHEMA_KEY);
105
145
  }
@@ -107,11 +147,81 @@ export const createValueMappingSettingsReaction = ({ adapter, targetPaths, scope
107
147
  }
108
148
  return;
109
149
  }
110
- // feedback 是输出,不能反过来成为依赖;协议写入则保留通知,使 autorun
111
- // 在数字路径从对象规范化为数组后重新建立对新数组叶节点的依赖。
112
- untracked(() => {
113
- syncValueMappingErrors(form, targetPaths, targetValues, error, message);
114
- });
115
- syncValueMappingProtocol(form, targetValues, adapter, scope, submitSource);
150
+ const currentCardinality = getCardinality(form, cardinality);
151
+ // target 必须在 cardinality 确定后解析,保证 Cascader.multiple 切换时协议组合合法。
152
+ syncValueMappingProtocol(form, settings.values, adapter, currentCardinality, resolveTarget(form, currentCardinality, target), fields);
153
+ };
154
+ };
155
+ const createFixedFieldsSchema = (fields, localeKey) => {
156
+ // 固定字段直接生成多个 Input,字段名即协议中对应的 settings path。
157
+ const getLocaleMessage = (token) => `{{$node.getMessage("settings.${localeKey}.${token}")}}`;
158
+ return fields.paths.reduce((properties, path, index) => {
159
+ properties[path] = {
160
+ type: 'string',
161
+ title: getLocaleMessage(fields.tokens[index]),
162
+ 'x-decorator': 'FormItem',
163
+ 'x-decorator-props': { feedbackLayout: 'loose' },
164
+ 'x-component': 'Input',
165
+ 'x-component-props': { allowClear: true },
166
+ };
167
+ return properties;
168
+ }, {});
169
+ };
170
+ const createDynamicFieldsSchema = (fields, localeKey) => ({
171
+ // 动态字段使用 ArrayItems 承载可变数量的映射项,顺序就是 tuple 层级顺序。
172
+ [fields.path]: {
173
+ type: 'array',
174
+ default: [],
175
+ 'x-component': 'ArrayItems',
176
+ items: {
177
+ type: 'object',
178
+ 'x-decorator': 'ArrayItems.Item',
179
+ 'x-decorator-props': {
180
+ type: 'divide',
181
+ style: { borderBottom: 'none' },
182
+ },
183
+ properties: {
184
+ // remove 只负责删除当前行;字段顺序由 ArrayItems 自身维护。
185
+ path: {
186
+ type: 'string',
187
+ title: `{{$node.getMessage("settings.${localeKey}.${fields.token}")}}`,
188
+ 'x-decorator': 'FormItem',
189
+ 'x-decorator-props': { feedbackLayout: 'loose' },
190
+ 'x-component': 'Input',
191
+ 'x-component-props': { allowClear: true },
192
+ },
193
+ remove: {
194
+ type: 'void',
195
+ 'x-component': 'ArrayItems.Remove',
196
+ },
197
+ },
198
+ },
199
+ properties: {
200
+ addition: {
201
+ type: 'void',
202
+ 'x-component': 'ArrayItems.Addition',
203
+ 'x-component-props': { style: { marginBottom: 8 } },
204
+ },
205
+ },
206
+ },
207
+ });
208
+ /**
209
+ * 创建组件属性面板中的新 v1 value-mapping 分组。
210
+ * 具体组件可通过 settingsProperties 注入额外设置,但协议同步仍由公共 reaction 负责。
211
+ */
212
+ export const createValueMappingGroup = (_a) => {
213
+ var { localeKey = 'value-mapping-group', settingsProperties } = _a, options = __rest(_a, ["localeKey", "settingsProperties"]);
214
+ const fieldProperties = options.fields.type === 'fixed'
215
+ ? createFixedFieldsSchema(options.fields, localeKey)
216
+ : createDynamicFieldsSchema(options.fields, localeKey);
217
+ const properties = Object.assign(Object.assign({}, fieldProperties), settingsProperties);
218
+ return {
219
+ type: 'void',
220
+ 'x-component': 'CollapseItem',
221
+ 'x-component-props': { defaultExpand: false },
222
+ 'x-reactions': [
223
+ createValueMappingSettingsReaction(Object.assign(Object.assign({}, options), { getMessage: (error, reactionScope) => { var _a; return (_a = reactionScope.$node) === null || _a === void 0 ? void 0 : _a.getMessage(`settings.${localeKey}.${error}`); } })),
224
+ ],
225
+ properties,
116
226
  };
117
227
  };