@@ -24,9 +24,11 @@ import {FieldPathNode} from '../schema/path_node';
2424import { assertPathIsCurrent , SchemaImpl } from '../schema/schema' ;
2525import { normalizeFormArgs } from '../util/normalize_form_args' ;
2626import { isArray } from '../util/type_guards' ;
27- import type { ValidationError } from './rules/validation/validation_errors ' ;
27+ import type { ValidationError } from './rules' ;
2828import type {
29+ FieldState ,
2930 FieldTree ,
31+ FormSubmitOptions ,
3032 ItemType ,
3133 LogicFn ,
3234 OneOrMany ,
@@ -35,28 +37,8 @@ import type {
3537 SchemaFn ,
3638 SchemaOrSchemaFn ,
3739 SchemaPath ,
38- TreeValidationResult ,
3940} from './types' ;
4041
41- /**
42- * Options that can be specified when submitting a form.
43- *
44- * @experimental 21.2.0
45- */
46- export interface FormSubmitOptions < TModel > {
47- /** Function to run when submitting the form data (when form is valid). */
48- action : ( form : FieldTree < TModel > ) => Promise < TreeValidationResult > ;
49- /** Function to run when attempting to submit the form data but validation is failing. */
50- onInvalid ?: ( form : FieldTree < TModel > ) => void ;
51- /**
52- * Whether to ignore any of the validators when submitting:
53- * - 'pending': Will submit if there are no invalid validators, pending validators do not block submission (default)
54- * - 'none': Will not submit unless all validators are passing, pending validators block submission
55- * - 'ignore': Will always submit regardless of invalid or pending validators
56- */
57- ignoreValidators ?: 'pending' | 'none' | 'all' ;
58- }
59-
6042/**
6143 * Options that may be specified when creating a form.
6244 *
@@ -72,7 +54,7 @@ export interface FormOptions<TModel> {
7254 /** The name of the root form, used in generating name attributes for the fields. */
7355 name ?: string ;
7456 /** Options that define how to handle form submission. */
75- submission ?: FormSubmitOptions < TModel > ;
57+ submission ?: FormSubmitOptions < TModel , unknown > ;
7658
7759 /**
7860 * Adapter allows managing fields in a more flexible way.
@@ -216,7 +198,7 @@ export function form<TModel>(...args: any[]): FieldTree<TModel> {
216198 const fieldManager = new FormFieldManager (
217199 injector ,
218200 options ?. name ,
219- options ?. submission as FormSubmitOptions < unknown > | undefined ,
201+ options ?. submission as FormSubmitOptions < unknown , unknown > | undefined ,
220202 ) ;
221203 const adapter = options ?. adapter ?? new BasicFieldAdapter ( ) ;
222204 const fieldRoot = FieldNode . newRoot ( fieldManager , model , pathNode , adapter ) ;
@@ -402,35 +384,39 @@ export function applyWhenValue(
402384 */
403385export async function submit < TModel > (
404386 form : FieldTree < TModel > ,
405- options ?: FormSubmitOptions < TModel > ,
387+ options ?: NoInfer < FormSubmitOptions < unknown , TModel > > ,
406388) : Promise < boolean > ;
407389export async function submit < TModel > (
408390 form : FieldTree < TModel > ,
409- action : FormSubmitOptions < TModel > [ 'action' ] ,
391+ action : NoInfer < FormSubmitOptions < unknown , TModel > [ 'action' ] > ,
410392) : Promise < boolean > ;
411393export async function submit < TModel > (
412394 form : FieldTree < TModel > ,
413- options ?: FormSubmitOptions < TModel > | FormSubmitOptions < TModel > [ 'action' ] ,
395+ options ?: FormSubmitOptions < unknown , TModel > | FormSubmitOptions < unknown , TModel > [ 'action' ] ,
414396) : Promise < boolean > {
415- const node = form ( ) as unknown as FieldNode ;
416- const opts =
397+ const node = untracked ( form ) as FieldState < unknown > as FieldNode ;
398+
399+ const field = options === undefined ? node . structure . root . fieldProxy : form ;
400+ const detail = { root : node . structure . root . fieldProxy , submitted : form } ;
401+
402+ // Normalize options.
403+ options =
417404 typeof options === 'function'
418405 ? { action : options }
419- : ( {
420- ...( node . structure . fieldManager . submitOptions ?? { } ) ,
421- ...( options ?? { } ) ,
422- } as Partial < FormSubmitOptions < TModel > > ) ;
423- const action = opts ?. action ;
406+ : ( options ?? node . structure . fieldManager . submitOptions ) ;
407+
408+ // Verify that an action was provided.
409+ const action = options ?. action as FormSubmitOptions < unknown , unknown > [ 'action' ] ;
424410 if ( ! action ) {
425411 throw new RuntimeError (
426412 RuntimeErrorCode . MISSING_SUBMIT_ACTION ,
427- ngDevMode &&
413+ ( typeof ngDevMode === 'undefined' || ngDevMode ) &&
428414 'Cannot submit form with no submit action. Specify the action when creating the form, or as an additional argument to `submit()`.' ,
429415 ) ;
430416 }
431417
432- const onInvalid = opts ?. onInvalid ;
433- const ignoreValidators = opts ?. ignoreValidators ?? 'pending' ;
418+ const onInvalid = options ?. onInvalid as FormSubmitOptions < unknown , unknown > [ 'onInvalid' ] ;
419+ const ignoreValidators = options ?. ignoreValidators ?? 'pending' ;
434420
435421 // Determine whether or not to run the action based on the current validity.
436422 let shouldRunAction = true ;
@@ -448,18 +434,45 @@ export async function submit<TModel>(
448434 try {
449435 if ( shouldRunAction ) {
450436 node . submitState . selfSubmitting . set ( true ) ;
451- const errors = await untracked ( ( ) => action ?.( form ) ) ;
437+ const errors = await untracked ( ( ) => action ?.( field , detail ) ) ;
452438 errors && setSubmissionErrors ( node , errors ) ;
453439 return ! errors || ( isArray ( errors ) && errors . length === 0 ) ;
454440 } else {
455- untracked ( ( ) => onInvalid ?.( form ) ) ;
441+ untracked ( ( ) => onInvalid ?.( field , detail ) ) ;
456442 }
457443 return false ;
458444 } finally {
459445 node . submitState . selfSubmitting . set ( false ) ;
460446 }
461447}
462448
449+ /**
450+ * Creates a `Schema` that adds logic rules to a form.
451+ * @param fn A **non-reactive** function that sets up reactive logic rules for the form.
452+ * @returns A schema object that implements the given logic.
453+ * @template TValue The value type of a `FieldTree` that this schema binds to.
454+ *
455+ * @category structure
456+ * @experimental 21.0.0
457+ */
458+ export function schema < TValue > ( fn : SchemaFn < TValue > ) : Schema < TValue > {
459+ return SchemaImpl . create ( fn ) as unknown as Schema < TValue > ;
460+ }
461+
462+ /** Marks a {@link node} and its descendants as touched. */
463+ function markAllAsTouched ( node : FieldNode ) {
464+ // Don't mark hidden, disabled, or readonly fields as touched since they don't contribute to the
465+ // form's validity. This also prevents errors from appearing immediately if they're later made
466+ // interactive.
467+ if ( node . validationState . shouldSkipValidation ( ) ) {
468+ return ;
469+ }
470+ node . markAsTouched ( ) ;
471+ for ( const child of node . structure . children ( ) ) {
472+ markAllAsTouched ( child ) ;
473+ }
474+ }
475+
463476/**
464477 * Sets a list of submission errors to their individual fields.
465478 *
@@ -488,30 +501,3 @@ function setSubmissionErrors(
488501 field . submitState . submissionErrors . set ( fieldErrors ) ;
489502 }
490503}
491-
492- /**
493- * Creates a `Schema` that adds logic rules to a form.
494- * @param fn A **non-reactive** function that sets up reactive logic rules for the form.
495- * @returns A schema object that implements the given logic.
496- * @template TValue The value type of a `FieldTree` that this schema binds to.
497- *
498- * @category structure
499- * @experimental 21.0.0
500- */
501- export function schema < TValue > ( fn : SchemaFn < TValue > ) : Schema < TValue > {
502- return SchemaImpl . create ( fn ) as unknown as Schema < TValue > ;
503- }
504-
505- /** Marks a {@link node} and its descendants as touched. */
506- function markAllAsTouched ( node : FieldNode ) {
507- // Don't mark hidden, disabled, or readonly fields as touched since they don't contribute to the
508- // form's validity. This also prevents errors from appearing immediately if they're later made
509- // interactive.
510- if ( node . validationState . shouldSkipValidation ( ) ) {
511- return ;
512- }
513- node . markAsTouched ( ) ;
514- for ( const child of node . structure . children ( ) ) {
515- markAllAsTouched ( child ) ;
516- }
517- }
0 commit comments