@@ -31,6 +31,25 @@ import type {
3131 TreeValidationResult ,
3232} from './types' ;
3333
34+ /**
35+ * Options that can be specified when submitting a form.
36+ *
37+ * @experimental 21.2.0
38+ */
39+ export interface FormSubmitOptions < TModel > {
40+ /** Function to run when submitting the form data (when form is valid). */
41+ action : ( form : FieldTree < TModel > ) => Promise < TreeValidationResult > ;
42+ /** Function to run when attempting to submit the form data but validation is failing. */
43+ onInvalid ?: ( form : FieldTree < TModel > ) => void ;
44+ /**
45+ * Whether to ignore any of the validators when submitting:
46+ * - 'pending': Will submit if there are no invalid validators, pending validators do not block submission (default)
47+ * - 'none': Will not submit unless all validators are passing, pending validators block submission
48+ * - 'ignore': Will always submit regardless of invalid or pending validators
49+ */
50+ ignoreValidators ?: 'pending' | 'none' | 'all' ;
51+ }
52+
3453/**
3554 * Options that may be specified when creating a form.
3655 *
@@ -43,11 +62,13 @@ export interface FormOptions {
4362 * current [injection context](guide/di/dependency-injection-context), will be used.
4463 */
4564 injector ?: Injector ;
65+ /** The name of the root form, used in generating name attributes for the fields. */
4666 name ?: string ;
4767
4868 /**
4969 * Adapter allows managing fields in a more flexible way.
5070 * Currently this is used to support interop with reactive forms.
71+ * @internal
5172 */
5273 adapter ?: FieldAdapter ;
5374}
@@ -350,42 +371,56 @@ export function applyWhenValue(
350371 * }
351372 *
352373 * const registrationForm = form(signal({username: 'god', password: ''}));
353- * submit(registrationForm, async (f) => {
354- * return registerNewUser(registrationForm);
374+ * submit(registrationForm, {
375+ * action: async (f) => {
376+ * return registerNewUser(registrationForm);
377+ * }
355378 * });
356379 * registrationForm.username().errors(); // [{kind: 'server', message: 'Username already taken'}]
357380 * ```
358381 *
359382 * @param form The field to submit.
360- * @param action An asynchronous action used to submit the field. The action may return submission
361- * errors .
383+ * @param options Options for the submission.
384+ * @returns Whether the submission was successful .
362385 * @template TModel The data type of the field being submitted.
363386 *
364387 * @category submission
365388 * @experimental 21.0.0
366389 */
367390export async function submit < TModel > (
368391 form : FieldTree < TModel > ,
369- action : ( form : FieldTree < TModel > ) => Promise < TreeValidationResult > ,
370- ) {
371- const node = form ( ) as unknown as FieldNode ;
372- const invalid = untracked ( ( ) => {
392+ options : FormSubmitOptions < TModel > ,
393+ ) : Promise < boolean > {
394+ return untracked ( async ( ) => {
395+ const { action, onInvalid} = options ;
396+ const ignoreValidators = options . ignoreValidators ?? 'pending' ;
397+ const node = form ( ) as unknown as FieldNode ;
398+
373399 markAllAsTouched ( node ) ;
374- return node . invalid ( ) ;
375- } ) ;
376400
377- // Fail fast if the form is already invalid.
378- if ( invalid ) {
379- return ;
380- }
401+ // Determine whether or not to run the action based on the current validity.
402+ let shouldRunAction = true ;
403+ if ( ignoreValidators === 'none' ) {
404+ shouldRunAction = node . valid ( ) ;
405+ } else if ( ignoreValidators === 'pending' ) {
406+ shouldRunAction = ! node . invalid ( ) ;
407+ }
381408
382- node . submitState . selfSubmitting . set ( true ) ;
383- try {
384- const errors = await action ( form ) ;
385- errors && setSubmissionErrors ( node , errors ) ;
386- } finally {
387- node . submitState . selfSubmitting . set ( false ) ;
388- }
409+ // Run the action (or alternatively the `onInvalid` callback)
410+ try {
411+ if ( shouldRunAction ) {
412+ node . submitState . selfSubmitting . set ( true ) ;
413+ const errors = await action ( form ) ;
414+ errors && setSubmissionErrors ( node , errors ) ;
415+ return ! errors || ( isArray ( errors ) && errors . length === 0 ) ;
416+ } else if ( onInvalid ) {
417+ onInvalid ( form ) ;
418+ }
419+ return false ;
420+ } finally {
421+ node . submitState . selfSubmitting . set ( false ) ;
422+ }
423+ } ) ;
389424}
390425
391426/**
0 commit comments