@formily-design/formily-antd 1.0.20 → 1.0.21
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.
- package/esm/components/DatePicker/preview.js +42 -2
- package/esm/components/Field/index.d.ts +1 -0
- package/esm/components/Field/index.js +1 -0
- package/esm/components/Field/shared.d.ts +1 -1
- package/esm/components/Field/shared.js +3 -3
- package/esm/components/Field/valueMapping.d.ts +45 -0
- package/esm/components/Field/valueMapping.js +117 -0
- package/esm/locales/DatePicker.js +30 -0
- package/lib/components/DatePicker/preview.js +43 -3
- package/lib/components/Field/index.d.ts +1 -0
- package/lib/components/Field/index.js +1 -0
- package/lib/components/Field/shared.d.ts +1 -1
- package/lib/components/Field/shared.js +3 -3
- package/lib/components/Field/valueMapping.d.ts +45 -0
- package/lib/components/Field/valueMapping.js +122 -0
- package/lib/locales/DatePicker.js +30 -0
- package/package.json +8 -8
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
import { DatePicker as FormilyDatePicker } from '@formily/antd-v5';
|
|
2
2
|
import { createBehavior, createResource } from '@formily-design/core';
|
|
3
|
-
import {
|
|
3
|
+
import { VALUE_MAPPING_SCHEMA_KEY } from '@formily-design/core';
|
|
4
|
+
import { createFieldSchema } from '../Field/shared';
|
|
5
|
+
import { createValueMappingSettingsReaction } from '../Field/valueMapping';
|
|
4
6
|
import { AllSchemas } from '../../schemas';
|
|
5
7
|
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',
|
|
19
|
+
},
|
|
20
|
+
'x-component': 'Input',
|
|
21
|
+
'x-component-props': {
|
|
22
|
+
allowClear: true,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
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
|
+
};
|
|
6
44
|
export const DatePicker = FormilyDatePicker;
|
|
7
45
|
DatePicker.Behavior = createBehavior({
|
|
8
46
|
name: 'DatePicker',
|
|
@@ -17,7 +55,9 @@ DatePicker.Behavior = createBehavior({
|
|
|
17
55
|
extends: ['Field'],
|
|
18
56
|
selector: (node) => node.props['x-component'] === 'DatePicker.RangePicker',
|
|
19
57
|
designerProps: {
|
|
20
|
-
propsSchema: createFieldSchema(AllSchemas.DatePicker.RangePicker
|
|
58
|
+
propsSchema: createFieldSchema(AllSchemas.DatePicker.RangePicker, undefined, {
|
|
59
|
+
'value-mapping-group': ValueMappingGroup,
|
|
60
|
+
}),
|
|
21
61
|
},
|
|
22
62
|
designerLocales: AllLocales.DateRangePicker,
|
|
23
63
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ISchema } from '@formily/json-schema';
|
|
2
2
|
export declare const createComponentSchema: (component: ISchema, decorator: ISchema) => Record<string, ISchema | undefined>;
|
|
3
|
-
export declare const createFieldSchema: (component?: ISchema, decorator?: ISchema) => ISchema;
|
|
3
|
+
export declare const createFieldSchema: (component?: ISchema, decorator?: ISchema, additionalGroups?: Record<string, ISchema>) => ISchema;
|
|
4
4
|
export declare const createVoidFieldSchema: (component?: ISchema, decorator?: ISchema) => ISchema;
|
|
@@ -64,10 +64,10 @@ export const createComponentSchema = (component, decorator) => {
|
|
|
64
64
|
},
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
|
-
export const createFieldSchema = (component, decorator = AllSchemas.FormItem) => {
|
|
67
|
+
export const createFieldSchema = (component, decorator = AllSchemas.FormItem, additionalGroups = {}) => {
|
|
68
68
|
return {
|
|
69
69
|
type: 'object',
|
|
70
|
-
properties: Object.assign({ 'field-group': {
|
|
70
|
+
properties: Object.assign(Object.assign({ 'field-group': {
|
|
71
71
|
type: 'void',
|
|
72
72
|
'x-component': 'CollapseItem',
|
|
73
73
|
properties: {
|
|
@@ -170,7 +170,7 @@ export const createFieldSchema = (component, decorator = AllSchemas.FormItem) =>
|
|
|
170
170
|
'x-component': 'Switch',
|
|
171
171
|
},
|
|
172
172
|
},
|
|
173
|
-
} }, createComponentSchema(component, decorator)),
|
|
173
|
+
} }, additionalGroups), createComponentSchema(component, decorator)),
|
|
174
174
|
};
|
|
175
175
|
};
|
|
176
176
|
export const createVoidFieldSchema = (component, decorator = AllSchemas.FormItem) => {
|
|
@@ -0,0 +1,45 @@
|
|
|
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';
|
|
4
|
+
export type { ValueMappingTargetValidationError } from '@formily-design/core';
|
|
5
|
+
export interface ICreateValueMappingSettingsReactionOptions {
|
|
6
|
+
/**
|
|
7
|
+
* 映射运行时使用的 adapter 名称,例如 tuple。
|
|
8
|
+
*/
|
|
9
|
+
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
|
+
*/
|
|
23
|
+
getMessage: (error: ValueMappingTargetValidationError, scope: IScopeContext) => string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 校验一组手动输入的映射 target 路径。
|
|
27
|
+
*
|
|
28
|
+
* 空数组表示尚未启用映射;一旦任意路径有值,所有 target 都必须填写、合法且唯一。
|
|
29
|
+
* 该方法与具体组件数量无关,日期范围、省市区等配置界面均可复用。
|
|
30
|
+
*/
|
|
31
|
+
export declare const validateValueMappingTargetPaths: (paths: unknown[]) => ValueMappingTargetValidationError | undefined;
|
|
32
|
+
/**
|
|
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。
|
|
44
|
+
*/
|
|
45
|
+
export declare const createValueMappingSettingsReaction: ({ adapter, targetPaths, scope, submitSource, getMessage, }: ICreateValueMappingSettingsReactionOptions) => SchemaReaction;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
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。
|
|
4
|
+
const pendingMappingCleanups = new WeakSet();
|
|
5
|
+
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) => ({
|
|
16
|
+
path: typeof path === 'string' ? path.trim() : '',
|
|
17
|
+
}));
|
|
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);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
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);
|
|
35
|
+
};
|
|
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))
|
|
41
|
+
return;
|
|
42
|
+
const hasInput = hasInputText(targetValues[index]);
|
|
43
|
+
const shouldShowError = Boolean(error && (error !== 'incomplete' || hasInput));
|
|
44
|
+
targetField.setSelfErrors(shouldShowError && message ? [message] : []);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
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。
|
|
76
|
+
*/
|
|
77
|
+
export const createValueMappingSettingsReaction = ({ adapter, targetPaths, scope = VALUE_MAPPING_RELATIVE_SCOPE, submitSource = false, getMessage, }) => {
|
|
78
|
+
return (_field, reactionScope) => {
|
|
79
|
+
const form = reactionScope === null || reactionScope === void 0 ? void 0 : reactionScope.$form;
|
|
80
|
+
if (!form)
|
|
81
|
+
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);
|
|
87
|
+
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
|
+
});
|
|
94
|
+
if (form.getValuesIn(VALUE_MAPPING_SCHEMA_KEY) !== undefined &&
|
|
95
|
+
!pendingMappingCleanups.has(form)) {
|
|
96
|
+
pendingMappingCleanups.add(form);
|
|
97
|
+
Promise.resolve().then(() => {
|
|
98
|
+
pendingMappingCleanups.delete(form);
|
|
99
|
+
// 清空与重新输入可能发生在同一批次或连续微任务中,因此这里必须重新读值。
|
|
100
|
+
const latestTargetValues = readValueMappingTargetValues(form, targetPaths);
|
|
101
|
+
const latestHasTarget = latestTargetValues.some(hasInputText);
|
|
102
|
+
if (!latestHasTarget &&
|
|
103
|
+
form.getValuesIn(VALUE_MAPPING_SCHEMA_KEY) !== undefined) {
|
|
104
|
+
form.deleteValuesIn(VALUE_MAPPING_SCHEMA_KEY);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// feedback 是输出,不能反过来成为依赖;协议写入则保留通知,使 autorun
|
|
111
|
+
// 在数字路径从对象规范化为数组后重新建立对新数组叶节点的依赖。
|
|
112
|
+
untracked(() => {
|
|
113
|
+
syncValueMappingErrors(form, targetPaths, targetValues, error, message);
|
|
114
|
+
});
|
|
115
|
+
syncValueMappingProtocol(form, targetValues, adapter, scope, submitSource);
|
|
116
|
+
};
|
|
117
|
+
};
|
|
@@ -76,11 +76,41 @@ export const DatePicker = {
|
|
|
76
76
|
export const DateRangePicker = createLocales(DatePicker, {
|
|
77
77
|
'zh-CN': {
|
|
78
78
|
title: '日期范围',
|
|
79
|
+
settings: {
|
|
80
|
+
'value-mapping-group': {
|
|
81
|
+
title: '数据映射',
|
|
82
|
+
startPath: '开始日期字段路径',
|
|
83
|
+
endPath: '结束日期字段路径',
|
|
84
|
+
incomplete: '开始和结束字段路径必须同时填写',
|
|
85
|
+
duplicate: '开始和结束字段路径不能相同',
|
|
86
|
+
invalid: '请输入合法的相对字段路径',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
79
89
|
},
|
|
80
90
|
'en-US': {
|
|
81
91
|
title: 'DateRange',
|
|
92
|
+
settings: {
|
|
93
|
+
'value-mapping-group': {
|
|
94
|
+
title: 'Data Mapping',
|
|
95
|
+
startPath: 'Start date field path',
|
|
96
|
+
endPath: 'End date field path',
|
|
97
|
+
incomplete: 'Both start and end field paths are required',
|
|
98
|
+
duplicate: 'Start and end field paths must be different',
|
|
99
|
+
invalid: 'Enter a valid relative field path',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
82
102
|
},
|
|
83
103
|
'ko-KR': {
|
|
84
104
|
title: '날짜범위 선택 상자',
|
|
105
|
+
settings: {
|
|
106
|
+
'value-mapping-group': {
|
|
107
|
+
title: '데이터 매핑',
|
|
108
|
+
startPath: '시작 날짜 필드 경로',
|
|
109
|
+
endPath: '종료 날짜 필드 경로',
|
|
110
|
+
incomplete: '시작 및 종료 필드 경로를 모두 입력하세요',
|
|
111
|
+
duplicate: '시작 및 종료 필드 경로는 달라야 합니다',
|
|
112
|
+
invalid: '올바른 상대 필드 경로를 입력하세요',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
85
115
|
},
|
|
86
116
|
});
|
|
@@ -3,16 +3,54 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DatePicker = void 0;
|
|
4
4
|
const antd_v5_1 = require("@formily/antd-v5");
|
|
5
5
|
const core_1 = require("@formily-design/core");
|
|
6
|
-
const
|
|
6
|
+
const core_2 = require("@formily-design/core");
|
|
7
|
+
const shared_1 = require("../Field/shared");
|
|
8
|
+
const valueMapping_1 = require("../Field/valueMapping");
|
|
7
9
|
const schemas_1 = require("../../schemas");
|
|
8
10
|
const locales_1 = require("../../locales");
|
|
11
|
+
const valueMappingTargetPaths = [
|
|
12
|
+
`${core_2.VALUE_MAPPING_SCHEMA_KEY}.targets.0.path`,
|
|
13
|
+
`${core_2.VALUE_MAPPING_SCHEMA_KEY}.targets.1.path`,
|
|
14
|
+
];
|
|
15
|
+
const createMappingPathSchema = (titleToken) => ({
|
|
16
|
+
type: 'string',
|
|
17
|
+
title: `{{$node.getMessage("settings.value-mapping-group.${titleToken}")}}`,
|
|
18
|
+
'x-decorator': 'FormItem',
|
|
19
|
+
'x-decorator-props': {
|
|
20
|
+
// SettingsForm 默认不展示 feedback;映射路径需要即时呈现输入校验结果。
|
|
21
|
+
feedbackLayout: 'loose',
|
|
22
|
+
},
|
|
23
|
+
'x-component': 'Input',
|
|
24
|
+
'x-component-props': {
|
|
25
|
+
allowClear: true,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const ValueMappingGroup = {
|
|
29
|
+
type: 'void',
|
|
30
|
+
'x-component': 'CollapseItem',
|
|
31
|
+
'x-component-props': { defaultExpand: false },
|
|
32
|
+
// 分组只挂一个 reaction,避免协议同步与反馈校验的两个 autorun 产生竞态。
|
|
33
|
+
'x-reactions': [
|
|
34
|
+
(0, valueMapping_1.createValueMappingSettingsReaction)({
|
|
35
|
+
adapter: 'tuple',
|
|
36
|
+
targetPaths: valueMappingTargetPaths,
|
|
37
|
+
scope: 'relative',
|
|
38
|
+
submitSource: false,
|
|
39
|
+
getMessage: (error, scope) => { var _a; return (_a = scope.$node) === null || _a === void 0 ? void 0 : _a.getMessage(`settings.value-mapping-group.${error}`); },
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
properties: {
|
|
43
|
+
[valueMappingTargetPaths[0]]: createMappingPathSchema('startPath'),
|
|
44
|
+
[valueMappingTargetPaths[1]]: createMappingPathSchema('endPath'),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
9
47
|
exports.DatePicker = antd_v5_1.DatePicker;
|
|
10
48
|
exports.DatePicker.Behavior = (0, core_1.createBehavior)({
|
|
11
49
|
name: 'DatePicker',
|
|
12
50
|
extends: ['Field'],
|
|
13
51
|
selector: (node) => node.props['x-component'] === 'DatePicker',
|
|
14
52
|
designerProps: {
|
|
15
|
-
propsSchema: (0,
|
|
53
|
+
propsSchema: (0, shared_1.createFieldSchema)(schemas_1.AllSchemas.DatePicker),
|
|
16
54
|
},
|
|
17
55
|
designerLocales: locales_1.AllLocales.DatePicker,
|
|
18
56
|
}, {
|
|
@@ -20,7 +58,9 @@ exports.DatePicker.Behavior = (0, core_1.createBehavior)({
|
|
|
20
58
|
extends: ['Field'],
|
|
21
59
|
selector: (node) => node.props['x-component'] === 'DatePicker.RangePicker',
|
|
22
60
|
designerProps: {
|
|
23
|
-
propsSchema: (0,
|
|
61
|
+
propsSchema: (0, shared_1.createFieldSchema)(schemas_1.AllSchemas.DatePicker.RangePicker, undefined, {
|
|
62
|
+
'value-mapping-group': ValueMappingGroup,
|
|
63
|
+
}),
|
|
24
64
|
},
|
|
25
65
|
designerLocales: locales_1.AllLocales.DateRangePicker,
|
|
26
66
|
});
|
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./preview"), exports);
|
|
18
18
|
__exportStar(require("./shared"), exports);
|
|
19
|
+
__exportStar(require("./valueMapping"), exports);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ISchema } from '@formily/json-schema';
|
|
2
2
|
export declare const createComponentSchema: (component: ISchema, decorator: ISchema) => Record<string, ISchema | undefined>;
|
|
3
|
-
export declare const createFieldSchema: (component?: ISchema, decorator?: ISchema) => ISchema;
|
|
3
|
+
export declare const createFieldSchema: (component?: ISchema, decorator?: ISchema, additionalGroups?: Record<string, ISchema>) => ISchema;
|
|
4
4
|
export declare const createVoidFieldSchema: (component?: ISchema, decorator?: ISchema) => ISchema;
|
|
@@ -68,10 +68,10 @@ const createComponentSchema = (component, decorator) => {
|
|
|
68
68
|
};
|
|
69
69
|
};
|
|
70
70
|
exports.createComponentSchema = createComponentSchema;
|
|
71
|
-
const createFieldSchema = (component, decorator = schemas_1.AllSchemas.FormItem) => {
|
|
71
|
+
const createFieldSchema = (component, decorator = schemas_1.AllSchemas.FormItem, additionalGroups = {}) => {
|
|
72
72
|
return {
|
|
73
73
|
type: 'object',
|
|
74
|
-
properties: Object.assign({ 'field-group': {
|
|
74
|
+
properties: Object.assign(Object.assign({ 'field-group': {
|
|
75
75
|
type: 'void',
|
|
76
76
|
'x-component': 'CollapseItem',
|
|
77
77
|
properties: {
|
|
@@ -174,7 +174,7 @@ const createFieldSchema = (component, decorator = schemas_1.AllSchemas.FormItem)
|
|
|
174
174
|
'x-component': 'Switch',
|
|
175
175
|
},
|
|
176
176
|
},
|
|
177
|
-
} }, (0, exports.createComponentSchema)(component, decorator)),
|
|
177
|
+
} }, additionalGroups), (0, exports.createComponentSchema)(component, decorator)),
|
|
178
178
|
};
|
|
179
179
|
};
|
|
180
180
|
exports.createFieldSchema = createFieldSchema;
|
|
@@ -0,0 +1,45 @@
|
|
|
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';
|
|
4
|
+
export type { ValueMappingTargetValidationError } from '@formily-design/core';
|
|
5
|
+
export interface ICreateValueMappingSettingsReactionOptions {
|
|
6
|
+
/**
|
|
7
|
+
* 映射运行时使用的 adapter 名称,例如 tuple。
|
|
8
|
+
*/
|
|
9
|
+
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
|
+
*/
|
|
23
|
+
getMessage: (error: ValueMappingTargetValidationError, scope: IScopeContext) => string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 校验一组手动输入的映射 target 路径。
|
|
27
|
+
*
|
|
28
|
+
* 空数组表示尚未启用映射;一旦任意路径有值,所有 target 都必须填写、合法且唯一。
|
|
29
|
+
* 该方法与具体组件数量无关,日期范围、省市区等配置界面均可复用。
|
|
30
|
+
*/
|
|
31
|
+
export declare const validateValueMappingTargetPaths: (paths: unknown[]) => ValueMappingTargetValidationError | undefined;
|
|
32
|
+
/**
|
|
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。
|
|
44
|
+
*/
|
|
45
|
+
export declare const createValueMappingSettingsReaction: ({ adapter, targetPaths, scope, submitSource, getMessage, }: ICreateValueMappingSettingsReactionOptions) => SchemaReaction;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createValueMappingSettingsReaction = exports.validateValueMappingTargetPaths = void 0;
|
|
4
|
+
const reactive_1 = require("@formily/reactive");
|
|
5
|
+
const core_1 = require("@formily-design/core");
|
|
6
|
+
// 一个 Form 只允许排队一个 cleanup;WeakSet 不持有 Form 强引用,也不会串扰其它 Form。
|
|
7
|
+
const pendingMappingCleanups = new WeakSet();
|
|
8
|
+
const hasInputText = (value) => typeof value === 'string' && Boolean(value.trim());
|
|
9
|
+
/**
|
|
10
|
+
* 校验一组手动输入的映射 target 路径。
|
|
11
|
+
*
|
|
12
|
+
* 空数组表示尚未启用映射;一旦任意路径有值,所有 target 都必须填写、合法且唯一。
|
|
13
|
+
* 该方法与具体组件数量无关,日期范围、省市区等配置界面均可复用。
|
|
14
|
+
*/
|
|
15
|
+
const validateValueMappingTargetPaths = (paths) => (0, core_1.validateValueMappingTargetPaths)(paths, { allowEmpty: true });
|
|
16
|
+
exports.validateValueMappingTargetPaths = validateValueMappingTargetPaths;
|
|
17
|
+
/** 将同一份 target 快照规范化为运行时协议。 */
|
|
18
|
+
const syncValueMappingProtocol = (form, targetValues, adapter, scope, submitSource) => {
|
|
19
|
+
const normalizedTargets = targetValues.map((path) => ({
|
|
20
|
+
path: typeof path === 'string' ? path.trim() : '',
|
|
21
|
+
}));
|
|
22
|
+
const currentTargets = form.getValuesIn(`${core_1.VALUE_MAPPING_SCHEMA_KEY}.targets`);
|
|
23
|
+
const targetsChanged = !Array.isArray(currentTargets) ||
|
|
24
|
+
currentTargets.length !== normalizedTargets.length ||
|
|
25
|
+
normalizedTargets.some((target, index) => { var _a; return ((_a = currentTargets[index]) === null || _a === void 0 ? void 0 : _a.path) !== target.path; });
|
|
26
|
+
// FormPath 的数字点路径默认可能生成对象,这里统一规范化为运行时协议要求的数组。
|
|
27
|
+
if (targetsChanged) {
|
|
28
|
+
form.setValuesIn(`${core_1.VALUE_MAPPING_SCHEMA_KEY}.targets`, normalizedTargets);
|
|
29
|
+
}
|
|
30
|
+
const setProtocolValue = (path, value) => {
|
|
31
|
+
if (form.getValuesIn(path) !== value) {
|
|
32
|
+
form.setValuesIn(path, value);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
setProtocolValue(`${core_1.VALUE_MAPPING_SCHEMA_KEY}.version`, core_1.VALUE_MAPPING_VERSION);
|
|
36
|
+
setProtocolValue(`${core_1.VALUE_MAPPING_SCHEMA_KEY}.adapter`, adapter);
|
|
37
|
+
setProtocolValue(`${core_1.VALUE_MAPPING_SCHEMA_KEY}.scope`, scope);
|
|
38
|
+
setProtocolValue(`${core_1.VALUE_MAPPING_SCHEMA_KEY}.submitSource`, submitSource);
|
|
39
|
+
};
|
|
40
|
+
/** 同步 target Field 的错误反馈;它不读取 feedback,因此 feedback 只是 reaction 输出。 */
|
|
41
|
+
const syncValueMappingErrors = (form, targetPaths, targetValues, error, message) => {
|
|
42
|
+
targetPaths.forEach((path, index) => {
|
|
43
|
+
form.query(path).take((targetField) => {
|
|
44
|
+
if (!(targetField === null || targetField === void 0 ? void 0 : targetField.setSelfErrors))
|
|
45
|
+
return;
|
|
46
|
+
const hasInput = hasInputText(targetValues[index]);
|
|
47
|
+
const shouldShowError = Boolean(error && (error !== 'incomplete' || hasInput));
|
|
48
|
+
targetField.setSelfErrors(shouldShowError && message ? [message] : []);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* 读取一次 target 快照。主 reaction 和延迟 cleanup 必须使用同一口径:优先读
|
|
54
|
+
* 已注册 Field 的 value,未注册的动态路径才从 Form values 兜底,避免 cleanup
|
|
55
|
+
* 看到的值与主 reaction 不一致。
|
|
56
|
+
*/
|
|
57
|
+
const readValueMappingTargetValues = (form, targetPaths) => targetPaths.map((path) => {
|
|
58
|
+
let hasField = false;
|
|
59
|
+
let fieldValue;
|
|
60
|
+
// 使用 take callback 兼容 Formily Query 和设置表单测试/动态节点实现。
|
|
61
|
+
form.query(path).take((targetField) => {
|
|
62
|
+
hasField = Boolean(targetField);
|
|
63
|
+
fieldValue = targetField === null || targetField === void 0 ? void 0 : targetField.value;
|
|
64
|
+
});
|
|
65
|
+
const fallbackValue = form.getValuesIn(path);
|
|
66
|
+
return hasField && fieldValue !== undefined ? fieldValue : fallbackValue;
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* 创建设置表单使用的 value-mapping 协议与校验 reaction。
|
|
70
|
+
*
|
|
71
|
+
* 协议和反馈必须由一个 autorun 编排。若分别挂两个 x-reactions,它们对同一
|
|
72
|
+
* 次 onInput 的调度顺序不稳定:协议 reaction 规范化数字路径时会再次写值,
|
|
73
|
+
* 校验 reaction 可能拿到中间快照,导致残留错误或覆盖刚写入的配置。本 reaction
|
|
74
|
+
* 先读取一次 targetValues,再同步反馈和协议,保证二者始终对应同一组输入。
|
|
75
|
+
*
|
|
76
|
+
* target 全空时先同步清除反馈,让界面立即恢复;父级协议延迟到当前 onInput/batch
|
|
77
|
+
* 结束后的 Promise 微任务再删除。微任务执行前会重新读取最新 target:若用户已快速
|
|
78
|
+
* 重新输入则跳过删除,避免旧 cleanup 误删。按 Form 隔离的 WeakSet 会合并同一 Form
|
|
79
|
+
* 的重复 cleanup,同时不会影响其它设置 Form。
|
|
80
|
+
*/
|
|
81
|
+
const createValueMappingSettingsReaction = ({ adapter, targetPaths, scope = core_1.VALUE_MAPPING_RELATIVE_SCOPE, submitSource = false, getMessage, }) => {
|
|
82
|
+
return (_field, reactionScope) => {
|
|
83
|
+
const form = reactionScope === null || reactionScope === void 0 ? void 0 : reactionScope.$form;
|
|
84
|
+
if (!form)
|
|
85
|
+
return;
|
|
86
|
+
// 优先读取已注册 Field 的 value computed,让依赖绑定在稳定的 Field 上;协议
|
|
87
|
+
// 首次从数字路径对象规范化成数组后,后续另一个 Input 仍能触发同一 autorun。
|
|
88
|
+
// 未挂载的 target 则退回 Form values,兼容动态设置 Schema。
|
|
89
|
+
const targetValues = readValueMappingTargetValues(form, targetPaths);
|
|
90
|
+
const error = (0, exports.validateValueMappingTargetPaths)(targetValues);
|
|
91
|
+
const message = error ? getMessage(error, reactionScope) : undefined;
|
|
92
|
+
const hasTarget = targetValues.some(hasInputText);
|
|
93
|
+
if (!hasTarget) {
|
|
94
|
+
// 反馈清理同步执行;只有协议确实存在时才排队父对象删除。
|
|
95
|
+
(0, reactive_1.untracked)(() => {
|
|
96
|
+
syncValueMappingErrors(form, targetPaths, targetValues, error, message);
|
|
97
|
+
});
|
|
98
|
+
if (form.getValuesIn(core_1.VALUE_MAPPING_SCHEMA_KEY) !== undefined &&
|
|
99
|
+
!pendingMappingCleanups.has(form)) {
|
|
100
|
+
pendingMappingCleanups.add(form);
|
|
101
|
+
Promise.resolve().then(() => {
|
|
102
|
+
pendingMappingCleanups.delete(form);
|
|
103
|
+
// 清空与重新输入可能发生在同一批次或连续微任务中,因此这里必须重新读值。
|
|
104
|
+
const latestTargetValues = readValueMappingTargetValues(form, targetPaths);
|
|
105
|
+
const latestHasTarget = latestTargetValues.some(hasInputText);
|
|
106
|
+
if (!latestHasTarget &&
|
|
107
|
+
form.getValuesIn(core_1.VALUE_MAPPING_SCHEMA_KEY) !== undefined) {
|
|
108
|
+
form.deleteValuesIn(core_1.VALUE_MAPPING_SCHEMA_KEY);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// feedback 是输出,不能反过来成为依赖;协议写入则保留通知,使 autorun
|
|
115
|
+
// 在数字路径从对象规范化为数组后重新建立对新数组叶节点的依赖。
|
|
116
|
+
(0, reactive_1.untracked)(() => {
|
|
117
|
+
syncValueMappingErrors(form, targetPaths, targetValues, error, message);
|
|
118
|
+
});
|
|
119
|
+
syncValueMappingProtocol(form, targetValues, adapter, scope, submitSource);
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
exports.createValueMappingSettingsReaction = createValueMappingSettingsReaction;
|
|
@@ -79,11 +79,41 @@ exports.DatePicker = {
|
|
|
79
79
|
exports.DateRangePicker = (0, core_1.createLocales)(exports.DatePicker, {
|
|
80
80
|
'zh-CN': {
|
|
81
81
|
title: '日期范围',
|
|
82
|
+
settings: {
|
|
83
|
+
'value-mapping-group': {
|
|
84
|
+
title: '数据映射',
|
|
85
|
+
startPath: '开始日期字段路径',
|
|
86
|
+
endPath: '结束日期字段路径',
|
|
87
|
+
incomplete: '开始和结束字段路径必须同时填写',
|
|
88
|
+
duplicate: '开始和结束字段路径不能相同',
|
|
89
|
+
invalid: '请输入合法的相对字段路径',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
82
92
|
},
|
|
83
93
|
'en-US': {
|
|
84
94
|
title: 'DateRange',
|
|
95
|
+
settings: {
|
|
96
|
+
'value-mapping-group': {
|
|
97
|
+
title: 'Data Mapping',
|
|
98
|
+
startPath: 'Start date field path',
|
|
99
|
+
endPath: 'End date field path',
|
|
100
|
+
incomplete: 'Both start and end field paths are required',
|
|
101
|
+
duplicate: 'Start and end field paths must be different',
|
|
102
|
+
invalid: 'Enter a valid relative field path',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
85
105
|
},
|
|
86
106
|
'ko-KR': {
|
|
87
107
|
title: '날짜범위 선택 상자',
|
|
108
|
+
settings: {
|
|
109
|
+
'value-mapping-group': {
|
|
110
|
+
title: '데이터 매핑',
|
|
111
|
+
startPath: '시작 날짜 필드 경로',
|
|
112
|
+
endPath: '종료 날짜 필드 경로',
|
|
113
|
+
incomplete: '시작 및 종료 필드 경로를 모두 입력하세요',
|
|
114
|
+
duplicate: '시작 및 종료 필드 경로는 달라야 합니다',
|
|
115
|
+
invalid: '올바른 상대 필드 경로를 입력하세요',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
88
118
|
},
|
|
89
119
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formily-design/formily-antd",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "lib",
|
|
6
6
|
"module": "esm",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"start": "webpack-dev-server --config playground/webpack.dev.ts"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@formily-design/react-settings-form": "1.0.
|
|
27
|
+
"@formily-design/react-settings-form": "1.0.21",
|
|
28
28
|
"@formily/antd-v5": "^1.2.4",
|
|
29
29
|
"@formily/core": "^2.0.2",
|
|
30
30
|
"@formily/json-schema": "^2.0.2",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"react-is": ">=16.8.0 || >=17.0.0"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@formily-design/core": "1.0.
|
|
73
|
-
"@formily-design/formily-setters": "1.0.
|
|
74
|
-
"@formily-design/formily-transformer": "1.0.
|
|
75
|
-
"@formily-design/react": "1.0.
|
|
76
|
-
"@formily-design/shared": "1.0.
|
|
72
|
+
"@formily-design/core": "1.0.21",
|
|
73
|
+
"@formily-design/formily-setters": "1.0.21",
|
|
74
|
+
"@formily-design/formily-transformer": "1.0.21",
|
|
75
|
+
"@formily-design/react": "1.0.21",
|
|
76
|
+
"@formily-design/shared": "1.0.21"
|
|
77
77
|
},
|
|
78
78
|
"publishConfig": {
|
|
79
79
|
"access": "public"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "e0694bf3df21eeba42fa476affe8642d07fb3522"
|
|
82
82
|
}
|