@zat-design/sisyphus-react 4.6.2 → 4.6.3-beta.2
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/dist/index.esm.css +1 -1
- package/dist/less.esm.css +1 -1
- package/es/ProForm/components/combination/FormList/components/Empty.d.ts +2 -1
- package/es/ProForm/components/combination/FormList/components/Empty.js +13 -1
- package/es/ProForm/components/combination/FormList/components/TabsFields.js +122 -43
- package/es/ProForm/components/combination/FormList/style/index.less +36 -0
- package/es/ProForm/components/combination/FormList/tabsValidation.d.ts +14 -0
- package/es/ProForm/components/combination/FormList/tabsValidation.js +19 -0
- package/es/ProForm/index.js +16 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ interface EmptyProps {
|
|
|
8
8
|
form?: FormInstance;
|
|
9
9
|
namePath?: InternalNamePath;
|
|
10
10
|
emptyBtnText?: string;
|
|
11
|
+
onAdd?: () => unknown | Promise<unknown>;
|
|
11
12
|
}
|
|
12
|
-
declare const Empty: ({ disabled, toolbarProps, operation, form, namePath, emptyBtnText }: EmptyProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
declare const Empty: ({ disabled, toolbarProps, operation, form, namePath, emptyBtnText, onAdd, }: EmptyProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
14
|
export default Empty;
|
|
@@ -3,12 +3,24 @@ import { Button } from "antd";
|
|
|
3
3
|
import isFunction from "lodash/isFunction";
|
|
4
4
|
import EmptyImg from "../../../../../assets/empty.png";
|
|
5
5
|
import locale from "../../../../../locale";
|
|
6
|
-
const Empty = ({
|
|
6
|
+
const Empty = ({
|
|
7
|
+
disabled,
|
|
8
|
+
toolbarProps,
|
|
9
|
+
operation,
|
|
10
|
+
form,
|
|
11
|
+
namePath,
|
|
12
|
+
emptyBtnText,
|
|
13
|
+
onAdd
|
|
14
|
+
}) => {
|
|
7
15
|
const addConfig = Array.isArray(toolbarProps) ? toolbarProps.find((item) => item.type === "add") : null;
|
|
8
16
|
const handleAddClick = async () => {
|
|
9
17
|
var _a;
|
|
10
18
|
if (!operation || !form || !namePath)
|
|
11
19
|
return;
|
|
20
|
+
if (onAdd) {
|
|
21
|
+
await onAdd();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
12
24
|
const value = form.getFieldValue(namePath);
|
|
13
25
|
const data = await ((_a = addConfig == null ? void 0 : addConfig.onClick) == null ? void 0 : _a.call(addConfig, value, { operation, form, namePath, index: 0 }));
|
|
14
26
|
if (!(addConfig == null ? void 0 : addConfig.onClick) || data === true) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { Row, Tabs } from "antd";
|
|
3
|
+
import { Badge, Row, Tabs } from "antd";
|
|
4
4
|
import { PlusOutlined } from "@ant-design/icons";
|
|
5
5
|
import { DndContext } from "@dnd-kit/core";
|
|
6
6
|
import { horizontalListSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
|
|
@@ -11,6 +11,7 @@ import ProIcon from "../../../../../ProIcon";
|
|
|
11
11
|
import locale from "../../../../../locale";
|
|
12
12
|
import RenderFields from "../../../render/RenderFields";
|
|
13
13
|
import ActionButton from "./ActionButton";
|
|
14
|
+
import Empty from "./Empty";
|
|
14
15
|
import { memoWith } from "../utils";
|
|
15
16
|
import {
|
|
16
17
|
executeToolbarAdd,
|
|
@@ -18,7 +19,29 @@ import {
|
|
|
18
19
|
isToolbarActionVisible,
|
|
19
20
|
normalizeToolbarActions
|
|
20
21
|
} from "../toolbarActions";
|
|
22
|
+
import { subscribeFormListTabsValidation } from "../tabsValidation";
|
|
21
23
|
const MemoRenderFields = React.memo(RenderFields, memoWith);
|
|
24
|
+
const getTabErrorCounts = (errorFields, fields, namePath) => {
|
|
25
|
+
const uniqueErrors = /* @__PURE__ */ new Map();
|
|
26
|
+
errorFields.forEach((errorField) => {
|
|
27
|
+
var _a;
|
|
28
|
+
if (!((_a = errorField.errors) == null ? void 0 : _a.length) || errorField.name.length <= namePath.length + 1)
|
|
29
|
+
return;
|
|
30
|
+
const matchesList = namePath.every((name, index) => errorField.name[index] === name);
|
|
31
|
+
if (!matchesList)
|
|
32
|
+
return;
|
|
33
|
+
const fieldName = errorField.name[namePath.length];
|
|
34
|
+
const field = fields.find((item) => item.name === fieldName);
|
|
35
|
+
if (!field)
|
|
36
|
+
return;
|
|
37
|
+
const fieldKey = String(field.key);
|
|
38
|
+
const errorPath = JSON.stringify(errorField.name.slice(namePath.length + 1));
|
|
39
|
+
const paths = uniqueErrors.get(fieldKey) || /* @__PURE__ */ new Set();
|
|
40
|
+
paths.add(errorPath);
|
|
41
|
+
uniqueErrors.set(fieldKey, paths);
|
|
42
|
+
});
|
|
43
|
+
return new Map(Array.from(uniqueErrors, ([fieldKey, paths]) => [fieldKey, paths.size]));
|
|
44
|
+
};
|
|
22
45
|
const SortableTabLabel = ({
|
|
23
46
|
children,
|
|
24
47
|
disabled,
|
|
@@ -69,6 +92,7 @@ const TabsFields = (props) => {
|
|
|
69
92
|
diffConfig,
|
|
70
93
|
disabled,
|
|
71
94
|
draggable,
|
|
95
|
+
emptyBtnText,
|
|
72
96
|
fields,
|
|
73
97
|
form,
|
|
74
98
|
isView,
|
|
@@ -84,11 +108,17 @@ const TabsFields = (props) => {
|
|
|
84
108
|
const fieldKeys = fields.map((field) => String(field.key));
|
|
85
109
|
const fieldKeysSignature = fieldKeys.join("\0");
|
|
86
110
|
const [activeKey, setActiveKey] = useState(fieldKeys[0]);
|
|
111
|
+
const [validationErrorFields, setValidationErrorFields] = useState([]);
|
|
87
112
|
const activeIndexRef = useRef(0);
|
|
88
113
|
const fieldKeysRef = useRef(fieldKeys);
|
|
114
|
+
const fieldsRef = useRef(fields);
|
|
115
|
+
const namePathRef = useRef(namePath);
|
|
89
116
|
const pendingAddKeysRef = useRef(null);
|
|
90
117
|
const validatingRef = useRef(false);
|
|
91
118
|
fieldKeysRef.current = fieldKeys;
|
|
119
|
+
fieldsRef.current = fields;
|
|
120
|
+
namePathRef.current = namePath;
|
|
121
|
+
const errorCounts = getTabErrorCounts(validationErrorFields, fields, namePath);
|
|
92
122
|
const currentIndex = activeKey ? fieldKeys.indexOf(activeKey) : -1;
|
|
93
123
|
if (currentIndex >= 0)
|
|
94
124
|
activeIndexRef.current = currentIndex;
|
|
@@ -106,6 +136,22 @@ const TabsFields = (props) => {
|
|
|
106
136
|
const fallbackIndex = Math.min(activeIndexRef.current, fieldKeys.length - 1);
|
|
107
137
|
setActiveKey(fallbackIndex >= 0 ? fieldKeys[fallbackIndex] : void 0);
|
|
108
138
|
}, [activeKey, fieldKeysSignature]);
|
|
139
|
+
useEffect(
|
|
140
|
+
() => subscribeFormListTabsValidation(form, (event) => {
|
|
141
|
+
setValidationErrorFields(event.errorFields);
|
|
142
|
+
if (event.reason !== "submitFailed")
|
|
143
|
+
return;
|
|
144
|
+
const counts = getTabErrorCounts(event.errorFields, fieldsRef.current, namePathRef.current);
|
|
145
|
+
const firstErrorIndex = fieldsRef.current.findIndex(
|
|
146
|
+
(field) => counts.has(String(field.key))
|
|
147
|
+
);
|
|
148
|
+
if (firstErrorIndex < 0)
|
|
149
|
+
return;
|
|
150
|
+
activeIndexRef.current = firstErrorIndex;
|
|
151
|
+
setActiveKey(String(fieldsRef.current[firstErrorIndex].key));
|
|
152
|
+
}),
|
|
153
|
+
[form]
|
|
154
|
+
);
|
|
109
155
|
const validateCurrent = useCallback(async () => {
|
|
110
156
|
if (!activeKey)
|
|
111
157
|
return true;
|
|
@@ -142,7 +188,8 @@ const TabsFields = (props) => {
|
|
|
142
188
|
(item) => item.type === "add" && isToolbarActionVisible(item, form, namePath)
|
|
143
189
|
);
|
|
144
190
|
const addLabel = (addAction == null ? void 0 : addAction.label) ?? locale.ProForm.formListTabsAdd;
|
|
145
|
-
const
|
|
191
|
+
const hasReachedMax = max !== void 0 && max <= fields.length;
|
|
192
|
+
const hideAdd = !addAction || addAction.disabled || disabled || isView || hasReachedMax;
|
|
146
193
|
const handleAdd = async () => {
|
|
147
194
|
if (!addAction || hideAdd)
|
|
148
195
|
return;
|
|
@@ -198,30 +245,48 @@ const TabsFields = (props) => {
|
|
|
198
245
|
const tabTitle = isFunction(title) ? title(record, index, form) : title;
|
|
199
246
|
const renderedTitle = tabTitle ?? `Tab ${index + 1}`;
|
|
200
247
|
const tabActionProps = wrapValidatedActions(actionProps || void 0);
|
|
248
|
+
const errorCount = errorCounts.get(fieldKey) || 0;
|
|
201
249
|
return {
|
|
202
250
|
key: fieldKey,
|
|
203
251
|
closable: false,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
252
|
+
// 提前注册未访问页签的字段,确保全量提交能定位隐藏卡片错误。
|
|
253
|
+
forceRender: true,
|
|
254
|
+
label: /* @__PURE__ */ jsx(SortableTabLabel, { fieldKey, draggable, disabled: disabled || isView, children: /* @__PURE__ */ jsx(
|
|
255
|
+
Badge,
|
|
256
|
+
{
|
|
257
|
+
className: "pro-form-list-tabs-error-badge",
|
|
258
|
+
count: errorCount,
|
|
259
|
+
overflowCount: 99,
|
|
260
|
+
size: "small",
|
|
261
|
+
children: /* @__PURE__ */ jsxs(
|
|
262
|
+
"span",
|
|
263
|
+
{
|
|
264
|
+
className: `pro-form-list-tabs-title-content${errorCount ? " pro-form-list-tabs-title-content-error" : ""}`,
|
|
265
|
+
children: [
|
|
266
|
+
/* @__PURE__ */ jsx("span", { className: "pro-form-list-tabs-title", children: renderedTitle }),
|
|
267
|
+
actionProps !== false && /* @__PURE__ */ jsx(
|
|
268
|
+
ActionButton,
|
|
269
|
+
{
|
|
270
|
+
min,
|
|
271
|
+
max,
|
|
272
|
+
index,
|
|
273
|
+
length: fields.length,
|
|
274
|
+
operation,
|
|
275
|
+
namePath: itemNamePath,
|
|
276
|
+
form,
|
|
277
|
+
field: { ...field, key: fieldKey },
|
|
278
|
+
fields,
|
|
279
|
+
actionProps: tabActionProps,
|
|
280
|
+
mode: "tabs",
|
|
281
|
+
disabled,
|
|
282
|
+
isView
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
) }),
|
|
225
290
|
children: /* @__PURE__ */ jsx("div", { className: "pro-form-list-tabs-content", children: /* @__PURE__ */ jsx(Row, { gutter: 16, className: "pro-form-list-fields", children: /* @__PURE__ */ jsx(
|
|
226
291
|
MemoRenderFields,
|
|
227
292
|
{
|
|
@@ -235,31 +300,45 @@ const TabsFields = (props) => {
|
|
|
235
300
|
) }) })
|
|
236
301
|
};
|
|
237
302
|
});
|
|
238
|
-
return /* @__PURE__ */ jsx(DndContext, { onDragEnd: handleDragEnd, children: /* @__PURE__ */
|
|
303
|
+
return /* @__PURE__ */ jsx(DndContext, { onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsxs(
|
|
239
304
|
SortableContext,
|
|
240
305
|
{
|
|
241
306
|
items: fieldKeys.map((key) => ({ id: key })),
|
|
242
307
|
strategy: horizontalListSortingStrategy,
|
|
243
|
-
children:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
/* @__PURE__ */
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
308
|
+
children: [
|
|
309
|
+
/* @__PURE__ */ jsx(
|
|
310
|
+
Tabs,
|
|
311
|
+
{
|
|
312
|
+
className: "pro-form-list-tabs",
|
|
313
|
+
type: "editable-card",
|
|
314
|
+
size: "small",
|
|
315
|
+
activeKey,
|
|
316
|
+
addIcon: /* @__PURE__ */ jsxs("span", { className: "pro-form-list-tabs-add", children: [
|
|
317
|
+
/* @__PURE__ */ jsx(PlusOutlined, {}),
|
|
318
|
+
/* @__PURE__ */ jsx("span", { className: "pro-form-list-tabs-add-label", children: addLabel })
|
|
319
|
+
] }),
|
|
320
|
+
hideAdd,
|
|
321
|
+
items,
|
|
322
|
+
onChange: handleChange,
|
|
323
|
+
onEdit: (_, action) => {
|
|
324
|
+
if (action === "add")
|
|
325
|
+
void handleAdd();
|
|
326
|
+
}
|
|
260
327
|
}
|
|
261
|
-
|
|
262
|
-
|
|
328
|
+
),
|
|
329
|
+
fields.length === 0 && /* @__PURE__ */ jsx(
|
|
330
|
+
Empty,
|
|
331
|
+
{
|
|
332
|
+
disabled: hideAdd,
|
|
333
|
+
toolbarProps: addAction ? [{ ...addAction, show: true }] : false,
|
|
334
|
+
operation,
|
|
335
|
+
form,
|
|
336
|
+
namePath,
|
|
337
|
+
emptyBtnText,
|
|
338
|
+
onAdd: handleAdd
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
]
|
|
263
342
|
}
|
|
264
343
|
) });
|
|
265
344
|
};
|
|
@@ -130,6 +130,17 @@
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
.pro-form-list-tabs {
|
|
133
|
+
&.@{ant-prefix}-tabs > .@{ant-prefix}-tabs-nav {
|
|
134
|
+
.@{ant-prefix}-tabs-nav-wrap {
|
|
135
|
+
overflow-x: clip;
|
|
136
|
+
overflow-y: visible;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.@{ant-prefix}-tabs-nav-list {
|
|
140
|
+
overflow: visible;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
133
144
|
&.@{ant-prefix}-tabs-card > .@{ant-prefix}-tabs-nav {
|
|
134
145
|
&::before {
|
|
135
146
|
border-bottom-color: var(--ant-color-border, #d9d9d9);
|
|
@@ -168,6 +179,31 @@
|
|
|
168
179
|
white-space: nowrap;
|
|
169
180
|
}
|
|
170
181
|
|
|
182
|
+
.pro-form-list-tabs-error-badge {
|
|
183
|
+
display: inline-flex;
|
|
184
|
+
|
|
185
|
+
.@{ant-prefix}-badge-count {
|
|
186
|
+
top: 0;
|
|
187
|
+
right: 0;
|
|
188
|
+
z-index: 2;
|
|
189
|
+
pointer-events: none;
|
|
190
|
+
transform: translate(50%, -50%);
|
|
191
|
+
box-shadow: 0 0 0 2px var(--ant-color-bg-container, #fff);
|
|
192
|
+
transition: none;
|
|
193
|
+
animation: none;
|
|
194
|
+
|
|
195
|
+
.@{ant-prefix}-scroll-number-only-unit {
|
|
196
|
+
transition: none;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.pro-form-list-tabs-title-content {
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
gap: 4px;
|
|
204
|
+
align-items: center;
|
|
205
|
+
}
|
|
206
|
+
|
|
171
207
|
.pro-form-list-tabs-drag-handle {
|
|
172
208
|
display: inline-flex;
|
|
173
209
|
align-items: center;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FormInstance } from 'antd';
|
|
2
|
+
import type { InternalNamePath } from 'antd/es/form/interface';
|
|
3
|
+
export interface FormListTabsErrorField {
|
|
4
|
+
errors?: unknown[];
|
|
5
|
+
name: InternalNamePath;
|
|
6
|
+
}
|
|
7
|
+
export interface FormListTabsValidationEvent {
|
|
8
|
+
errorFields: FormListTabsErrorField[];
|
|
9
|
+
reason: 'fieldsChange' | 'submitFailed' | 'submitSuccess';
|
|
10
|
+
}
|
|
11
|
+
type FormListTabsValidationListener = (event: FormListTabsValidationEvent) => void;
|
|
12
|
+
export declare const subscribeFormListTabsValidation: (form: FormInstance, listener: FormListTabsValidationListener) => () => void;
|
|
13
|
+
export declare const notifyFormListTabsValidation: (form: FormInstance, event: FormListTabsValidationEvent) => void;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const formListeners = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
const subscribeFormListTabsValidation = (form, listener) => {
|
|
3
|
+
const listeners = formListeners.get(form) || /* @__PURE__ */ new Set();
|
|
4
|
+
listeners.add(listener);
|
|
5
|
+
formListeners.set(form, listeners);
|
|
6
|
+
return () => {
|
|
7
|
+
listeners.delete(listener);
|
|
8
|
+
if (!listeners.size)
|
|
9
|
+
formListeners.delete(form);
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const notifyFormListTabsValidation = (form, event) => {
|
|
13
|
+
var _a;
|
|
14
|
+
(_a = formListeners.get(form)) == null ? void 0 : _a.forEach((listener) => listener(event));
|
|
15
|
+
};
|
|
16
|
+
export {
|
|
17
|
+
notifyFormListTabsValidation,
|
|
18
|
+
subscribeFormListTabsValidation
|
|
19
|
+
};
|
package/es/ProForm/index.js
CHANGED
|
@@ -29,6 +29,7 @@ import locale from "../locale";
|
|
|
29
29
|
import useWatch from "./hooks/useWatch";
|
|
30
30
|
import FormsProvider, { useContextForms, useForms } from "../FormsProvider";
|
|
31
31
|
import { useStep } from "../ProStep";
|
|
32
|
+
import { notifyFormListTabsValidation } from "./components/combination/FormList/tabsValidation";
|
|
32
33
|
const ProForm = (props, ref) => {
|
|
33
34
|
const {
|
|
34
35
|
mode = "search",
|
|
@@ -144,8 +145,20 @@ const ProForm = (props, ref) => {
|
|
|
144
145
|
}
|
|
145
146
|
};
|
|
146
147
|
const handleFinish = () => {
|
|
148
|
+
notifyFormListTabsValidation(form, { reason: "submitSuccess", errorFields: [] });
|
|
147
149
|
onFinish == null ? void 0 : onFinish(form.getFieldsValue());
|
|
148
150
|
};
|
|
151
|
+
const handleFieldsChange = (changedFields, allFields) => {
|
|
152
|
+
var _a;
|
|
153
|
+
notifyFormListTabsValidation(form, {
|
|
154
|
+
reason: "fieldsChange",
|
|
155
|
+
errorFields: allFields.filter((field) => {
|
|
156
|
+
var _a2;
|
|
157
|
+
return (_a2 = field.errors) == null ? void 0 : _a2.length;
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
(_a = otherProps.onFieldsChange) == null ? void 0 : _a.call(otherProps, changedFields, allFields);
|
|
161
|
+
};
|
|
149
162
|
const cls = classnames({
|
|
150
163
|
"pro-form": true,
|
|
151
164
|
"pro-form-view": isView,
|
|
@@ -177,7 +190,7 @@ const ProForm = (props, ref) => {
|
|
|
177
190
|
otherProps.layout = otherProps.layout || "vertical";
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
|
-
const filteredOtherProps = omit(otherProps, ["desensitizationKey"]);
|
|
193
|
+
const filteredOtherProps = omit(otherProps, ["desensitizationKey", "onFieldsChange"]);
|
|
181
194
|
return /* @__PURE__ */ jsxs(
|
|
182
195
|
Form,
|
|
183
196
|
{
|
|
@@ -194,10 +207,12 @@ const ProForm = (props, ref) => {
|
|
|
194
207
|
...omit(config, ["isDiffAll"]),
|
|
195
208
|
...filteredOtherProps,
|
|
196
209
|
labelAlign: labelAlign ?? config.labelAlign ?? "left",
|
|
210
|
+
onFieldsChange: handleFieldsChange,
|
|
197
211
|
onValuesChange: handleValuesChange,
|
|
198
212
|
onFinish: handleFinish,
|
|
199
213
|
onFinishFailed: ({ errorFields }) => {
|
|
200
214
|
var _a;
|
|
215
|
+
notifyFormListTabsValidation(form, { reason: "submitFailed", errorFields });
|
|
201
216
|
if (scrollToError && (errorFields == null ? void 0 : errorFields.length)) {
|
|
202
217
|
form.scrollToField((_a = errorFields[0]) == null ? void 0 : _a.name, {
|
|
203
218
|
block: "center",
|