@zat-design/sisyphus-react 4.6.2 → 4.6.3-beta.1
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 +120 -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,46 @@ 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
|
-
label: /* @__PURE__ */
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
252
|
+
label: /* @__PURE__ */ jsx(SortableTabLabel, { fieldKey, draggable, disabled: disabled || isView, children: /* @__PURE__ */ jsx(
|
|
253
|
+
Badge,
|
|
254
|
+
{
|
|
255
|
+
className: "pro-form-list-tabs-error-badge",
|
|
256
|
+
count: errorCount,
|
|
257
|
+
overflowCount: 99,
|
|
258
|
+
size: "small",
|
|
259
|
+
children: /* @__PURE__ */ jsxs(
|
|
260
|
+
"span",
|
|
261
|
+
{
|
|
262
|
+
className: `pro-form-list-tabs-title-content${errorCount ? " pro-form-list-tabs-title-content-error" : ""}`,
|
|
263
|
+
children: [
|
|
264
|
+
/* @__PURE__ */ jsx("span", { className: "pro-form-list-tabs-title", children: renderedTitle }),
|
|
265
|
+
actionProps !== false && /* @__PURE__ */ jsx(
|
|
266
|
+
ActionButton,
|
|
267
|
+
{
|
|
268
|
+
min,
|
|
269
|
+
max,
|
|
270
|
+
index,
|
|
271
|
+
length: fields.length,
|
|
272
|
+
operation,
|
|
273
|
+
namePath: itemNamePath,
|
|
274
|
+
form,
|
|
275
|
+
field: { ...field, key: fieldKey },
|
|
276
|
+
fields,
|
|
277
|
+
actionProps: tabActionProps,
|
|
278
|
+
mode: "tabs",
|
|
279
|
+
disabled,
|
|
280
|
+
isView
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
) }),
|
|
225
288
|
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
289
|
MemoRenderFields,
|
|
227
290
|
{
|
|
@@ -235,31 +298,45 @@ const TabsFields = (props) => {
|
|
|
235
298
|
) }) })
|
|
236
299
|
};
|
|
237
300
|
});
|
|
238
|
-
return /* @__PURE__ */ jsx(DndContext, { onDragEnd: handleDragEnd, children: /* @__PURE__ */
|
|
301
|
+
return /* @__PURE__ */ jsx(DndContext, { onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsxs(
|
|
239
302
|
SortableContext,
|
|
240
303
|
{
|
|
241
304
|
items: fieldKeys.map((key) => ({ id: key })),
|
|
242
305
|
strategy: horizontalListSortingStrategy,
|
|
243
|
-
children:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
/* @__PURE__ */
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ jsx(
|
|
308
|
+
Tabs,
|
|
309
|
+
{
|
|
310
|
+
className: "pro-form-list-tabs",
|
|
311
|
+
type: "editable-card",
|
|
312
|
+
size: "small",
|
|
313
|
+
activeKey,
|
|
314
|
+
addIcon: /* @__PURE__ */ jsxs("span", { className: "pro-form-list-tabs-add", children: [
|
|
315
|
+
/* @__PURE__ */ jsx(PlusOutlined, {}),
|
|
316
|
+
/* @__PURE__ */ jsx("span", { className: "pro-form-list-tabs-add-label", children: addLabel })
|
|
317
|
+
] }),
|
|
318
|
+
hideAdd,
|
|
319
|
+
items,
|
|
320
|
+
onChange: handleChange,
|
|
321
|
+
onEdit: (_, action) => {
|
|
322
|
+
if (action === "add")
|
|
323
|
+
void handleAdd();
|
|
324
|
+
}
|
|
260
325
|
}
|
|
261
|
-
|
|
262
|
-
|
|
326
|
+
),
|
|
327
|
+
fields.length === 0 && /* @__PURE__ */ jsx(
|
|
328
|
+
Empty,
|
|
329
|
+
{
|
|
330
|
+
disabled: hideAdd,
|
|
331
|
+
toolbarProps: addAction ? [{ ...addAction, show: true }] : false,
|
|
332
|
+
operation,
|
|
333
|
+
form,
|
|
334
|
+
namePath,
|
|
335
|
+
emptyBtnText,
|
|
336
|
+
onAdd: handleAdd
|
|
337
|
+
}
|
|
338
|
+
)
|
|
339
|
+
]
|
|
263
340
|
}
|
|
264
341
|
) });
|
|
265
342
|
};
|
|
@@ -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",
|