ding-react-admin 1.0.1 → 1.0.4
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/README.md +107 -2
- package/dist/index.js +712 -723
- package/dist/src/crud/InlineFormSet.d.ts +13 -11
- package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
- package/dist/src/crud/ResourceForm.d.ts +10 -2
- package/dist/src/crud/ResourceForm.d.ts.map +1 -1
- package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
- package/dist/src/crud/ResourceList.d.ts.map +1 -1
- package/dist/src/crud/context/SubmitFieldsContext.d.ts +18 -0
- package/dist/src/crud/context/SubmitFieldsContext.d.ts.map +1 -0
- package/dist/src/crud/fields/BooleanField.d.ts +3 -1
- package/dist/src/crud/fields/BooleanField.d.ts.map +1 -1
- package/dist/src/crud/fields/DateField.d.ts +3 -1
- package/dist/src/crud/fields/DateField.d.ts.map +1 -1
- package/dist/src/crud/fields/FieldWrapper.d.ts +7 -14
- package/dist/src/crud/fields/FieldWrapper.d.ts.map +1 -1
- package/dist/src/crud/fields/NumberField.d.ts +5 -3
- package/dist/src/crud/fields/NumberField.d.ts.map +1 -1
- package/dist/src/crud/fields/PasswordField.d.ts +5 -3
- package/dist/src/crud/fields/PasswordField.d.ts.map +1 -1
- package/dist/src/crud/fields/ReferenceField.d.ts +6 -5
- package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
- package/dist/src/crud/fields/ReferenceManyField.d.ts +3 -1
- package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
- package/dist/src/crud/fields/SelectField.d.ts +3 -1
- package/dist/src/crud/fields/SelectField.d.ts.map +1 -1
- package/dist/src/crud/fields/TextField.d.ts +5 -3
- package/dist/src/crud/fields/TextField.d.ts.map +1 -1
- package/dist/src/crud/index.d.ts +8 -4
- package/dist/src/crud/index.d.ts.map +1 -1
- package/dist/src/crud/types.d.ts +21 -20
- package/dist/src/crud/types.d.ts.map +1 -1
- package/dist/src/crud/utils/inlineFieldName.d.ts +3 -0
- package/dist/src/crud/utils/inlineFieldName.d.ts.map +1 -0
- package/dist/src/crud/utils/useAbortableEffect.d.ts +7 -0
- package/dist/src/crud/utils/useAbortableEffect.d.ts.map +1 -0
- package/dist/src/crud/utils/useResourceFormData.d.ts +47 -0
- package/dist/src/crud/utils/useResourceFormData.d.ts.map +1 -0
- package/dist/src/data/abortError.d.ts +3 -0
- package/dist/src/data/abortError.d.ts.map +1 -0
- package/dist/src/data/abortError.test.d.ts +2 -0
- package/dist/src/data/abortError.test.d.ts.map +1 -0
- package/dist/src/data/createRestResourceHandlers.d.ts +2 -2
- package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
- package/dist/src/data/dataProviderTypes.d.ts +7 -1
- package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
- package/dist/src/data/resourceHandlers.d.ts +2 -2
- package/dist/src/data/resourceHandlers.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/docs/assets/form-with-inline.png +0 -0
- package/package.json +59 -59
- package/src/crud/InlineFormSet.tsx +77 -89
- package/src/crud/ResourceForm.tsx +87 -202
- package/src/crud/ResourceFormModal.tsx +37 -25
- package/src/crud/ResourceList.tsx +35 -26
- package/src/crud/context/SubmitFieldsContext.tsx +62 -0
- package/src/crud/fields/BooleanField.tsx +22 -16
- package/src/crud/fields/DateField.tsx +32 -26
- package/src/crud/fields/FieldWrapper.tsx +32 -54
- package/src/crud/fields/NumberField.tsx +36 -34
- package/src/crud/fields/PasswordField.tsx +41 -40
- package/src/crud/fields/ReferenceField.tsx +35 -130
- package/src/crud/fields/ReferenceManyField.tsx +31 -25
- package/src/crud/fields/SelectField.tsx +6 -0
- package/src/crud/fields/TextField.tsx +32 -30
- package/src/crud/index.ts +19 -2
- package/src/crud/types.ts +132 -130
- package/src/crud/utils/inlineFieldName.ts +8 -0
- package/src/crud/utils/useAbortableEffect.ts +17 -0
- package/src/crud/utils/useResourceFormData.ts +231 -0
- package/src/data/abortError.test.ts +19 -0
- package/src/data/abortError.ts +10 -0
- package/src/data/createMemoryResourceHandlers.ts +1 -1
- package/src/data/createRestResourceHandlers.ts +4 -4
- package/src/data/dataProviderTypes.ts +8 -0
- package/src/data/resourceHandlers.ts +4 -3
- package/src/index.ts +14 -1
- package/dist/src/crud/context/FormFieldsContext.d.ts +0 -14
- package/dist/src/crud/context/FormFieldsContext.d.ts.map +0 -1
- package/dist/src/crud/context/InlineFormContext.d.ts +0 -16
- package/dist/src/crud/context/InlineFormContext.d.ts.map +0 -1
- package/dist/src/crud/fields/useInlineOrFormField.d.ts +0 -10
- package/dist/src/crud/fields/useInlineOrFormField.d.ts.map +0 -1
- package/src/crud/context/FormFieldsContext.tsx +0 -76
- package/src/crud/context/InlineFormContext.tsx +0 -71
- package/src/crud/fields/useInlineOrFormField.tsx +0 -91
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { MessageInstance } from "antd/es/message/interface";
|
|
2
|
+
import {
|
|
3
|
+
useCallback,
|
|
4
|
+
useState,
|
|
5
|
+
type MutableRefObject,
|
|
6
|
+
} from "react";
|
|
7
|
+
import type { NavigateFunction } from "react-router-dom";
|
|
8
|
+
import type { DataProvider } from "../../data/dataProviderTypes";
|
|
9
|
+
import { isAbortError } from "../../data/abortError";
|
|
10
|
+
import {
|
|
11
|
+
type DefaultValues,
|
|
12
|
+
type FieldValues,
|
|
13
|
+
type UseFormReturn,
|
|
14
|
+
} from "react-hook-form";
|
|
15
|
+
import {
|
|
16
|
+
loadInlineRows,
|
|
17
|
+
saveInlineRows,
|
|
18
|
+
type InlineFormSetProps,
|
|
19
|
+
} from "../InlineFormSet";
|
|
20
|
+
import { inlineArrayName } from "./inlineArrayName";
|
|
21
|
+
import { pickBySources } from "./pickBySources";
|
|
22
|
+
import { parseAndApplyFormErrors } from "./formErrors";
|
|
23
|
+
import { useAbortableEffect } from "./useAbortableEffect";
|
|
24
|
+
|
|
25
|
+
export type ResourceFormInlineConfig = Pick<
|
|
26
|
+
InlineFormSetProps,
|
|
27
|
+
"resource" | "foreignKey" | "name"
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export function resolveInlineArrayName(config: ResourceFormInlineConfig) {
|
|
31
|
+
return inlineArrayName(config.resource, config.name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type LoadOptions<T extends FieldValues> = {
|
|
35
|
+
dp: DataProvider;
|
|
36
|
+
resource: string;
|
|
37
|
+
id: string | undefined;
|
|
38
|
+
isNew: boolean;
|
|
39
|
+
form: UseFormReturn<T>;
|
|
40
|
+
message: MessageInstance;
|
|
41
|
+
defaultValues?: Partial<T>;
|
|
42
|
+
inlines?: ResourceFormInlineConfig[];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Edit mode: fetch parent (+ inline rows) → form.reset → bump formVersion for field arrays. */
|
|
46
|
+
export function useResourceFormLoad<T extends FieldValues>({
|
|
47
|
+
dp,
|
|
48
|
+
resource,
|
|
49
|
+
id,
|
|
50
|
+
isNew,
|
|
51
|
+
form,
|
|
52
|
+
message,
|
|
53
|
+
defaultValues,
|
|
54
|
+
inlines,
|
|
55
|
+
}: LoadOptions<T>) {
|
|
56
|
+
const [loading, setLoading] = useState(!isNew);
|
|
57
|
+
const [formVersion, setFormVersion] = useState(0);
|
|
58
|
+
const [existingInlineIds, setExistingInlineIds] = useState<
|
|
59
|
+
Record<string, (string | number)[]>
|
|
60
|
+
>({});
|
|
61
|
+
|
|
62
|
+
const load = useCallback(
|
|
63
|
+
async (signal?: AbortSignal) => {
|
|
64
|
+
if (isNew || !id) {
|
|
65
|
+
if (defaultValues) {
|
|
66
|
+
form.reset({ ...defaultValues } as DefaultValues<T>);
|
|
67
|
+
}
|
|
68
|
+
setLoading(false);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setLoading(true);
|
|
72
|
+
try {
|
|
73
|
+
const res = await dp.getOne(resource, id, { signal });
|
|
74
|
+
if (signal?.aborted) return;
|
|
75
|
+
const record = res.data as DefaultValues<T>;
|
|
76
|
+
if (inlines?.length) {
|
|
77
|
+
const merged = { ...record } as DefaultValues<T>;
|
|
78
|
+
const idsMap: Record<string, (string | number)[]> = {};
|
|
79
|
+
for (const cfg of inlines) {
|
|
80
|
+
const arrayName = resolveInlineArrayName(cfg);
|
|
81
|
+
const { rows, ids } = await loadInlineRows(
|
|
82
|
+
dp,
|
|
83
|
+
cfg.resource,
|
|
84
|
+
cfg.foreignKey,
|
|
85
|
+
id,
|
|
86
|
+
);
|
|
87
|
+
(merged as Record<string, unknown>)[arrayName] = rows;
|
|
88
|
+
idsMap[arrayName] = ids;
|
|
89
|
+
}
|
|
90
|
+
if (signal?.aborted) return;
|
|
91
|
+
form.reset(merged);
|
|
92
|
+
setExistingInlineIds(idsMap);
|
|
93
|
+
setFormVersion((v) => v + 1);
|
|
94
|
+
} else {
|
|
95
|
+
form.reset(record);
|
|
96
|
+
setFormVersion((v) => v + 1);
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
if (!isAbortError(e)) {
|
|
100
|
+
message.error(e instanceof Error ? e.message : "Load failed");
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
if (!signal?.aborted) setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
[dp, resource, id, isNew, form, message, defaultValues, inlines],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
useAbortableEffect((signal) => load(signal), [load]);
|
|
110
|
+
|
|
111
|
+
return { loading, formVersion, existingInlineIds };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type SubmitOptions<T extends FieldValues & { id?: unknown }> = {
|
|
115
|
+
dp: DataProvider;
|
|
116
|
+
resource: string;
|
|
117
|
+
id: string | undefined;
|
|
118
|
+
isNew: boolean;
|
|
119
|
+
form: UseFormReturn<T>;
|
|
120
|
+
message: MessageInstance;
|
|
121
|
+
navigate: NavigateFunction;
|
|
122
|
+
listPath: string;
|
|
123
|
+
submitFieldsRef: MutableRefObject<Set<string>>;
|
|
124
|
+
inlines?: ResourceFormInlineConfig[];
|
|
125
|
+
existingInlineIds: Record<string, (string | number)[]>;
|
|
126
|
+
onSaved?: (record: T) => void;
|
|
127
|
+
stayOnPage?: boolean;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** Save: pickBySources → create/update parent → saveInlineRows for each inline config. */
|
|
131
|
+
export function useResourceFormSubmit<T extends FieldValues & { id?: unknown }>({
|
|
132
|
+
dp,
|
|
133
|
+
resource,
|
|
134
|
+
id,
|
|
135
|
+
isNew,
|
|
136
|
+
form,
|
|
137
|
+
message,
|
|
138
|
+
navigate,
|
|
139
|
+
listPath,
|
|
140
|
+
submitFieldsRef,
|
|
141
|
+
inlines,
|
|
142
|
+
existingInlineIds,
|
|
143
|
+
onSaved,
|
|
144
|
+
stayOnPage,
|
|
145
|
+
}: SubmitOptions<T>) {
|
|
146
|
+
return useCallback(
|
|
147
|
+
async (values: T) => {
|
|
148
|
+
try {
|
|
149
|
+
const raw = values as Record<string, unknown>;
|
|
150
|
+
let payload = pickBySources(raw, Array.from(submitFieldsRef.current));
|
|
151
|
+
if (inlines?.length) {
|
|
152
|
+
for (const cfg of inlines) {
|
|
153
|
+
delete payload[resolveInlineArrayName(cfg)];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let saved: T;
|
|
158
|
+
if (isNew) {
|
|
159
|
+
const res = await dp.create(resource, payload);
|
|
160
|
+
saved = res.data as T;
|
|
161
|
+
message.success("Created");
|
|
162
|
+
} else if (id) {
|
|
163
|
+
const res = await dp.update(resource, { id, data: payload });
|
|
164
|
+
saved = res.data as T;
|
|
165
|
+
message.success("Updated");
|
|
166
|
+
} else {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parentId = saved.id as string | number;
|
|
171
|
+
if (inlines?.length && parentId != null) {
|
|
172
|
+
for (const cfg of inlines) {
|
|
173
|
+
const arrayName = resolveInlineArrayName(cfg);
|
|
174
|
+
const rows =
|
|
175
|
+
((form.getValues(arrayName as never) as unknown) as
|
|
176
|
+
| Record<string, unknown>[]
|
|
177
|
+
| undefined) ?? [];
|
|
178
|
+
const inlineOk = await saveInlineRows(dp, {
|
|
179
|
+
resource: cfg.resource,
|
|
180
|
+
foreignKey: cfg.foreignKey,
|
|
181
|
+
parentId,
|
|
182
|
+
rows,
|
|
183
|
+
existingIds: existingInlineIds[arrayName] ?? [],
|
|
184
|
+
onRowError: (e, index) => {
|
|
185
|
+
const row = rows[index];
|
|
186
|
+
const rowId = row?.id as string | number | undefined;
|
|
187
|
+
return parseAndApplyFormErrors(dp, form, message, e, {
|
|
188
|
+
resource: cfg.resource,
|
|
189
|
+
mutation:
|
|
190
|
+
rowId != null &&
|
|
191
|
+
(existingInlineIds[arrayName] ?? []).some(
|
|
192
|
+
(existingId) => existingId === rowId,
|
|
193
|
+
)
|
|
194
|
+
? "update"
|
|
195
|
+
: "create",
|
|
196
|
+
inlineArrayName: arrayName,
|
|
197
|
+
rowIndex: index,
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
if (!inlineOk) return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
onSaved?.(saved);
|
|
206
|
+
if (!stayOnPage) navigate(listPath);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
parseAndApplyFormErrors(dp, form, message, e, {
|
|
209
|
+
resource,
|
|
210
|
+
mutation: isNew ? "create" : "update",
|
|
211
|
+
});
|
|
212
|
+
message.error(e instanceof Error ? e.message : "Save failed");
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
[
|
|
216
|
+
dp,
|
|
217
|
+
resource,
|
|
218
|
+
id,
|
|
219
|
+
isNew,
|
|
220
|
+
form,
|
|
221
|
+
message,
|
|
222
|
+
navigate,
|
|
223
|
+
listPath,
|
|
224
|
+
submitFieldsRef,
|
|
225
|
+
inlines,
|
|
226
|
+
existingInlineIds,
|
|
227
|
+
onSaved,
|
|
228
|
+
stayOnPage,
|
|
229
|
+
],
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { isAbortError } from "./abortError";
|
|
3
|
+
|
|
4
|
+
describe("isAbortError", () => {
|
|
5
|
+
it("detects DOM AbortError", () => {
|
|
6
|
+
expect(isAbortError({ name: "AbortError" })).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("detects axios CanceledError", () => {
|
|
10
|
+
expect(isAbortError({ name: "CanceledError", code: "ERR_CANCELED" })).toBe(
|
|
11
|
+
true,
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("returns false for ordinary errors", () => {
|
|
16
|
+
expect(isAbortError(new Error("Network Error"))).toBe(false);
|
|
17
|
+
expect(isAbortError(null)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** True when a fetch/axios call was aborted via `AbortSignal` (not a real failure). */
|
|
2
|
+
export function isAbortError(error: unknown): boolean {
|
|
3
|
+
if (error == null || typeof error !== "object") return false;
|
|
4
|
+
const e = error as { name?: string; code?: string };
|
|
5
|
+
return (
|
|
6
|
+
e.name === "AbortError" ||
|
|
7
|
+
e.name === "CanceledError" ||
|
|
8
|
+
e.code === "ERR_CANCELED"
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -48,7 +48,7 @@ export function createMemoryResourceHandlers<
|
|
|
48
48
|
return applyInMemoryListParams(asRows(base), params) as GetListResult<T>;
|
|
49
49
|
},
|
|
50
50
|
|
|
51
|
-
async getOne(id): Promise<GetOneResult<T>> {
|
|
51
|
+
async getOne(id, _params?): Promise<GetOneResult<T>> {
|
|
52
52
|
return { data: getById(config.getRows(), id) as T };
|
|
53
53
|
},
|
|
54
54
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { GetListParams, Identifier } from "./dataProviderTypes";
|
|
1
|
+
import type { GetListParams, GetOneParams, Identifier } from "./dataProviderTypes";
|
|
2
2
|
import type { ResourceHandlers } from "./resourceHandlers";
|
|
3
3
|
|
|
4
4
|
export type RestResourceHandlersConfig<
|
|
5
5
|
T extends Record<string, unknown> = Record<string, unknown>,
|
|
6
6
|
> = {
|
|
7
7
|
list: (params: GetListParams) => Promise<{ data: T[]; total: number }>;
|
|
8
|
-
retrieve: (id: Identifier) => Promise<T>;
|
|
8
|
+
retrieve: (id: Identifier, params?: GetOneParams) => Promise<T>;
|
|
9
9
|
create: (data: Record<string, unknown>) => Promise<T>;
|
|
10
10
|
update: (id: Identifier, data: Record<string, unknown>) => Promise<T>;
|
|
11
11
|
destroy: (id: Identifier) => Promise<void>;
|
|
@@ -27,8 +27,8 @@ export function createRestResourceHandlers<
|
|
|
27
27
|
return config.list(params);
|
|
28
28
|
},
|
|
29
29
|
|
|
30
|
-
async getOne(id) {
|
|
31
|
-
return { data: await config.retrieve(id) };
|
|
30
|
+
async getOne(id, params) {
|
|
31
|
+
return { data: await config.retrieve(id, params) };
|
|
32
32
|
},
|
|
33
33
|
|
|
34
34
|
async create(data) {
|
|
@@ -13,12 +13,19 @@ export type PaginationParams = {
|
|
|
13
13
|
perPage: number;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
export type GetOneParams = {
|
|
17
|
+
/** Forward to fetch/axios to cancel in-flight reads when the UI unmounts or deps change. */
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
};
|
|
20
|
+
|
|
16
21
|
export type GetListParams = {
|
|
17
22
|
pagination?: PaginationParams;
|
|
18
23
|
/** Single or multi-column sort. */
|
|
19
24
|
sort?: SortSpec | SortSpec[];
|
|
20
25
|
/** Filter values; arrays mean "match any of" for that field. */
|
|
21
26
|
filter?: Record<string, unknown>;
|
|
27
|
+
/** Forward to fetch/axios to cancel in-flight reads when the UI unmounts or deps change. */
|
|
28
|
+
signal?: AbortSignal;
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
export type GetListResult<RecordType extends Record<string, unknown>> = {
|
|
@@ -83,6 +90,7 @@ export type DataProvider<
|
|
|
83
90
|
getOne: (
|
|
84
91
|
resource: string,
|
|
85
92
|
id: Identifier,
|
|
93
|
+
params?: GetOneParams,
|
|
86
94
|
) => Promise<GetOneResult<RecordType>>;
|
|
87
95
|
create: (
|
|
88
96
|
resource: string,
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
DeleteResult,
|
|
8
8
|
GetListParams,
|
|
9
9
|
GetListResult,
|
|
10
|
+
GetOneParams,
|
|
10
11
|
GetOneResult,
|
|
11
12
|
Identifier,
|
|
12
13
|
ParseFormError,
|
|
@@ -27,7 +28,7 @@ export type ResourceHandlers<
|
|
|
27
28
|
RecordType extends Record<string, unknown> = Record<string, unknown>,
|
|
28
29
|
> = {
|
|
29
30
|
getList: (params: GetListParams) => Promise<GetListResult<RecordType>>;
|
|
30
|
-
getOne: (id: Identifier) => Promise<GetOneResult<RecordType>>;
|
|
31
|
+
getOne: (id: Identifier, params?: GetOneParams) => Promise<GetOneResult<RecordType>>;
|
|
31
32
|
create: (data: Partial<RecordType>) => Promise<CreateResult<RecordType>>;
|
|
32
33
|
update: (
|
|
33
34
|
params: UpdateParams<RecordType>,
|
|
@@ -95,11 +96,11 @@ export function combineResourceHandlers(
|
|
|
95
96
|
assertPermission(can, permissions, "list");
|
|
96
97
|
return h.getList(params);
|
|
97
98
|
},
|
|
98
|
-
async getOne(resource, id) {
|
|
99
|
+
async getOne(resource, id, params) {
|
|
99
100
|
const { handlers: h, permissions } = resolve(resource);
|
|
100
101
|
guard?.(resource, "read");
|
|
101
102
|
assertPermission(can, permissions, "read");
|
|
102
|
-
return h.getOne(id);
|
|
103
|
+
return h.getOne(id, params);
|
|
103
104
|
},
|
|
104
105
|
async create(resource, data) {
|
|
105
106
|
const { handlers: h, permissions } = resolve(resource);
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export type {
|
|
|
28
28
|
DeleteResult,
|
|
29
29
|
GetListParams,
|
|
30
30
|
GetListResult,
|
|
31
|
+
GetOneParams,
|
|
31
32
|
GetOneResult,
|
|
32
33
|
Identifier,
|
|
33
34
|
PaginationParams,
|
|
@@ -41,6 +42,7 @@ export type {
|
|
|
41
42
|
ParseFormErrorContext,
|
|
42
43
|
} from "./data/dataProviderTypes";
|
|
43
44
|
export { combineResourceHandlers } from "./data/resourceHandlers";
|
|
45
|
+
export { isAbortError } from "./data/abortError";
|
|
44
46
|
export {
|
|
45
47
|
parseDjangoDRFFormErrors,
|
|
46
48
|
parseDotNetFormErrors,
|
|
@@ -118,6 +120,7 @@ export {
|
|
|
118
120
|
ResourceForm,
|
|
119
121
|
ResourceFormModal,
|
|
120
122
|
InlineFormSet,
|
|
123
|
+
InlineFormSetStacked,
|
|
121
124
|
FormTabs,
|
|
122
125
|
FormTab,
|
|
123
126
|
FormSteps,
|
|
@@ -132,6 +135,7 @@ export {
|
|
|
132
135
|
PasswordField,
|
|
133
136
|
ReferenceField,
|
|
134
137
|
ReferenceManyField,
|
|
138
|
+
FieldWrapper,
|
|
135
139
|
TextColumn,
|
|
136
140
|
NumberColumn,
|
|
137
141
|
BooleanColumn,
|
|
@@ -148,17 +152,21 @@ export {
|
|
|
148
152
|
ReferenceManyFilter,
|
|
149
153
|
useListQueryState,
|
|
150
154
|
useChoices,
|
|
155
|
+
useAbortableEffect,
|
|
151
156
|
getByPath,
|
|
152
157
|
pickBySources,
|
|
153
158
|
inlineArrayName,
|
|
159
|
+
inlineFieldName,
|
|
154
160
|
useResourceListContext,
|
|
155
|
-
|
|
161
|
+
useSubmitField,
|
|
162
|
+
useSectionField,
|
|
156
163
|
} from "./crud";
|
|
157
164
|
export type {
|
|
158
165
|
ResourceFormProps,
|
|
159
166
|
ResourceFormInlineConfig,
|
|
160
167
|
ResourceFormModalProps,
|
|
161
168
|
InlineFormSetProps,
|
|
169
|
+
InlineFormSetStackedProps,
|
|
162
170
|
FormTabsProps,
|
|
163
171
|
FormTabProps,
|
|
164
172
|
FormStepsProps,
|
|
@@ -173,6 +181,11 @@ export type {
|
|
|
173
181
|
DisplayProps,
|
|
174
182
|
EditMode,
|
|
175
183
|
FieldRules,
|
|
184
|
+
InlineCellContext,
|
|
185
|
+
InlineColumnDef,
|
|
186
|
+
InlineFormSetBaseProps,
|
|
187
|
+
InlineRowContext,
|
|
188
|
+
FieldWrapperProps,
|
|
176
189
|
ResourceListBuiltInActions,
|
|
177
190
|
ResourceListBulkAction,
|
|
178
191
|
ResourceListBulkActionHelpers,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { MutableRefObject, ReactNode } from 'react';
|
|
2
|
-
type FormFieldsContextValue = {
|
|
3
|
-
registerSource: (source: string) => () => void;
|
|
4
|
-
getSources: () => string[];
|
|
5
|
-
};
|
|
6
|
-
export declare function FormFieldsProvider({ children, sourcesRef: externalSourcesRef, }: {
|
|
7
|
-
children: ReactNode;
|
|
8
|
-
/** Optional ref shared with submit handler outside this provider. */
|
|
9
|
-
sourcesRef?: MutableRefObject<Set<string>>;
|
|
10
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
export declare function useFormFieldsOptional(): FormFieldsContextValue | null;
|
|
12
|
-
export declare function useRegisterFormSource(source: string): void;
|
|
13
|
-
export {};
|
|
14
|
-
//# sourceMappingURL=FormFieldsContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"FormFieldsContext.d.ts","sourceRoot":"","sources":["../../../../src/crud/context/FormFieldsContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,KAAK,sBAAsB,GAAG;IAC5B,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;IAC/C,UAAU,EAAE,MAAM,MAAM,EAAE,CAAC;CAC5B,CAAC;AAIF,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,UAAU,EAAE,kBAAkB,GAC/B,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,qEAAqE;IACrE,UAAU,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAC5C,2CA6BA;AAED,wBAAgB,qBAAqB,kCAEpC;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,QAcnD"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { InlineFieldDefinition } from '../types';
|
|
3
|
-
export type InlineFormSetLayout = "tabular" | "stacked";
|
|
4
|
-
export declare function InlineFormSetProvider({ children, arrayName, layout, }: {
|
|
5
|
-
children: ReactNode;
|
|
6
|
-
arrayName: string;
|
|
7
|
-
layout?: InlineFormSetLayout;
|
|
8
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
export declare function useInlineFormSetContext(): {
|
|
10
|
-
fields: InlineFieldDefinition[];
|
|
11
|
-
registerField: (def: InlineFieldDefinition) => () => void;
|
|
12
|
-
arrayName: string;
|
|
13
|
-
layout: InlineFormSetLayout;
|
|
14
|
-
} | null;
|
|
15
|
-
export declare function useRegisterInlineField(def: InlineFieldDefinition): void;
|
|
16
|
-
//# sourceMappingURL=InlineFormContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InlineFormContext.d.ts","sourceRoot":"","sources":["../../../../src/crud/context/InlineFormContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,CAAC;AAWxD,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACR,SAAS,EACT,MAAkB,GACnB,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B,2CAyBA;AAED,wBAAgB,uBAAuB;;mBA3CtB,CAAC,GAAG,EAAE,qBAAqB,KAAK,MAAM,IAAI;eAC9C,MAAM;YACT,mBAAmB;SA8C5B;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,qBAAqB,QAMhE"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { FieldRenderProps, FieldRules, InlineFieldOptions } from '../types';
|
|
3
|
-
export declare function useInlineOrFormField(source: string, label: string | undefined, required: boolean | undefined, rules: FieldRules | undefined, renderInput: (props: FieldRenderProps) => ReactNode, inlineOptions?: InlineFieldOptions): {
|
|
4
|
-
mode: "inline";
|
|
5
|
-
element?: undefined;
|
|
6
|
-
} | {
|
|
7
|
-
mode: "form";
|
|
8
|
-
element: import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
};
|
|
10
|
-
//# sourceMappingURL=useInlineOrFormField.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useInlineOrFormField.d.ts","sourceRoot":"","sources":["../../../../src/crud/fields/useInlineOrFormField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EACV,gBAAgB,EAChB,UAAU,EAEV,kBAAkB,EACnB,MAAM,UAAU,CAAC;AAGlB,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,EAAE,OAAO,GAAG,SAAS,EAC7B,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,WAAW,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,SAAS,EACnD,aAAa,CAAC,EAAE,kBAAkB;;;;;;EA0EnC"}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
type MutableRefObject,
|
|
9
|
-
type ReactNode,
|
|
10
|
-
} from "react";
|
|
11
|
-
import { useFormSectionSourcesOptional } from "./FormSectionContext";
|
|
12
|
-
|
|
13
|
-
type FormFieldsContextValue = {
|
|
14
|
-
registerSource: (source: string) => () => void;
|
|
15
|
-
getSources: () => string[];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const FormFieldsContext = createContext<FormFieldsContextValue | null>(null);
|
|
19
|
-
|
|
20
|
-
export function FormFieldsProvider({
|
|
21
|
-
children,
|
|
22
|
-
sourcesRef: externalSourcesRef,
|
|
23
|
-
}: {
|
|
24
|
-
children: ReactNode;
|
|
25
|
-
/** Optional ref shared with submit handler outside this provider. */
|
|
26
|
-
sourcesRef?: MutableRefObject<Set<string>>;
|
|
27
|
-
}) {
|
|
28
|
-
const internalSourcesRef = useRef(new Set<string>());
|
|
29
|
-
const sourcesRef = externalSourcesRef ?? internalSourcesRef;
|
|
30
|
-
|
|
31
|
-
const registerSource = useCallback(
|
|
32
|
-
(source: string) => {
|
|
33
|
-
sourcesRef.current.add(source);
|
|
34
|
-
return () => {
|
|
35
|
-
sourcesRef.current.delete(source);
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
[sourcesRef],
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const getSources = useCallback(
|
|
42
|
-
() => Array.from(sourcesRef.current),
|
|
43
|
-
[sourcesRef],
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const value = useMemo(
|
|
47
|
-
() => ({ registerSource, getSources }),
|
|
48
|
-
[registerSource, getSources],
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<FormFieldsContext.Provider value={value}>
|
|
53
|
-
{children}
|
|
54
|
-
</FormFieldsContext.Provider>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function useFormFieldsOptional() {
|
|
59
|
-
return useContext(FormFieldsContext);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function useRegisterFormSource(source: string) {
|
|
63
|
-
const ctx = useFormFieldsOptional();
|
|
64
|
-
const registerSource = ctx?.registerSource;
|
|
65
|
-
const sectionSourcesRef = useFormSectionSourcesOptional();
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (!registerSource) return;
|
|
69
|
-
const unregister = registerSource(source);
|
|
70
|
-
sectionSourcesRef?.current.add(source);
|
|
71
|
-
return () => {
|
|
72
|
-
unregister();
|
|
73
|
-
sectionSourcesRef?.current.delete(source);
|
|
74
|
-
};
|
|
75
|
-
}, [registerSource, sectionSourcesRef, source]);
|
|
76
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useState,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from "react";
|
|
10
|
-
import type { InlineFieldDefinition } from "../types";
|
|
11
|
-
|
|
12
|
-
export type InlineFormSetLayout = "tabular" | "stacked";
|
|
13
|
-
|
|
14
|
-
type InlineFormSetMeta = {
|
|
15
|
-
registerField: (def: InlineFieldDefinition) => () => void;
|
|
16
|
-
arrayName: string;
|
|
17
|
-
layout: InlineFormSetLayout;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const InlineFormSetMetaContext = createContext<InlineFormSetMeta | null>(null);
|
|
21
|
-
const InlineFormSetFieldsContext = createContext<InlineFieldDefinition[]>([]);
|
|
22
|
-
|
|
23
|
-
export function InlineFormSetProvider({
|
|
24
|
-
children,
|
|
25
|
-
arrayName,
|
|
26
|
-
layout = "tabular",
|
|
27
|
-
}: {
|
|
28
|
-
children: ReactNode;
|
|
29
|
-
arrayName: string;
|
|
30
|
-
layout?: InlineFormSetLayout;
|
|
31
|
-
}) {
|
|
32
|
-
const [fields, setFields] = useState<InlineFieldDefinition[]>([]);
|
|
33
|
-
|
|
34
|
-
const registerField = useCallback((def: InlineFieldDefinition) => {
|
|
35
|
-
setFields((prev) => {
|
|
36
|
-
if (prev.some((f) => f.key === def.key)) return prev;
|
|
37
|
-
return [...prev, def];
|
|
38
|
-
});
|
|
39
|
-
return () => {
|
|
40
|
-
setFields((prev) => prev.filter((f) => f.key !== def.key));
|
|
41
|
-
};
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
const meta = useMemo(
|
|
45
|
-
(): InlineFormSetMeta => ({ arrayName, registerField, layout }),
|
|
46
|
-
[arrayName, registerField, layout],
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<InlineFormSetMetaContext.Provider value={meta}>
|
|
51
|
-
<InlineFormSetFieldsContext.Provider value={fields}>
|
|
52
|
-
{children}
|
|
53
|
-
</InlineFormSetFieldsContext.Provider>
|
|
54
|
-
</InlineFormSetMetaContext.Provider>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function useInlineFormSetContext() {
|
|
59
|
-
const meta = useContext(InlineFormSetMetaContext);
|
|
60
|
-
const fields = useContext(InlineFormSetFieldsContext);
|
|
61
|
-
if (!meta) return null;
|
|
62
|
-
return { ...meta, fields };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function useRegisterInlineField(def: InlineFieldDefinition) {
|
|
66
|
-
const registerField = useContext(InlineFormSetMetaContext)?.registerField;
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (!registerField) return;
|
|
69
|
-
return registerField(def);
|
|
70
|
-
}, [registerField, def]);
|
|
71
|
-
}
|