diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 27098a168560..4d60afb6e527 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -32,6 +32,12 @@ var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
+var PARTIAL_VALIDATION_EVENTS = 'keydown wheel mousedown';
+var PARTIAL_VALIDATION_TYPES = createMap();
+forEach('date,datetime-local,month,time,week'.split(','), function(type) {
+ PARTIAL_VALIDATION_TYPES[type] = true;
+});
+
var inputType = {
/**
@@ -1118,6 +1124,8 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
});
}
+ var timeout;
+
var listener = function(ev) {
if (timeout) {
$browser.defer.cancel(timeout);
@@ -1147,8 +1155,6 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
if ($sniffer.hasEvent('input')) {
element.on('input', listener);
} else {
- var timeout;
-
var deferListener = function(ev, input, origValue) {
if (!timeout) {
timeout = $browser.defer(function() {
@@ -1180,6 +1186,26 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// or form autocomplete on newer browser, we need "change" event to catch it
element.on('change', listener);
+ // Some native input types (date-family) have the ability to change validity without
+ // firing any input/change events.
+ // For these event types, when native validators are present and the browser supports the type,
+ // check for validity changes on various DOM events.
+ if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
+ element.on(PARTIAL_VALIDATION_EVENTS, function(ev) {
+ if (!timeout) {
+ var validity = this[VALIDITY_STATE_PROPERTY];
+ var origBadInput = validity.badInput;
+ var origTypeMismatch = validity.typeMismatch;
+ timeout = $browser.defer(function() {
+ timeout = null;
+ if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
+ listener(ev);
+ }
+ });
+ }
+ });
+ }
+
ctrl.$render = function() {
// Workaround for Firefox validation #12102.
var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 4b9727913737..9dc40d93e09d 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -1943,6 +1943,83 @@ describe('input', function() {
});
});
+ ['month', 'week', 'time', 'date', 'datetime-local'].forEach(function(inputType) {
+ if (jqLite('').prop('type') !== inputType) {
+ return;
+ }
+
+ describe(inputType, function() {
+ they('should re-validate and dirty when partially editing the input value ($prop event)',
+ ['keydown', 'wheel', 'mousedown'],
+ function(validationEvent) {
+ var mockValidity = {valid: true, badInput: false};
+ var inputElm = helper.compileInput('', mockValidity);
+
+ expect(inputElm).toBeValid();
+ expect($rootScope.form.alias.$pristine).toBeTruthy();
+
+ inputElm.triggerHandler({type: validationEvent});
+ mockValidity.valid = false;
+ mockValidity.badInput = true;
+ $browser.defer.flush();
+ expect(inputElm).toBeInvalid();
+ expect($rootScope.form.alias.$pristine).toBeFalsy();
+ }
+ );
+
+ they('should do nothing when $prop event fired but validity does not change',
+ ['keydown', 'wheel', 'mousedown'],
+ function(validationEvent) {
+ var mockValidity = {valid: true, badInput: false};
+ var inputElm = helper.compileInput('', mockValidity);
+
+ expect(inputElm).toBeValid();
+ expect($rootScope.form.alias.$pristine).toBeTruthy();
+
+ inputElm.triggerHandler({type: validationEvent});
+ $browser.defer.flush();
+ expect(inputElm).toBeValid();
+ expect($rootScope.form.alias.$pristine).toBeTruthy();
+ }
+ );
+
+ they('should re-validate dirty when already $invalid and partially editing the input value ($prop event)',
+ ['keydown', 'wheel', 'mousedown'],
+ function(validationEvent) {
+ var mockValidity = {valid: false, valueMissing: true, badInput: false};
+ var inputElm = helper.compileInput('', mockValidity);
+
+ expect(inputElm).toBeInvalid();
+ expect($rootScope.form.alias.$pristine).toBeTruthy();
+
+ inputElm.triggerHandler({type: validationEvent});
+ mockValidity.valid = false;
+ mockValidity.valueMissing = true;
+ mockValidity.badInput = true;
+ $browser.defer.flush();
+ expect(inputElm).toBeInvalid();
+ expect($rootScope.form.alias.$pristine).toBeFalsy();
+ }
+ );
+
+ they('should do nothing when already $invalid and $prop event fired but validity does not change',
+ ['keydown', 'wheel', 'mousedown'],
+ function(validationEvent) {
+ var mockValidity = {valid: false, valueMissing: true, badInput: false};
+ var inputElm = helper.compileInput('', mockValidity);
+
+ expect(inputElm).toBeInvalid();
+ expect($rootScope.form.alias.$pristine).toBeTruthy();
+
+ inputElm.triggerHandler({type: validationEvent});
+ $browser.defer.flush();
+ expect(inputElm).toBeInvalid();
+ expect($rootScope.form.alias.$pristine).toBeTruthy();
+ }
+ );
+ });
+ });
+
describe('number', function() {