@@ -10,13 +10,32 @@ import SwiftUI
1010import Hooks
1111
1212public class FormControl < FieldName> where FieldName: Hashable {
13- var currentErrorNotifyTask : Task < Void , Error > ?
14-
1513 var options : FormOption < FieldName >
16- var fields : [ FieldName : FieldProtocol ]
14+
15+ private var currentErrorNotifyTask : Task < Void , Error > ?
16+ private var fields : [ FieldName : FieldProtocol ]
17+
18+ private var _currentFocusedField : FieldName ?
19+
20+ @MainActor
21+ private var currentFocusedField : FieldName ? {
22+ get {
23+ _currentFocusedField ?? options. focusedFieldOption. focusedFieldBindingValue
24+ }
25+ set {
26+ if let newValue {
27+ options. focusedFieldOption. triggerFocus ( on: newValue)
28+ }
29+ if options. focusedFieldOption. hasFocusedFieldBinder {
30+ return
31+ }
32+ _currentFocusedField = newValue
33+ }
34+ }
35+
36+ var instantFormState : FormState < FieldName >
1737 @MainActor @Binding
1838 private( set) public var formState : FormState < FieldName >
19- var instantFormState : FormState < FieldName >
2039
2140 init ( options: FormOption < FieldName > , formState: Binding < FormState < FieldName > > ) {
2241 self . options = options
@@ -181,50 +200,12 @@ public class FormControl<FieldName> where FieldName: Hashable {
181200 try await onInvalid ( instantFormState. formValues, errors)
182201 }
183202 await postHandleSubmit ( isOveralValid: isOveralValid, errors: errors, isSubmitSuccessful: errors. errorFields. isEmpty)
184- if !isOveralValid {
185- await focusError ( with: errors)
186- }
187203 } catch {
188204 await postHandleSubmit ( isOveralValid: isOveralValid, errors: errors, isSubmitSuccessful: false )
189205 throw error
190206 }
191207 }
192208
193- @MainActor
194- private func focusError( with errors: FormError < FieldName > ) {
195- guard options. shouldFocusError else {
196- return
197- }
198- let fields = fields. sorted { $0. value. fieldOrdinal < $1. value. fieldOrdinal }
199- let firstErrorField = fields. first ( where: { errors. errorFields. contains ( $0. key) } ) ? . key
200- guard let firstErrorField else {
201- return
202- }
203- options. focusedFieldOption. triggerFocus ( on: firstErrorField)
204- }
205-
206- private func postHandleSubmit( isOveralValid: Bool , errors: FormError < FieldName > , isSubmitSuccessful: Bool ) async {
207- instantFormState. isValid = isOveralValid
208- instantFormState. submissionState = . submitted
209- instantFormState. isSubmitSuccessful = isSubmitSuccessful
210- currentErrorNotifyTask? . cancel ( )
211- instantFormState. submitCount += 1
212- if options. delayErrorInNanoseconds == 0 || isOveralValid {
213- currentErrorNotifyTask = nil
214- instantFormState. errors = errors
215- await syncFormState ( )
216- } else {
217- await syncFormState ( )
218- let delayErrorInNanoseconds = options. delayErrorInNanoseconds
219- currentErrorNotifyTask = Task { [ weak self] in
220- try await Task . sleep ( nanoseconds: delayErrorInNanoseconds)
221- self ? . instantFormState. errors = errors
222- await self ? . syncFormState ( )
223- await self ? . focusError ( with: errors)
224- }
225- }
226- }
227-
228209 public func reset( defaultValues: FormValue < FieldName > , options: ResetOption = [ ] ) async {
229210 for (name, defaultValue) in defaultValues {
230211 if let defaultValue = Optional . some ( defaultValue) . flattened ( ) {
@@ -302,23 +283,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
302283 guard options. contains ( . shouldValidate) else {
303284 return await syncFormState ( )
304285 }
305- if let resolver = self . options. resolver {
306- let result = await resolver ( instantFormState. formValues, self . options. context, [ name] )
307- switch result {
308- case . success:
309- break
310- case . failure( let e) :
311- instantFormState. errors = instantFormState. errors. union ( e)
312- instantFormState. isValid = false
313- }
314- } else if let field = fields [ name] {
315- let ( isValid, messages) = await field. computeMessages ( )
316- instantFormState. errors. setMessages ( name: name, messages: messages, isValid: isValid)
317- if !isValid {
318- instantFormState. isValid = false
319- }
320- }
321- return await syncFormState ( )
286+ await trigger ( name: name)
322287 }
323288
324289 public func getFieldState( name: FieldName ) -> FieldState {
@@ -330,7 +295,7 @@ public class FormControl<FieldName> where FieldName: Hashable {
330295 }
331296
332297 @discardableResult
333- public func trigger( names: [ FieldName ] ) async -> Bool {
298+ public func trigger( names: [ FieldName ] , shouldFocus : Bool = false ) async -> Bool {
334299 let validationNames = names. isEmpty ? fields. map { $0. key } : names
335300 instantFormState. isValidating = true
336301 await syncFormState ( )
@@ -370,31 +335,36 @@ public class FormControl<FieldName> where FieldName: Hashable {
370335 }
371336 }
372337 if !isValid {
373- instantFormState. isValid = false
338+ instantFormState. isValid = isValid
374339 }
375340 instantFormState. isValidating = false
341+
376342 currentErrorNotifyTask? . cancel ( )
377- if options. delayErrorInNanoseconds == 0 {
343+ if options. delayErrorInNanoseconds == 0 || isValid {
344+ currentErrorNotifyTask = nil
378345 instantFormState. errors = errors
379346 await syncFormState ( )
347+ if shouldFocus {
348+ await focusFieldAfterTrigger ( names: validationNames, errorFields: errors. errorFields)
349+ }
380350 } else {
381351 await syncFormState ( )
382352 let delayErrorInNanoseconds = options. delayErrorInNanoseconds
383353 currentErrorNotifyTask = Task { [ weak self] in
384354 try await Task . sleep ( nanoseconds: delayErrorInNanoseconds)
385355 self ? . instantFormState. errors = errors
386356 await self ? . syncFormState ( )
387- if validationNames . count == 1 , let firstField = validationNames . first {
388- await self ? . options . focusedFieldOption . triggerFocus ( on : firstField )
357+ if shouldFocus {
358+ await self ? . focusFieldAfterTrigger ( names : validationNames , errorFields : errors . errorFields )
389359 }
390360 }
391361 }
392362 return isValid
393363 }
394364
395365 @discardableResult
396- public func trigger( name: FieldName ... ) async -> Bool {
397- await trigger ( names: name)
366+ public func trigger( name: FieldName ... , shouldFocus : Bool = false ) async -> Bool {
367+ await trigger ( names: name, shouldFocus : shouldFocus )
398368 }
399369}
400370
@@ -404,64 +374,36 @@ extension FormControl {
404374 return
405375 }
406376 if let resolver = options. resolver {
407- let isValid : Bool
408377 let result = await resolver ( instantFormState. formValues, options. context, Array ( fields. keys) )
409378 switch result {
410379 case . success( let formValues) :
411- isValid = true
380+ instantFormState . isValid = true
412381 instantFormState. formValues. unioned ( formValues)
382+ await syncFormState ( )
413383 case . failure( let e) :
414- isValid = false
415- currentErrorNotifyTask? . cancel ( )
416- if options. delayErrorInNanoseconds == 0 || isValid {
417- currentErrorNotifyTask = nil
418- instantFormState. errors = e
419- } else {
420- let delayErrorInNanoseconds = options. delayErrorInNanoseconds
421- currentErrorNotifyTask = Task { [ weak self] in
422- try await Task . sleep ( nanoseconds: delayErrorInNanoseconds)
423- self ? . instantFormState. errors = e
424- await self ? . syncFormState ( )
425- await self ? . focusError ( with: e)
426- }
427- }
384+ await onResultPostUpdateValid ( e, isValid: false )
428385 }
429- instantFormState. isValid = isValid
430- } else {
431- let ( isValid, errors) = await withTaskGroup ( of: KeyValidationResult . self) { group in
432- var errors = instantFormState. errors
433- for (key, field) in fields {
434- group. addTask {
435- let ( isValid, messages) = await field. computeMessages ( )
436- return KeyValidationResult ( key: key, isValid: isValid, messages: messages)
437- }
438- }
439- for await keyResult in group {
440- errors. setMessages ( name: keyResult. key, messages: keyResult. messages, isValid: keyResult. isValid)
441- if keyResult. isValid {
442- continue
443- }
444- group. cancelAll ( )
445- return ( false , errors)
386+ return
387+ }
388+ let ( isValid, errors) = await withTaskGroup ( of: KeyValidationResult . self) { group in
389+ var errors = instantFormState. errors
390+ for (key, field) in fields {
391+ group. addTask {
392+ let ( isValid, messages) = await field. computeMessages ( )
393+ return KeyValidationResult ( key: key, isValid: isValid, messages: messages)
446394 }
447- return ( true , errors)
448395 }
449- currentErrorNotifyTask? . cancel ( )
450- if options. delayErrorInNanoseconds == 0 || isValid {
451- currentErrorNotifyTask = nil
452- instantFormState. errors = errors
453- } else {
454- let delayErrorInNanoseconds = options. delayErrorInNanoseconds
455- currentErrorNotifyTask = Task { [ weak self] in
456- try await Task . sleep ( nanoseconds: delayErrorInNanoseconds)
457- self ? . instantFormState. errors = errors
458- await self ? . syncFormState ( )
459- await self ? . focusError ( with: errors)
396+ for await keyResult in group {
397+ errors. setMessages ( name: keyResult. key, messages: keyResult. messages, isValid: keyResult. isValid)
398+ if keyResult. isValid {
399+ continue
460400 }
401+ group. cancelAll ( )
402+ return ( false , errors)
461403 }
462- instantFormState . isValid = isValid
404+ return ( true , errors )
463405 }
464- await syncFormState ( )
406+ await onResultPostUpdateValid ( errors , isValid : isValid )
465407 }
466408
467409 @MainActor
@@ -500,10 +442,6 @@ private extension FormControl {
500442 options. shouldUnregister
501443 }
502444
503- func computeResult( ) async -> Bool {
504- await options. rules. isValid ( value. wrappedValue)
505- }
506-
507445 func computeMessages( ) async -> ( Bool , [ String ] ) {
508446 await options. rules. computeMessage ( value: value. wrappedValue)
509447 }
@@ -524,13 +462,7 @@ private extension FormControl {
524462 return
525463 }
526464 Task {
527- guard await self . trigger ( name: name) else {
528- return
529- }
530- guard self . options. delayErrorInNanoseconds == 0 else {
531- return
532- }
533- await self . options. focusedFieldOption. triggerFocus ( on: name)
465+ await self . trigger ( name: name, shouldFocus: true )
534466 }
535467 }
536468 }
@@ -544,6 +476,62 @@ private extension FormControl {
544476 }
545477 return options. reValidateMode. contains ( . onChange)
546478 }
479+
480+ func postHandleSubmit( isOveralValid: Bool , errors: FormError < FieldName > , isSubmitSuccessful: Bool ) async {
481+ instantFormState. submissionState = . submitted
482+ instantFormState. isSubmitSuccessful = isSubmitSuccessful
483+ instantFormState. submitCount += 1
484+ await onResultPostUpdateValid ( errors, isValid: isOveralValid)
485+ }
486+
487+ @MainActor
488+ func focusFieldAfterTrigger( names: [ FieldName ] , errorFields: Set < FieldName > ) {
489+ let focusField : FieldName ?
490+ if names. count == 1 {
491+ focusField = names [ 0 ]
492+ if currentFocusedField != nil && currentFocusedField != focusField {
493+ return
494+ }
495+ } else {
496+ focusField = names. first ( where: errorFields. contains)
497+ }
498+ guard let focusField else {
499+ return
500+ }
501+ currentFocusedField = focusField
502+ }
503+
504+ func onResultPostUpdateValid( _ errors: FormError < FieldName > , isValid: Bool ) async {
505+ instantFormState. isValid = isValid
506+ currentErrorNotifyTask? . cancel ( )
507+ if options. delayErrorInNanoseconds == 0 || isValid {
508+ currentErrorNotifyTask = nil
509+ instantFormState. errors = errors
510+ await syncFormState ( )
511+ return await focusError ( with: errors)
512+ }
513+ await syncFormState ( )
514+ let delayErrorInNanoseconds = options. delayErrorInNanoseconds
515+ currentErrorNotifyTask = Task { [ weak self] in
516+ try await Task . sleep ( nanoseconds: delayErrorInNanoseconds)
517+ self ? . instantFormState. errors = errors
518+ await self ? . syncFormState ( )
519+ await self ? . focusError ( with: errors)
520+ }
521+ }
522+
523+ @MainActor
524+ func focusError( with errors: FormError < FieldName > ) {
525+ guard options. shouldFocusError else {
526+ return
527+ }
528+ let fields = fields. sorted { $0. value. fieldOrdinal < $1. value. fieldOrdinal }
529+ let firstErrorField = fields. first ( where: { errors. errorFields. contains ( $0. key) } ) ? . key
530+ guard let firstErrorField else {
531+ return
532+ }
533+ currentFocusedField = firstErrorField
534+ }
547535}
548536
549537private extension FormControl {
0 commit comments