diff --git a/demos/text-field.html b/demos/text-field.html index eb4b7017ce4..74554ff15d7 100644 --- a/demos/text-field.html +++ b/demos/text-field.html @@ -386,6 +386,20 @@

Full-Width Text Field and Textarea

+ +
+

Custom error state behaviour - remove error state as soon as invalid input is resolved

+
+
+ + +
+
+ +
+
@@ -723,6 +737,17 @@

Full-Width Text Field and Textarea

tfMultiRoot.classList[target.checked ? 'add' : 'remove']('demo-textarea'); }); }); + + demoReady(function() { + var textFieldEl = document.querySelector('#demo-textfield-error-state'); + var textField = new mdc.textField.MDCTextField(textFieldEl); + + textFieldEl.addEventListener('input', function(event) { + if (textField.valid) { + textField.valid = true; + } + }); + }); diff --git a/packages/mdc-textfield/README.md b/packages/mdc-textfield/README.md index 5caf8341068..cc128f2ac75 100644 --- a/packages/mdc-textfield/README.md +++ b/packages/mdc-textfield/README.md @@ -9,9 +9,9 @@ path: /catalog/input-controls/text-field/ ## Important - Default Style Deprecation Notice The existing default text field style will be changed in an upcoming release. The Material spec indicates that -the default style will be the filled variant (currently referred to as the box variant). This will become the +the default style will be the filled variant (currently referred to as the box variant). This will become the default style. Continuing to add the `mdc-text-field--box` class to the text field will -result in no change. +result in no change. # Text Field @@ -255,6 +255,7 @@ Property | Value Type | Description --- | --- | --- `value` | String | Proxies to the foundation's `getValue`/`setValue` methods. `disabled` | Boolean | Proxies to the foundation's `isDisabled`/`setDisabled` methods. +`useNativeValidation` | Boolean (write-only) | Proxies to the foundation's `setUseNativeValidation` method. `valid` | Boolean | Proxies to the foundation's `isValid`/`setValid` methods. `required` | Boolean | Proxies to the foundation's `isRequired`/`setRequired` methods. `helperTextContent` | String | Proxies to the foundation's `setHelperTextContent` method when set. @@ -303,8 +304,9 @@ Method Signature | Description --- | --- `getValue() => string` | Returns the input's value. `setValue(value: string)` | Sets the input's value. -`isValid() => boolean` | If a custom validity is set, returns that value. Otherwise, returns the result of native validity checks. -`setValid(isValid: boolean)` | Sets custom validity. Once set, native validity checking is ignored. +`setUseNativeValidation(useNativeValidation: boolean)` | Sets whether to check native HTML validity state (`true`, default) or custom validity state when updating styles (`false`). +`setValid(isValid: boolean)` | Sets custom validity and updates styles accordingly. Note that native validation will still be honored subsequently unless `setUseNativeValidation(false)` is also called. +`isValid() => boolean` | Returns the component's current validity state (either native or custom, depending on how `setUseNativeValidation()` was configured). `isDisabled() => boolean` | Returns whether or not the input is disabled. `setDisabled(disabled: boolean) => void` | Updates the input's disabled state. `isRequired() => boolean` | Returns whether the input is required. diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index 9991686171a..d8f17070258 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -106,6 +106,10 @@ class MDCTextFieldFoundation extends MDCFoundation { this.useCustomValidityChecking_ = false; /** @private {boolean} */ this.isValid_ = true; + + /** @private {boolean} */ + this.useNativeValidation_ = true; + /** @private {function(): undefined} */ this.inputFocusHandler_ = () => this.activateFocus(); /** @private {function(): undefined} */ @@ -291,24 +295,31 @@ class MDCTextFieldFoundation extends MDCFoundation { * Otherwise, returns the result of native validity checks. */ isValid() { - return this.useCustomValidityChecking_ - ? this.isValid_ : this.isNativeInputValid_(); + return this.useNativeValidation_ + ? this.isNativeInputValid_() : this.isValid_; } /** * @param {boolean} isValid Sets the validity state of the Text Field. */ setValid(isValid) { - this.useCustomValidityChecking_ = true; this.isValid_ = isValid; - // Retrieve from the getter to ensure correct logic is applied. - isValid = this.isValid(); this.styleValidity_(isValid); + + const shouldShake = !isValid && !this.isFocused_; if (this.adapter_.hasLabel()) { - this.adapter_.shakeLabel(this.shouldShake); + this.adapter_.shakeLabel(shouldShake); } } + /** + * Enables or disables the use of native validation. Use this for custom validation. + * @param {boolean} useNativeValidation Set this to false to ignore native input validation. + */ + setUseNativeValidation(useNativeValidation) { + this.useNativeValidation_ = useNativeValidation; + } + /** * @return {boolean} True if the Text Field is disabled. */ diff --git a/packages/mdc-textfield/index.js b/packages/mdc-textfield/index.js index 6de2aad5568..bbabe39196b 100644 --- a/packages/mdc-textfield/index.js +++ b/packages/mdc-textfield/index.js @@ -325,6 +325,14 @@ class MDCTextField extends MDCComponent { this.foundation_.setIconContent(content); } + /** + * Enables or disables the use of native validation. Use this for custom validation. + * @param {boolean} useNativeValidation Set this to false to ignore native input validation. + */ + set useNativeValidation(useNativeValidation) { + this.foundation_.setUseNativeValidation(useNativeValidation); + } + /** * Recomputes the outline SVG path for the outline element. */ diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 2d79ddf3d8e..1fdf1a1be64 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -166,8 +166,9 @@ test('#isValid for native validation', () => { assert.isNotOk(foundation.isValid()); }); -test('#setValid overrides native validation', () => { +test('#setValid overrides native validation when useNativeValidation set to false', () => { const {foundation, nativeInput} = setupValueTest('', /* isValid */ false); + foundation.setUseNativeValidation(false); foundation.setValid(true); assert.isOk(foundation.isValid()); @@ -622,14 +623,14 @@ test('does not style label on blur if input has a value and hasLabel is false', td.verify(mockAdapter.floatLabel(td.matchers.anything()), {times: 0}); }); -test('on blur removes mdc-text-field--invalid if custom validity is false and' + +test('on blur removes mdc-text-field--invalid if useNativeValidation is true and' + 'input.checkValidity() returns true', () => { const {mockAdapter, blur} = setupBlurTest(); blur(); td.verify(mockAdapter.removeClass(cssClasses.INVALID)); }); -test('on blur adds mdc-textfied--invalid if custom validity is false and' + +test('on blur adds mdc-textfied--invalid if useNativeValidation is true and' + 'input.checkValidity() returns false', () => { const {mockAdapter, blur, nativeInput} = setupBlurTest(); nativeInput.validity.valid = false; @@ -637,9 +638,10 @@ test('on blur adds mdc-textfied--invalid if custom validity is false and' + td.verify(mockAdapter.addClass(cssClasses.INVALID)); }); -test('on blur does not remove mdc-text-field--invalid if custom validity is true and' + +test('on blur does not remove mdc-text-field--invalid if useNativeValidation is false and' + 'input.checkValidity() returns true', () => { const {foundation, mockAdapter, blur} = setupBlurTest(); + foundation.setUseNativeValidation(false); foundation.setValid(false); blur(); td.verify(mockAdapter.removeClass(cssClasses.INVALID), {times: 0}); diff --git a/test/unit/mdc-textfield/mdc-text-field.test.js b/test/unit/mdc-textfield/mdc-text-field.test.js index e6cbb337fa4..fd2d8fb8180 100644 --- a/test/unit/mdc-textfield/mdc-text-field.test.js +++ b/test/unit/mdc-textfield/mdc-text-field.test.js @@ -422,6 +422,12 @@ test('get/set required', () => { assert.isFalse(component.required); }); +test('set useNativeValidation', () => { + const {component, mockFoundation} = setupMockFoundationTest(); + component.useNativeValidation = true; + td.verify(mockFoundation.setUseNativeValidation(true)); +}); + test('get/set pattern', () => { const {component} = setupMockFoundationTest(); component.pattern = '.{8,}';