1
- import type { ActionFunction, GadgetRecord, GlobalActionFunction } from "@gadgetinc/api-client-core";
1
+ import type { ActionFunction, FieldSelection, GadgetRecord, GlobalActionFunction } from "@gadgetinc/api-client-core";
2
2
import { yupResolver } from "@hookform/resolvers/yup";
3
3
import type { ReactNode } from "react";
4
- import { useEffect, useMemo, useRef } from "react";
4
+ import React, { useEffect, useMemo, useRef } from "react";
5
5
import type { AnyActionWithId, RecordIdentifier, UseActionFormHookStateData } from "src/use-action-form/types.js";
6
6
import type { GadgetObjectFieldConfig } from "../internal/gql/graphql.js";
7
7
import type { FieldMetadata, GlobalActionMetadata, ModelWithOneActionMetadata } from "../metadata.js";
8
- import { FieldType, filterAutoFormFieldList, isModelActionMetadata, useActionMetadata } from "../metadata.js";
8
+ import { FieldType, buildAutoFormFieldList, isModelActionMetadata, useActionMetadata } from "../metadata.js";
9
+ import { pathListToSelection } from "../use-table/helpers.js";
9
10
import type { FieldErrors, FieldValues } from "../useActionForm.js";
10
11
import { useActionForm } from "../useActionForm.js";
11
12
import { get, getFlattenedObjectKeys, type OptionsType } from "../utils.js";
@@ -16,6 +17,7 @@ import {
16
17
validateTriggersFromApiClient,
17
18
validateTriggersFromMetadata,
18
19
} from "./AutoFormActionValidators.js";
20
+ import { isAutoInput } from "./AutoInput.js";
19
21
20
22
/** The props that any <AutoForm/> component accepts */
21
23
export type AutoFormProps<
@@ -89,22 +91,22 @@ export const useFormFields = (
89
91
: [];
90
92
const nonObjectFields = action.inputFields.filter((field) => field.configuration.__typename !== "GadgetObjectFieldConfig");
91
93
92
- const includedRootLevelFields = filterAutoFormFieldList (nonObjectFields, options as any).map(
93
- (field) =>
94
+ const includedRootLevelFields = buildAutoFormFieldList (nonObjectFields, options as any).map(
95
+ ([path, field] ) =>
94
96
({
95
- path: field.apiIdentifier ,
97
+ path,
96
98
metadata: field,
97
99
} as const)
98
100
);
99
101
100
102
const includedObjectFields = objectFields.flatMap((objectField) =>
101
- filterAutoFormFieldList ((objectField.configuration as unknown as GadgetObjectFieldConfig).fields as any, {
103
+ buildAutoFormFieldList ((objectField.configuration as unknown as GadgetObjectFieldConfig).fields as any, {
102
104
...(options as any),
103
105
isUpsertAction: true, // For upsert meta-actions, we allow IDs, and they are object fields instead of root level
104
106
}).map(
105
- (innerField) =>
107
+ ([innerPath, innerField] ) =>
106
108
({
107
- path: `${objectField.apiIdentifier}.${innerField.apiIdentifier }`,
109
+ path: `${objectField.apiIdentifier}.${innerPath }`,
108
110
metadata: innerField,
109
111
} as const)
110
112
)
@@ -120,6 +122,19 @@ export const useFormFields = (
120
122
}, [metadata, options]);
121
123
};
122
124
125
+ export const useFormSelection = (
126
+ modelApiIdentifier: string | undefined,
127
+ fields: readonly { path: string; metadata: FieldMetadata }[]
128
+ ): FieldSelection | undefined => {
129
+ if (!modelApiIdentifier) return;
130
+ if (!fields.length) return;
131
+
132
+ const paths = fields.map((f) => f.path.replace(new RegExp(`^${modelApiIdentifier}\\.`), ""));
133
+ const fieldMetaData = fields.map((f) => f.metadata);
134
+
135
+ return pathListToSelection(paths, fieldMetaData);
136
+ };
137
+
123
138
const validateFormFieldApiIdentifierUniqueness = (actionApiIdentifier: string, inputApiIdentifiers: string[]) => {
124
139
const seen = new Set<string>();
125
140
@@ -142,7 +157,15 @@ export const useAutoForm = <
142
157
>(
143
158
props: AutoFormProps<GivenOptions, SchemaT, ActionFunc, any, any> & { findBy?: any }
144
159
) => {
145
- const { action, record, onSuccess, onFailure, findBy } = props;
160
+ const { action, record, onSuccess, onFailure, findBy, children } = props;
161
+
162
+ let include = props.include;
163
+ let exclude = props.exclude;
164
+
165
+ if (children) {
166
+ include = extractPathsFromChildren(children);
167
+ exclude = undefined;
168
+ }
146
169
147
170
validateNonBulkAction(action);
148
171
validateTriggersFromApiClient(action);
@@ -152,12 +175,13 @@ export const useAutoForm = <
152
175
validateTriggersFromMetadata(metadata);
153
176
154
177
// filter down the fields to render only what we want to render for this form
155
- const fields = useFormFields(metadata, props );
178
+ const fields = useFormFields(metadata, { include, exclude } );
156
179
validateFindByObjectWithMetadata(fields, findBy);
157
180
const isDeleteAction = metadata && isModelActionMetadata(metadata) && metadata.action.isDeleteAction;
158
181
const isGlobalAction = action.type === "globalAction";
159
182
const operatesWithRecordId = !!(metadata && isModelActionMetadata(metadata) && metadata.action.operatesWithRecordIdentity);
160
183
const modelApiIdentifier = action.type == "action" ? action.modelApiIdentifier : undefined;
184
+ const selection = useFormSelection(modelApiIdentifier, fields);
161
185
const isUpsertMetaAction =
162
186
metadata && isModelActionMetadata(metadata) && fields.some((field) => field.metadata.fieldType === FieldType.Id);
163
187
const isUpsertWithFindBy = isUpsertMetaAction && !!findBy;
@@ -201,6 +225,8 @@ export const useAutoForm = <
201
225
defaultValues: defaultValues as any,
202
226
findBy: "findBy" in props ? props.findBy : undefined,
203
227
throwOnInvalidFindByObject: false,
228
+ pause: "findBy" in props ? fetchingMetadata : undefined,
229
+ select: selection as any,
204
230
resolver: useValidationResolver(metadata, fieldPathsToValidate),
205
231
send: () => {
206
232
const fieldsToSend = fields
@@ -282,6 +308,44 @@ export const useAutoForm = <
282
308
};
283
309
};
284
310
311
+ const extractPathsFromChildren = (children: React.ReactNode) => {
312
+ const paths = new Set<string>();
313
+
314
+ React.Children.forEach(children, (child) => {
315
+ if (React.isValidElement(child)) {
316
+ const grandChildren = child.props.children as React.ReactNode | undefined;
317
+ let childPaths: string[] = [];
318
+
319
+ if (grandChildren) {
320
+ childPaths = extractPathsFromChildren(grandChildren);
321
+ }
322
+
323
+ let field: string | undefined = undefined;
324
+
325
+ if (isAutoInput(child)) {
326
+ const props = child.props as { field: string; selectPaths?: string[]; children?: React.ReactNode };
327
+ field = props.field;
328
+
329
+ paths.add(field);
330
+
331
+ if (props.selectPaths && Array.isArray(props.selectPaths)) {
332
+ props.selectPaths.forEach((selectPath) => {
333
+ paths.add(`${field}.${selectPath}`);
334
+ });
335
+ }
336
+ }
337
+
338
+ if (childPaths.length > 0) {
339
+ for (const childPath of childPaths) {
340
+ paths.add(field ? `${field}.${childPath}` : childPath);
341
+ }
342
+ }
343
+ }
344
+ });
345
+
346
+ return Array.from(paths);
347
+ };
348
+
285
349
const removeIdFieldsUnlessUpsertWithoutFindBy = (isUpsertWithFindBy?: boolean) => {
286
350
return (field: { metadata: FieldMetadata }) => {
287
351
return field.metadata.fieldType === FieldType.Id ? !isUpsertWithFindBy : true;
0 commit comments