@@ -454,6 +454,16 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
454454 * when it updates the model due to a change in the view.
455455 */
456456 validateTrigger [ validateTrigger [ "changeOrBlur" ] = 3 ] = "changeOrBlur" ;
457+ /**
458+ * Validate the binding when the binding's target element fires a DOM "focusout" event.
459+ * Unlike "blur", this event bubbles.
460+ */
461+ validateTrigger [ validateTrigger [ "focusout" ] = 4 ] = "focusout" ;
462+ /**
463+ * Validate the binding when the binding's target element fires a DOM "focusout" event or
464+ * when it updates the model due to a change in the view.
465+ */
466+ validateTrigger [ validateTrigger [ "changeOrFocusout" ] = 6 ] = "changeOrFocusout" ;
457467 } ) ( exports . validateTrigger || ( exports . validateTrigger = { } ) ) ;
458468
459469 /**
@@ -1041,6 +1051,7 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
10411051 return ValidationController ;
10421052 } ( ) ) ;
10431053
1054+ // tslint:disable:no-bitwise
10441055 /**
10451056 * Binding behavior. Indicates the bound property should be validated.
10461057 */
@@ -1067,23 +1078,48 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
10671078 controller . registerBinding ( binding , target , rules ) ;
10681079 binding . validationController = controller ;
10691080 var trigger = this . getValidateTrigger ( controller ) ;
1070- // tslint:disable-next-line:no-bitwise
1071- if ( trigger & exports . validateTrigger . change ) {
1081+ var event = ( trigger & exports . validateTrigger . blur ) === exports . validateTrigger . blur ? 'blur'
1082+ : ( trigger & exports . validateTrigger . focusout ) === exports . validateTrigger . focusout ? 'focusout'
1083+ : null ;
1084+ var hasChangeTrigger = ( trigger & exports . validateTrigger . change ) === exports . validateTrigger . change ;
1085+ binding . isDirty = ! hasChangeTrigger ;
1086+ // validatedOnce is used to control whether controller should validate upon user input
1087+ //
1088+ // always true when validation trigger doesn't include "blur" event (blur/focusout)
1089+ // else it will be set to true after (a) the first user input & loss of focus or (b) validation
1090+ binding . validatedOnce = hasChangeTrigger && event === null ;
1091+ if ( hasChangeTrigger ) {
10721092 binding . vbbUpdateSource = binding . updateSource ;
10731093 // tslint:disable-next-line:only-arrow-functions
10741094 // tslint:disable-next-line:space-before-function-paren
10751095 binding . updateSource = function ( value ) {
10761096 this . vbbUpdateSource ( value ) ;
1077- this . validationController . validateBinding ( this ) ;
1097+ this . isDirty = true ;
1098+ if ( this . validatedOnce ) {
1099+ this . validationController . validateBinding ( this ) ;
1100+ }
10781101 } ;
10791102 }
1080- // tslint:disable-next-line:no-bitwise
1081- if ( trigger & exports . validateTrigger . blur ) {
1082- binding . validateBlurHandler = function ( ) {
1083- _this . taskQueue . queueMicroTask ( function ( ) { return controller . validateBinding ( binding ) ; } ) ;
1103+ if ( event !== null ) {
1104+ binding . focusLossHandler = function ( ) {
1105+ _this . taskQueue . queueMicroTask ( function ( ) {
1106+ if ( binding . isDirty ) {
1107+ controller . validateBinding ( binding ) ;
1108+ binding . validatedOnce = true ;
1109+ }
1110+ } ) ;
10841111 } ;
1112+ binding . validationTriggerEvent = event ;
10851113 binding . validateTarget = target ;
1086- target . addEventListener ( 'blur' , binding . validateBlurHandler ) ;
1114+ target . addEventListener ( event , binding . focusLossHandler ) ;
1115+ if ( hasChangeTrigger ) {
1116+ var propertyName_1 = getPropertyInfo ( binding . sourceExpression , binding . source ) . propertyName ;
1117+ binding . validationSubscription = controller . subscribe ( function ( event ) {
1118+ if ( ! binding . validatedOnce && event . type === 'validate' ) {
1119+ binding . validatedOnce = event . errors . findIndex ( function ( e ) { return e . propertyName === propertyName_1 ; } ) > - 1 ;
1120+ }
1121+ } ) ;
1122+ }
10871123 }
10881124 if ( trigger !== exports . validateTrigger . manual ) {
10891125 binding . standardUpdateTarget = binding . updateTarget ;
@@ -1105,13 +1141,19 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
11051141 binding . updateTarget = binding . standardUpdateTarget ;
11061142 binding . standardUpdateTarget = null ;
11071143 }
1108- if ( binding . validateBlurHandler ) {
1109- binding . validateTarget . removeEventListener ( 'blur' , binding . validateBlurHandler ) ;
1110- binding . validateBlurHandler = null ;
1144+ if ( binding . focusLossHandler ) {
1145+ binding . validateTarget . removeEventListener ( binding . validationTriggerEvent , binding . focusLossHandler ) ;
1146+ binding . focusLossHandler = null ;
11111147 binding . validateTarget = null ;
11121148 }
1149+ if ( binding . validationSubscription ) {
1150+ binding . validationSubscription . dispose ( ) ;
1151+ binding . validationSubscription = null ;
1152+ }
11131153 binding . validationController . unregisterBinding ( binding ) ;
11141154 binding . validationController = null ;
1155+ binding . isDirty = null ;
1156+ binding . validatedOnce = null ;
11151157 } ;
11161158 return ValidateBindingBehaviorBase ;
11171159 } ( ) ) ;
@@ -1209,6 +1251,34 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
12091251 aureliaBinding . bindingBehavior ( 'validateOnChangeOrBlur' )
12101252 ] , ValidateOnChangeOrBlurBindingBehavior ) ;
12111253 return ValidateOnChangeOrBlurBindingBehavior ;
1254+ } ( ValidateBindingBehaviorBase ) ) ;
1255+ var ValidateOnFocusoutBindingBehavior = /** @class */ ( function ( _super ) {
1256+ __extends ( ValidateOnFocusoutBindingBehavior , _super ) ;
1257+ function ValidateOnFocusoutBindingBehavior ( ) {
1258+ return _super !== null && _super . apply ( this , arguments ) || this ;
1259+ }
1260+ ValidateOnFocusoutBindingBehavior . prototype . getValidateTrigger = function ( ) {
1261+ return exports . validateTrigger . focusout ;
1262+ } ;
1263+ ValidateOnFocusoutBindingBehavior . inject = [ aureliaTaskQueue . TaskQueue ] ;
1264+ ValidateOnFocusoutBindingBehavior = __decorate ( [
1265+ aureliaBinding . bindingBehavior ( 'validateOnFocusout' )
1266+ ] , ValidateOnFocusoutBindingBehavior ) ;
1267+ return ValidateOnFocusoutBindingBehavior ;
1268+ } ( ValidateBindingBehaviorBase ) ) ;
1269+ var ValidateOnChangeOrFocusoutBindingBehavior = /** @class */ ( function ( _super ) {
1270+ __extends ( ValidateOnChangeOrFocusoutBindingBehavior , _super ) ;
1271+ function ValidateOnChangeOrFocusoutBindingBehavior ( ) {
1272+ return _super !== null && _super . apply ( this , arguments ) || this ;
1273+ }
1274+ ValidateOnChangeOrFocusoutBindingBehavior . prototype . getValidateTrigger = function ( ) {
1275+ return exports . validateTrigger . changeOrFocusout ;
1276+ } ;
1277+ ValidateOnChangeOrFocusoutBindingBehavior . inject = [ aureliaTaskQueue . TaskQueue ] ;
1278+ ValidateOnChangeOrFocusoutBindingBehavior = __decorate ( [
1279+ aureliaBinding . bindingBehavior ( 'validateOnChangeOrFocusout' )
1280+ ] , ValidateOnChangeOrFocusoutBindingBehavior ) ;
1281+ return ValidateOnChangeOrFocusoutBindingBehavior ;
12121282 } ( ValidateBindingBehaviorBase ) ) ;
12131283
12141284 /**
@@ -1865,7 +1935,7 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
18651935 config . apply ( frameworkConfig . container ) ;
18661936 // globalize the behaviors.
18671937 if ( frameworkConfig . globalResources ) {
1868- frameworkConfig . globalResources ( ValidateBindingBehavior , ValidateManuallyBindingBehavior , ValidateOnBlurBindingBehavior , ValidateOnChangeBindingBehavior , ValidateOnChangeOrBlurBindingBehavior , ValidationErrorsCustomAttribute , ValidationRendererCustomAttribute ) ;
1938+ frameworkConfig . globalResources ( ValidateBindingBehavior , ValidateManuallyBindingBehavior , ValidateOnBlurBindingBehavior , ValidateOnFocusoutBindingBehavior , ValidateOnChangeBindingBehavior , ValidateOnChangeOrBlurBindingBehavior , ValidateOnChangeOrFocusoutBindingBehavior , ValidationErrorsCustomAttribute , ValidationRendererCustomAttribute ) ;
18691939 }
18701940 }
18711941
@@ -1880,6 +1950,8 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
18801950 exports . ValidateOnBlurBindingBehavior = ValidateOnBlurBindingBehavior ;
18811951 exports . ValidateOnChangeBindingBehavior = ValidateOnChangeBindingBehavior ;
18821952 exports . ValidateOnChangeOrBlurBindingBehavior = ValidateOnChangeOrBlurBindingBehavior ;
1953+ exports . ValidateOnFocusoutBindingBehavior = ValidateOnFocusoutBindingBehavior ;
1954+ exports . ValidateOnChangeOrFocusoutBindingBehavior = ValidateOnChangeOrFocusoutBindingBehavior ;
18831955 exports . ValidateEvent = ValidateEvent ;
18841956 exports . ValidateResult = ValidateResult ;
18851957 exports . ValidationController = ValidationController ;
0 commit comments