import { observable , computed , action , reaction , autorun , when , makeObservable } from 'mobx' ; import { ComposibleValidatable , Validator , Validated , ValidationResponse , ValidateStatus , Error , ValidateResult } from './types' ; import { applyValidators , debounce , isPromiseLike } from './utils' ; import Disposable from './disposable' ; /**
 * The state for a field.
 */ export default class FieldState < TValue > extends Disposable implements ComposibleValidatable < TValue > { /**
     * If activated (with auto validation).
     * Field will only be activated when `validate()` or `onChange()` called.
     */ @ observable _activated = false ; /**
     * If value has been touched (different with `initialValue`)
     */ @ computed get dirty ( ) { return this . value !== this . initialValue ; } /**
     * Value that reacts to `onChange` immediately.
     * You should only use it to bind with UI input componnet.
     */ @ observable . ref _value ! : TValue ; /**
     * Value that can be consumed by your code.
     * It's synced from `_value` with debounce of 200ms.
     */ @ observable . ref value ! : TValue ; /**
     * Value that has bean validated with no error, AKA "safe".
     */ @ observable . ref $ ! : TValue ; /**
     * The validate status.
     */ @ observable _validateStatus : ValidateStatus = ValidateStatus . NotValidated ; /**
     * If the state is doing a validation.
     */ @ computed get validating ( ) { return this . validationDisabled ? false : this . _validateStatus === ValidateStatus . Validating ; } /**
     * The original error info of validation.
     */ @ observable _error : Error ; /**
     * The error info of validation.
     */ @ computed get error ( ) { return this . validationDisabled ? undefined : this . _error ; } /**
     * If the state contains error.
     */ @ computed get hasError ( ) { return ! ! this . error ; } /**
     * If the validation has been done.
     * It does not means validation passed.
     */ @ computed get validated ( ) { return this . validationDisabled ? false : this . _validateStatus === ValidateStatus . Validated ; } /**
     * Set error info.
     */ @ action setError ( error ) { this . _error = error ? error : undefined ; } /**
     * List of validator functions.
     */ @ observable . shallow private _validators : Validator < TValue > [ ] = [ ] ; /**
     * Add validator function.
     */ @ action validators ( ... validators ) { this . _validators . push ( ... validators ) ; return this ; } /**
     * Set `_value` on change event.
     */ @ action onChange ( value ) { this . _value = value ; } /**
     * Set `value` (& `_value`) synchronously.
     */ @ action set ( value ) { this . value = this . _value = value ; } /**
     * Reset to initial status.
     */ @ action reset ( ) { this . $ = this . value = this . _value = this . initialValue ; this . _activated = false ; this . _validateStatus = ValidateStatus . NotValidated ; this . _error = undefined ; this . validation = undefined ; } /**
     * Current validation info.
     */ @ observable . ref private validation ? : Validated < TValue > ; /**
     * Do validation.
     */ private _validate ( ) { const value = this . value ; if ( value !== this . _value ) { return ; } action ( 'set-validateStatus-when-_validate' , ( ) => { this . _validateStatus = ValidateStatus . Validating ; } ) ( ) ; const response = applyValidators ( value , this . _validators ) ; action ( 'set-validation-when-_validate' , ( ) => { this . validation = { value , response } ; } ) ( ) ; } /**
     * Fire a validation behavior.
     */ async validate ( ) { const validation = this . validation ; action ( 'activate-and-sync-_value-when-validate' , ( ) => { this . _activated = true ; this . value = this . _value ; } ) ( ) ; if ( this . validation === validation ) { this . _validate ( ) ; } await when ( ( ) => this . validationDisabled || this . validated , { name : 'return-validate-when-not-validating' } ) ; return ( this . hasError ? { hasError : true , error : this . error } as const : { hasError : false , value : this . value } as const ) ; } /**
     * Method to check if we should disable validation.
     */ @ observable . ref private shouldDisableValidation = ( ) => false ; /** If validation disabled. */ @ computed get validationDisabled ( ) { return this . shouldDisableValidation ( ) ; } /**
     * Configure when to disable validation.
     */ @ action disableValidationWhen ( predict ) { this . shouldDisableValidation = predict ; return this ; } /**
     * Apply validation.
     */ private async applyValidation ( ) { const validation = this . validation ; if ( ! validation ) { return ; } const error = ( isPromiseLike ( validation . response ) ? await validation . response : validation . response ) ; if ( validation !== this . validation || validation . value !== this . _value ) { return ; } action ( 'endValidation' , ( ) => { this . validation = undefined ; this . _validateStatus = ValidateStatus . Validated ; if ( error !== this . error ) { this . setError ( error ) ; } } ) ( ) ; } constructor ( private initialValue , delay = 200 ) { super ( ) ; makeObservable ( this ) ; this . reset ( ) ; this . addDisposer ( reaction ( ( ) => this . _value , debounce ( ( ) => { if ( this . value !== this . _value ) { action ( 'sync-value-when-_value-changed' , ( ) => { this . value = this . _value ; this . _validateStatus = ValidateStatus . NotValidated ; this . _activated = true ; } ) ( ) ; } } , delay ) , { name : 'reaction-when-_value-change' } ) ) ; this . addDisposer ( reaction ( ( ) => this . validated && ! this . hasError , validateOk => validateOk && ( this . $ = this . value ) , { name : 'sync-$-when-validatedOk' } ) ) ; this . addDisposer ( autorun ( ( ) => ! this . validationDisabled && this . _activated && this . _validate ( ) , { name : 'autorun-check-&-_validate' } ) ) ; this . addDisposer ( reaction ( ( ) => this . validation , ( ) => this . applyValidation ( ) , { name : 'applyValidation-when-validation-change' } ) ) ; } }