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