@@ -35,7 +35,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
3535 }
3636 field. options = options
3737 } else {
38- field = Field ( name: name, options: options, control: self )
38+ field = Field ( index : fields . count , name: name, options: options, control: self )
3939 fields [ name] = field
4040 instantFormState. formValues [ name] = options. defaultValue
4141 }
@@ -176,6 +176,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
176176 try await onValid ( instantFormState. formValues, errors)
177177 } else if let onInvalid {
178178 try await onInvalid ( instantFormState. formValues, errors)
179+ await focusError ( with: errors)
179180 }
180181 await postHandleSubmit ( isOveralValid: isOveralValid, errors: errors, isSubmitSuccessful: errors. errorFields. isEmpty)
181182 } catch {
@@ -184,6 +185,18 @@ public class FormControl<FieldName> where FieldName: Hashable {
184185 }
185186 }
186187
188+ private func focusError( with errors: FormError < FieldName > ) async {
189+ guard options. shouldFocusError else {
190+ return
191+ }
192+ let fields = fields
193+ . sorted { $0. value. index < $1. value. index }
194+ guard let firstErrorField = fields. first ( where: { errors. errorFields. contains ( $0. key) } ) ? . key else {
195+ return
196+ }
197+ await options. focusedFieldOption. triggerFocus ( on: firstErrorField)
198+ }
199+
187200 private func postHandleSubmit( isOveralValid: Bool , errors: FormError < FieldName > , isSubmitSuccessful: Bool ) async {
188201 instantFormState. isValid = isOveralValid
189202 instantFormState. submissionState = . submitted
@@ -450,6 +463,7 @@ extension FormControl {
450463
451464private extension FormControl {
452465 class Field < Value> : FieldProtocol {
466+ let index : Int
453467 let name : FieldName
454468 var options : RegisterOption < Value > {
455469 didSet {
@@ -462,7 +476,8 @@ private extension FormControl {
462476 unowned var control : FormControl < FieldName >
463477 var value : Binding < Value >
464478
465- init ( name: FieldName , options: RegisterOption < Value > , control: FormControl < FieldName > ) {
479+ init ( index: Int , name: FieldName , options: RegisterOption < Value > , control: FormControl < FieldName > ) {
480+ self . index = index
466481 self . name = name
467482 self . options = options
468483 self . control = control
@@ -498,6 +513,7 @@ private extension FormControl {
498513 }
499514 Task {
500515 await self . trigger ( name: name)
516+ await self . options. focusedFieldOption. triggerFocus ( on: name)
501517 }
502518 }
503519 }
@@ -522,41 +538,59 @@ private extension FormControl {
522538}
523539
524540public struct ContextualForm < Content, FieldName> : View where Content: View , FieldName: Hashable {
525- let mode : Mode
526- let reValidateMode : ReValidateMode
527- let resolver : Resolver < FieldName > ?
528- let context : Any ?
529- let shouldUnregister : Bool
530- let delayErrorInNanoseconds : UInt64
541+ let formOptions : FormOption < FieldName >
531542 let contentBuilder : ( FormControl < FieldName > ) -> Content
532543
533544 public init ( mode: Mode = . onSubmit,
534545 reValidateMode: ReValidateMode = . onChange,
535546 resolver: Resolver < FieldName > ? = nil ,
536547 context: Any ? = nil ,
537548 shouldUnregister: Bool = true ,
549+ shouldFocusError: Bool = true ,
550+ delayErrorInNanoseconds: UInt64 = 0 ,
551+ onFocusedField: @escaping ( FieldName ) -> Void ,
552+ @ViewBuilder content: @escaping ( FormControl < FieldName > ) -> Content
553+ ) {
554+ self . formOptions = . init(
555+ mode: mode,
556+ reValidateMode: reValidateMode,
557+ resolver: resolver,
558+ context: context,
559+ shouldUnregister: shouldUnregister,
560+ shouldFocusError: shouldFocusError,
561+ delayErrorInNanoseconds: delayErrorInNanoseconds,
562+ onFocusedField: onFocusedField
563+ )
564+ self . contentBuilder = content
565+ }
566+
567+ @available ( macOS 12 . 0 , iOS 15 . 0 , tvOS 15 . 0 , * )
568+ public init ( mode: Mode = . onSubmit,
569+ reValidateMode: ReValidateMode = . onChange,
570+ resolver: Resolver < FieldName > ? = nil ,
571+ context: Any ? = nil ,
572+ shouldUnregister: Bool = true ,
573+ shouldFocusError: Bool = true ,
538574 delayErrorInNanoseconds: UInt64 = 0 ,
575+ focusedFieldBinder: FocusState < FieldName ? > . Binding ,
539576 @ViewBuilder content: @escaping ( FormControl < FieldName > ) -> Content
540577 ) {
541- self . mode = mode
542- self . reValidateMode = reValidateMode
543- self . resolver = resolver
544- self . context = context
545- self . shouldUnregister = shouldUnregister
546- self . delayErrorInNanoseconds = delayErrorInNanoseconds
578+ self . formOptions = . init(
579+ mode: mode,
580+ reValidateMode: reValidateMode,
581+ resolver: resolver,
582+ context: context,
583+ shouldUnregister: shouldUnregister,
584+ shouldFocusError: shouldFocusError,
585+ delayErrorInNanoseconds: delayErrorInNanoseconds,
586+ focusedStateBinder: focusedFieldBinder
587+ )
547588 self . contentBuilder = content
548589 }
549590
550591 public var body : some View {
551592 HookScope {
552- let form = useForm (
553- mode: mode,
554- reValidateMode: reValidateMode,
555- resolver: resolver,
556- context: context,
557- shouldUnregister: shouldUnregister,
558- delayErrorInNanoseconds: delayErrorInNanoseconds
559- )
593+ let form = useForm ( formOptions)
560594 Context . Provider ( value: form) {
561595 contentBuilder ( form)
562596 }
0 commit comments