From 5521f3b246be4c24cd54f5e0b5383fc9e78e24dd Mon Sep 17 00:00:00 2001
From: Dan Chadwick <dan899@gmail.com>
Date: Tue, 26 Apr 2016 19:17:34 -0400
Subject: [PATCH] paper-input: Exposed invalid status via `onInvalid` action.

---
 addon/components/paper-input.js | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/addon/components/paper-input.js b/addon/components/paper-input.js
index 7ee8d7e64..8bef4386a 100644
--- a/addon/components/paper-input.js
+++ b/addon/components/paper-input.js
@@ -27,6 +27,7 @@ export default BaseFocusable.extend(ColorMixin, FlexMixin, {
   tabindex: null,
   hideAllMessages: false,
   isTouched: false,
+  lastIsInvalid: undefined,
 
   hasValue: computed('value', 'isNativeInvalid', function() {
     let value = this.get('value');
@@ -38,8 +39,21 @@ export default BaseFocusable.extend(ColorMixin, FlexMixin, {
     return `input-${this.get('elementId')}`;
   }),
 
+  /**
+   * The result of isInvalid is appropriate for controlling the display of
+   * validation error messages. It also may be used to distinguish whether
+   * the input would be considered valid after it is touched.
+   *
+   * @public
+   *
+   * @return {boolean|null} Whether the input is or would be invalid.
+   *    null: input has not yet been touched, but would be invalid if it were
+   *    false: input is valid (touched or not)
+   *    true: input has been touched and is invalid.
+   */
   isInvalid: computed('isTouched', 'validationErrorMessages.length', 'isNativeInvalid', function() {
-    return this.get('isTouched') && (this.get('validationErrorMessages.length') || this.get('isNativeInvalid'));
+    let isInvalid = this.get('validationErrorMessages.length') || this.get('isNativeInvalid');
+    return isInvalid && !this.get('isTouched') ? null : !!isInvalid;
   }),
 
   renderCharCount: computed('value', function() {
@@ -120,6 +134,7 @@ export default BaseFocusable.extend(ColorMixin, FlexMixin, {
   didReceiveAttrs() {
     this._super(...arguments);
     assert('{{paper-input}} and {{paper-select}} require an `onChange` action.', !!this.get('onChange'));
+    this.notifyInvalid();
   },
 
   didInsertElement() {
@@ -179,17 +194,27 @@ export default BaseFocusable.extend(ColorMixin, FlexMixin, {
     return offsetHeight + (line > 0 ? line : 0);
   },
 
+  notifyInvalid() {
+    let isInvalid = this.get('isInvalid');
+    if (this.get('lastIsInvalid') !== isInvalid) {
+      this.sendAction('onInvalid', this.get('isInvalid'));
+      this.set('lastIsinvalid');
+    }
+  },
+
   actions: {
     handleInput(e) {
       this.sendAction('onChange', e.target.value);
       this.growTextarea();
       let inputElement = this.$('input').get(0);
       this.set('isNativeInvalid', inputElement && inputElement.validity && inputElement.validity.badInput);
+      this.notifyInvalid();
     },
 
     handleBlur(e) {
       this.sendAction('onBlur', e);
       this.set('isTouched', true);
+      this.notifyInvalid();
     }
   }
 });