From 63c5885a446d5be433d6c1afd23e23a1ddc16553 Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Tue, 5 Dec 2017 11:23:01 -0800 Subject: [PATCH 01/12] chore(textfield): Split out label into subelement --- packages/mdc-textfield/adapter.js | 19 +---- packages/mdc-textfield/constants.js | 2 - packages/mdc-textfield/foundation.js | 44 ++++++++---- packages/mdc-textfield/index.js | 24 +++---- packages/mdc-textfield/label/adapter.js | 44 ++++++++++++ packages/mdc-textfield/label/constants.js | 24 +++++++ packages/mdc-textfield/label/foundation.js | 82 ++++++++++++++++++++++ packages/mdc-textfield/label/index.js | 54 ++++++++++++++ 8 files changed, 247 insertions(+), 46 deletions(-) create mode 100644 packages/mdc-textfield/label/adapter.js create mode 100644 packages/mdc-textfield/label/constants.js create mode 100644 packages/mdc-textfield/label/foundation.js create mode 100644 packages/mdc-textfield/label/index.js diff --git a/packages/mdc-textfield/adapter.js b/packages/mdc-textfield/adapter.js index bd530b3c7b4..2d1c0c72463 100644 --- a/packages/mdc-textfield/adapter.js +++ b/packages/mdc-textfield/adapter.js @@ -18,6 +18,7 @@ /* eslint-disable no-unused-vars */ import MDCTextFieldBottomLineFoundation from './bottom-line/foundation'; import MDCTextFieldHelperTextFoundation from './helper-text/foundation'; +import MDCTextFieldLabelFoundation from './label/foundation'; /* eslint no-unused-vars: [2, {"args": "none"}] */ @@ -34,7 +35,8 @@ let NativeInputType; /** * @typedef {{ * bottomLine: (!MDCTextFieldBottomLineFoundation|undefined), - * helperText: (!MDCTextFieldHelperTextFoundation|undefined) + * helperText: (!MDCTextFieldHelperTextFoundation|undefined), + * label: (!MDCTextFieldLabelFoundation|undefined) * }} */ let FoundationMapType; @@ -62,21 +64,6 @@ class MDCTextFieldAdapter { */ removeClass(className) {} - /** - * Adds a class to the label Element. We recommend you add a conditional - * check here, and in removeClassFromLabel for whether or not the label is - * present so that the JS component could be used with text fields that don't - * require a label, such as the full-width text field. - * @param {string} className - */ - addClassToLabel(className) {} - - /** - * Removes a class from the label Element. - * @param {string} className - */ - removeClassFromLabel(className) {} - /** * Sets an attribute on the icon Element. * @param {string} name diff --git a/packages/mdc-textfield/constants.js b/packages/mdc-textfield/constants.js index 3d564b97090..33a3cc4d4e9 100644 --- a/packages/mdc-textfield/constants.js +++ b/packages/mdc-textfield/constants.js @@ -32,8 +32,6 @@ const cssClasses = { DISABLED: 'mdc-text-field--disabled', FOCUSED: 'mdc-text-field--focused', INVALID: 'mdc-text-field--invalid', - LABEL_FLOAT_ABOVE: 'mdc-text-field__label--float-above', - LABEL_SHAKE: 'mdc-text-field__label--shake', BOX: 'mdc-text-field--box', TEXT_FIELD_ICON: 'mdc-text-field__icon', TEXTAREA: 'mdc-text-field--textarea', diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index 28ef3471cdb..86e0b15ad80 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -18,8 +18,10 @@ import MDCFoundation from '@material/base/foundation'; import {MDCTextFieldAdapter, NativeInputType, FoundationMapType} from './adapter'; import MDCTextFieldBottomLineFoundation from './bottom-line/foundation'; -// eslint-disable-next-line no-unused-vars +/* eslint-disable no-unused-vars */ import MDCTextFieldHelperTextFoundation from './helper-text/foundation'; +import MDCTextFieldLabelFoundation from './label/foundation'; +/* eslint-enable no-unused-vars */ import {cssClasses, strings} from './constants'; @@ -47,8 +49,6 @@ class MDCTextFieldFoundation extends MDCFoundation { return /** @type {!MDCTextFieldAdapter} */ ({ addClass: () => {}, removeClass: () => {}, - addClassToLabel: () => {}, - removeClassFromLabel: () => {}, setIconAttr: () => {}, eventTargetHasClass: () => {}, registerTextFieldInteractionHandler: () => {}, @@ -74,6 +74,8 @@ class MDCTextFieldFoundation extends MDCFoundation { this.bottomLine_ = foundationMap.bottomLine; /** @type {!MDCTextFieldHelperTextFoundation|undefined} */ this.helperText_ = foundationMap.helperText; + /** @type {!MDCTextFieldLabelFoundation|undefined} */ + this.label_ = foundationMap.label; /** @private {boolean} */ this.isFocused_ = false; @@ -98,8 +100,8 @@ class MDCTextFieldFoundation extends MDCFoundation { init() { this.adapter_.addClass(MDCTextFieldFoundation.cssClasses.UPGRADED); // Ensure label does not collide with any pre-filled value. - if (this.getNativeInput_().value) { - this.adapter_.addClassToLabel(MDCTextFieldFoundation.cssClasses.LABEL_FLOAT_ABOVE); + if (this.getNativeInput_().value && this.label_) { + this.label_.floatLabel(); } this.adapter_.registerInputInteractionHandler('focus', this.inputFocusHandler_); @@ -155,13 +157,14 @@ class MDCTextFieldFoundation extends MDCFoundation { * Activates the text field focus state. */ activateFocus() { - const {FOCUSED, LABEL_FLOAT_ABOVE, LABEL_SHAKE} = MDCTextFieldFoundation.cssClasses; + const {FOCUSED} = MDCTextFieldFoundation.cssClasses; this.adapter_.addClass(FOCUSED); if (this.bottomLine_) { this.bottomLine_.activate(); } - this.adapter_.addClassToLabel(LABEL_FLOAT_ABOVE); - this.adapter_.removeClassFromLabel(LABEL_SHAKE); + if (this.label_) { + this.label_.floatLabel(); + } if (this.helperText_) { this.helperText_.showToScreenReader(); } @@ -206,15 +209,26 @@ class MDCTextFieldFoundation extends MDCFoundation { * Deactives the Text Field's focus state. */ deactivateFocus() { - const {FOCUSED, LABEL_FLOAT_ABOVE, LABEL_SHAKE} = MDCTextFieldFoundation.cssClasses; + const {FOCUSED} = MDCTextFieldFoundation.cssClasses; const input = this.getNativeInput_(); + const hasEmptyInput = !input.value && !this.isBadInput_(); + + // this.isFocused_ = false; + // this.adapter_.removeClass(FOCUSED); + // this.adapter_.removeClassFromLabel(LABEL_SHAKE); + + // if (!input.value && !this.isBadInput_()) { + // this.adapter_.removeClassFromLabel(LABEL_FLOAT_ABOVE); + // this.receivedUserInput_ = false; + // } this.isFocused_ = false; this.adapter_.removeClass(FOCUSED); - this.adapter_.removeClassFromLabel(LABEL_SHAKE); - if (!input.value && !this.isBadInput_()) { - this.adapter_.removeClassFromLabel(LABEL_FLOAT_ABOVE); + if (this.label_) { + this.label_.deactivateFocus(hasEmptyInput); + } + if (hasEmptyInput) { this.receivedUserInput_ = false; } @@ -229,16 +243,18 @@ class MDCTextFieldFoundation extends MDCFoundation { * @private */ changeValidity_(isValid) { - const {INVALID, LABEL_SHAKE} = MDCTextFieldFoundation.cssClasses; + const {INVALID} = MDCTextFieldFoundation.cssClasses; if (isValid) { this.adapter_.removeClass(INVALID); } else { - this.adapter_.addClassToLabel(LABEL_SHAKE); this.adapter_.addClass(INVALID); } if (this.helperText_) { this.helperText_.setValidity(isValid); } + if (this.label_) { + this.label_.changeValidity(isValid); + } } /** diff --git a/packages/mdc-textfield/index.js b/packages/mdc-textfield/index.js index 988446d0d5d..538455ef876 100644 --- a/packages/mdc-textfield/index.js +++ b/packages/mdc-textfield/index.js @@ -26,6 +26,7 @@ import MDCTextFieldFoundation from './foundation'; /* eslint-disable no-unused-vars */ import {MDCTextFieldBottomLine, MDCTextFieldBottomLineFoundation} from './bottom-line'; import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text'; +import {MDCTextFieldLabel, MDCTextFieldLabelFoundation} from './label'; /* eslint-enable no-unused-vars */ /** @@ -40,7 +41,7 @@ class MDCTextField extends MDCComponent { super(...args); /** @private {?Element} */ this.input_; - /** @private {?Element} */ + /** @private {?MDCTextFieldLabel} */ this.label_; /** @type {?MDCRipple} */ this.ripple; @@ -70,7 +71,10 @@ class MDCTextField extends MDCComponent { rippleFactory = (el, foundation) => new MDCRipple(el, foundation), bottomLineFactory = (el) => new MDCTextFieldBottomLine(el)) { this.input_ = this.root_.querySelector(strings.INPUT_SELECTOR); - this.label_ = this.root_.querySelector(strings.LABEL_SELECTOR); + const labelElement = this.root_.querySelector(strings.LABEL_SELECTOR); + if (labelElement) { + this.label_ = new MDCTextFieldLabel(labelElement); + } this.ripple = null; if (this.root_.classList.contains(cssClasses.BOX)) { const MATCHES = getMatchesProperty(HTMLElement.prototype); @@ -109,6 +113,9 @@ class MDCTextField extends MDCComponent { if (this.helperText_) { this.helperText_.destroy(); } + if (this.label_) { + this.label_.destroy(); + } super.destroy(); } @@ -157,18 +164,6 @@ class MDCTextField extends MDCComponent { /** @type {!MDCTextFieldAdapter} */ (Object.assign({ addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), - addClassToLabel: (className) => { - const label = this.label_; - if (label) { - label.classList.add(className); - } - }, - removeClassFromLabel: (className) => { - const label = this.label_; - if (label) { - label.classList.remove(className); - } - }, eventTargetHasClass: (target, className) => target.classList.contains(className), registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler), deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler), @@ -227,6 +222,7 @@ class MDCTextField extends MDCComponent { return { bottomLine: this.bottomLine_ ? this.bottomLine_.foundation : undefined, helperText: this.helperText_ ? this.helperText_.foundation : undefined, + label: this.label_ ? this.label_.foundation : undefined, }; } } diff --git a/packages/mdc-textfield/label/adapter.js b/packages/mdc-textfield/label/adapter.js new file mode 100644 index 00000000000..ea01a7fbfc9 --- /dev/null +++ b/packages/mdc-textfield/label/adapter.js @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint no-unused-vars: [2, {"args": "none"}] */ + +/** + * Adapter for MDC Text Field Label. + * + * Defines the shape of the adapter expected by the foundation. Implement this + * adapter to integrate the Text Field label into your framework. See + * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md + * for more information. + * + * @record + */ +class MDCTextFieldLabelAdapter { + /** + * Adds a class to the label element. + * @param {string} className + */ + addClass(className) {} + + /** + * Removes a class from the label element. + * @param {string} className + */ + removeClass(className) {} +} + +export default MDCTextFieldLabelAdapter; diff --git a/packages/mdc-textfield/label/constants.js b/packages/mdc-textfield/label/constants.js new file mode 100644 index 00000000000..2346fcf6630 --- /dev/null +++ b/packages/mdc-textfield/label/constants.js @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @enum {string} */ +const cssClasses = { + LABEL_FLOAT_ABOVE: 'mdc-text-field__label--float-above', + LABEL_SHAKE: 'mdc-text-field__label--shake', +}; + +export {cssClasses}; diff --git a/packages/mdc-textfield/label/foundation.js b/packages/mdc-textfield/label/foundation.js new file mode 100644 index 00000000000..ed133694be3 --- /dev/null +++ b/packages/mdc-textfield/label/foundation.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCFoundation from '@material/base/foundation'; +import MDCTextFieldLabelAdapter from './adapter'; +import {cssClasses} from './constants'; + + +/** + * @extends {MDCFoundation} + * @final + */ +class MDCTextFieldLabelFoundation extends MDCFoundation { + /** @return enum {string} */ + static get cssClasses() { + return cssClasses; + } + + /** + * {@see MDCTextFieldLabelAdapter} for typing information on parameters and return + * types. + * @return {!MDCTextFieldLabelAdapter} + */ + static get defaultAdapter() { + return /** @type {!MDCTextFieldLabelAdapter} */ ({ + addClass: () => {}, + removeClass: () => {}, + }); + } + + /** + * @param {!MDCTextFieldLabelAdapter=} adapter + */ + constructor(adapter = /** @type {!MDCTextFieldLabelAdapter} */ ({})) { + super(Object.assign(MDCTextFieldLabelFoundation.defaultAdapter, adapter)); + } + + /** Makes the label float above the text field. */ + floatLabel() { + this.adapter_.addClass(cssClasses.LABEL_FLOAT_ABOVE); + this.adapter_.removeClass(cssClasses.LABEL_SHAKE); + } + + /** + * Deactivates the label's focus state based on whether the text + * field input is empty. + * @param {boolean} hasEmptyInput + */ + deactivateFocus(hasEmptyInput) { + this.adapter_.removeClass(cssClasses.LABEL_SHAKE); + + if (hasEmptyInput) { + this.adapter_.removeClass(cssClasses.LABEL_FLOAT_ABOVE); + } + } + + /** + * Updates the label's valid state based on the supplied validity. + * @param {boolean} isValid + */ + changeValidity(isValid) { + if (!isValid) { + this.adapter_.addClass(cssClasses.LABEL_SHAKE); + } + } +} + +export default MDCTextFieldLabelFoundation; diff --git a/packages/mdc-textfield/label/index.js b/packages/mdc-textfield/label/index.js new file mode 100644 index 00000000000..a03f5b5bb15 --- /dev/null +++ b/packages/mdc-textfield/label/index.js @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import MDCComponent from '@material/base/component'; + +import MDCTextFieldLabelAdapter from './adapter'; +import MDCTextFieldLabelFoundation from './foundation'; + +/** + * @extends {MDCComponent} + * @final + */ +class MDCTextFieldLabel extends MDCComponent { + /** + * @param {!Element} root + * @return {!MDCTextFieldLabel} + */ + static attachTo(root) { + return new MDCTextFieldLabel(root); + } + + /** + * @return {MDCTextFieldLabelFoundation}. + */ + get foundation() { + return this.foundation_; + } + + /** + * @return {!MDCTextFieldLabelFoundation} + */ + getDefaultFoundation() { + return new MDCTextFieldLabelFoundation(/** @type {!MDCTextFieldLabelAdapter} */ (Object.assign({ + addClass: (className) => this.root_.classList.add(className), + removeClass: (className) => this.root_.classList.remove(className), + }))); + } +} + +export {MDCTextFieldLabel, MDCTextFieldLabelFoundation}; From 473a44624da5ecae0a4206bdf80ef60522e1d93b Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Tue, 5 Dec 2017 13:26:06 -0800 Subject: [PATCH 02/12] Fix existing tests --- packages/mdc-textfield/foundation.js | 20 ++----- packages/mdc-textfield/label/foundation.js | 8 +-- packages/mdc-textfield/label/index.js | 2 +- test/unit/mdc-textfield/foundation.test.js | 52 +++++++++++-------- .../unit/mdc-textfield/mdc-text-field.test.js | 26 ---------- 5 files changed, 39 insertions(+), 69 deletions(-) diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index 86e0b15ad80..135e09f150d 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -101,7 +101,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.adapter_.addClass(MDCTextFieldFoundation.cssClasses.UPGRADED); // Ensure label does not collide with any pre-filled value. if (this.getNativeInput_().value && this.label_) { - this.label_.floatLabel(); + this.label_.floatAbove(); } this.adapter_.registerInputInteractionHandler('focus', this.inputFocusHandler_); @@ -163,7 +163,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.bottomLine_.activate(); } if (this.label_) { - this.label_.floatLabel(); + this.label_.floatAbove(); } if (this.helperText_) { this.helperText_.showToScreenReader(); @@ -211,24 +211,14 @@ class MDCTextFieldFoundation extends MDCFoundation { deactivateFocus() { const {FOCUSED} = MDCTextFieldFoundation.cssClasses; const input = this.getNativeInput_(); - const hasEmptyInput = !input.value && !this.isBadInput_(); - - // this.isFocused_ = false; - // this.adapter_.removeClass(FOCUSED); - // this.adapter_.removeClassFromLabel(LABEL_SHAKE); - - // if (!input.value && !this.isBadInput_()) { - // this.adapter_.removeClassFromLabel(LABEL_FLOAT_ABOVE); - // this.receivedUserInput_ = false; - // } + const inputIsEmpty = !input.value && !this.isBadInput_(); this.isFocused_ = false; this.adapter_.removeClass(FOCUSED); - if (this.label_) { - this.label_.deactivateFocus(hasEmptyInput); + this.label_.deactivateFocus(inputIsEmpty); } - if (hasEmptyInput) { + if (inputIsEmpty) { this.receivedUserInput_ = false; } diff --git a/packages/mdc-textfield/label/foundation.js b/packages/mdc-textfield/label/foundation.js index ed133694be3..2b677a2c141 100644 --- a/packages/mdc-textfield/label/foundation.js +++ b/packages/mdc-textfield/label/foundation.js @@ -50,7 +50,7 @@ class MDCTextFieldLabelFoundation extends MDCFoundation { } /** Makes the label float above the text field. */ - floatLabel() { + floatAbove() { this.adapter_.addClass(cssClasses.LABEL_FLOAT_ABOVE); this.adapter_.removeClass(cssClasses.LABEL_SHAKE); } @@ -58,12 +58,12 @@ class MDCTextFieldLabelFoundation extends MDCFoundation { /** * Deactivates the label's focus state based on whether the text * field input is empty. - * @param {boolean} hasEmptyInput + * @param {boolean} inputIsEmpty */ - deactivateFocus(hasEmptyInput) { + deactivateFocus(inputIsEmpty) { this.adapter_.removeClass(cssClasses.LABEL_SHAKE); - if (hasEmptyInput) { + if (inputIsEmpty) { this.adapter_.removeClass(cssClasses.LABEL_FLOAT_ABOVE); } } diff --git a/packages/mdc-textfield/label/index.js b/packages/mdc-textfield/label/index.js index a03f5b5bb15..69ed8590b27 100644 --- a/packages/mdc-textfield/label/index.js +++ b/packages/mdc-textfield/label/index.js @@ -34,7 +34,7 @@ class MDCTextFieldLabel extends MDCComponent { } /** - * @return {MDCTextFieldLabelFoundation}. + * @return {!MDCTextFieldLabelFoundation}. */ get foundation() { return this.foundation_; diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index caba19e32e7..bb556fe2f64 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -35,8 +35,8 @@ test('exports cssClasses', () => { test('defaultAdapter returns a complete adapter implementation', () => { verifyDefaultAdapter(MDCTextFieldFoundation, [ - 'addClass', 'removeClass', 'addClassToLabel', 'removeClassFromLabel', - 'setIconAttr', 'eventTargetHasClass', 'registerTextFieldInteractionHandler', + 'addClass', 'removeClass', 'setIconAttr', + 'eventTargetHasClass', 'registerTextFieldInteractionHandler', 'deregisterTextFieldInteractionHandler', 'notifyIconAction', 'registerInputInteractionHandler', 'deregisterInputInteractionHandler', 'registerBottomLineEventHandler', 'deregisterBottomLineEventHandler', @@ -57,12 +57,18 @@ const setupTest = () => { showToScreenReader: () => {}, setValidity: () => {}, }); + const label = td.object({ + floatAbove: () => {}, + deactivateFocus: () => {}, + changeValidity: () => {}, + }); const foundationMap = { bottomLine: bottomLine, helperText: helperText, + label: label, }; const foundation = new MDCTextFieldFoundation(mockAdapter, foundationMap); - return {foundation, mockAdapter, bottomLine, helperText}; + return {foundation, mockAdapter, bottomLine, helperText, label}; }; test('#constructor sets disabled to false', () => { @@ -176,26 +182,26 @@ test('#destroy removes event listeners', () => { MDCTextFieldBottomLineFoundation.strings.ANIMATION_END_EVENT, td.matchers.isA(Function))); }); -test('#init adds mdc-text-field__label--float-above class if the input contains a value', () => { - const {foundation, mockAdapter} = setupTest(); +test('#init floats label if the input contains a value', () => { + const {foundation, mockAdapter, label} = setupTest(); td.when(mockAdapter.getNativeInput()).thenReturn({ value: 'Pre-filled value', disabled: false, checkValidity: () => true, }); foundation.init(); - td.verify(mockAdapter.addClassToLabel(cssClasses.LABEL_FLOAT_ABOVE)); + td.verify(label.floatAbove()); }); -test('#init does not add mdc-text-field__label--float-above class if the input does not contain a value', () => { - const {foundation, mockAdapter} = setupTest(); +test('#init does not float label if the input does not contain a value', () => { + const {foundation, mockAdapter, label} = setupTest(); td.when(mockAdapter.getNativeInput()).thenReturn({ value: '', disabled: false, checkValidity: () => true, }); foundation.init(); - td.verify(mockAdapter.addClassToLabel(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); + td.verify(label.floatAbove(), {times: 0}); }); test('#setHelperTextContent sets the content of the helper text element', () => { @@ -204,8 +210,8 @@ test('#setHelperTextContent sets the content of the helper text element', () => td.verify(helperText.setContent('foo')); }); -test('on input focuses if input event occurs without any other events', () => { - const {foundation, mockAdapter} = setupTest(); +test('on input floats label if input event occurs without any other events', () => { + const {foundation, mockAdapter, label} = setupTest(); let input; td.when(mockAdapter.registerInputInteractionHandler('input', td.matchers.isA(Function))) @@ -214,11 +220,11 @@ test('on input focuses if input event occurs without any other events', () => { }); foundation.init(); input(); - td.verify(mockAdapter.addClassToLabel(cssClasses.LABEL_FLOAT_ABOVE)); + td.verify(label.floatAbove()); }); test('on input does nothing if input event preceded by keydown event', () => { - const {foundation, mockAdapter} = setupTest(); + const {foundation, mockAdapter, label} = setupTest(); const mockEvt = { type: 'keydown', key: 'Enter', @@ -241,7 +247,7 @@ test('on input does nothing if input event preceded by keydown event', () => { foundation.init(); keydown(mockEvt); input(); - td.verify(mockAdapter.addClassToLabel(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); + td.verify(label.floatAbove(), {times: 0}); }); test('on focus adds mdc-text-field--focused class', () => { @@ -256,8 +262,8 @@ test('on focus adds mdc-text-field--focused class', () => { td.verify(mockAdapter.addClass(cssClasses.FOCUSED)); }); -test('on focus adds mdc-text-field__label--float-above class', () => { - const {foundation, mockAdapter} = setupTest(); +test('on focus floats label', () => { + const {foundation, mockAdapter, label} = setupTest(); let focus; td.when(mockAdapter.registerInputInteractionHandler('focus', td.matchers.isA(Function))) .thenDo((evtType, handler) => { @@ -265,7 +271,7 @@ test('on focus adds mdc-text-field__label--float-above class', () => { }); foundation.init(); focus(); - td.verify(mockAdapter.addClassToLabel(cssClasses.LABEL_FLOAT_ABOVE)); + td.verify(label.floatAbove()); }); test('on focus makes helper text visible to the screen reader', () => { @@ -281,7 +287,7 @@ test('on focus makes helper text visible to the screen reader', () => { }); const setupBlurTest = () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText, label} = setupTest(); let blur; td.when(mockAdapter.registerInputInteractionHandler('blur', td.matchers.isA(Function))).thenDo((evtType, handler) => { blur = handler; @@ -293,7 +299,7 @@ const setupBlurTest = () => { td.when(mockAdapter.getNativeInput()).thenReturn(nativeInput); foundation.init(); - return {foundation, mockAdapter, blur, nativeInput, helperText}; + return {foundation, mockAdapter, blur, nativeInput, helperText, label}; }; test('on blur removes mdc-text-field--focused class', () => { @@ -303,16 +309,16 @@ test('on blur removes mdc-text-field--focused class', () => { }); test('on blur removes mdc-text-field__label--float-above when no input value present', () => { - const {mockAdapter, blur} = setupBlurTest(); + const {blur, label} = setupBlurTest(); blur(); - td.verify(mockAdapter.removeClassFromLabel(cssClasses.LABEL_FLOAT_ABOVE)); + td.verify(label.deactivateFocus(true /* inputIsEmpty */)); }); test('on blur does not remove mdc-text-field__label--float-above if input has a value', () => { - const {mockAdapter, blur, nativeInput} = setupBlurTest(); + const {blur, nativeInput, label} = setupBlurTest(); nativeInput.value = 'non-empty value'; blur(); - td.verify(mockAdapter.removeClassFromLabel(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); + td.verify(label.deactivateFocus(false /* inputIsEmpty */)); }); test('on blur removes mdc-text-field--invalid if custom validity is false and' + diff --git a/test/unit/mdc-textfield/mdc-text-field.test.js b/test/unit/mdc-textfield/mdc-text-field.test.js index f0e55ef1b18..c6e730cd9bd 100644 --- a/test/unit/mdc-textfield/mdc-text-field.test.js +++ b/test/unit/mdc-textfield/mdc-text-field.test.js @@ -192,32 +192,6 @@ test('#adapter.removeClass removes a class from the root element', () => { assert.isNotOk(root.classList.contains('foo')); }); -test('#adapter.addClassToLabel adds a class to the label element', () => { - const {root, component} = setupTest(); - component.getDefaultFoundation().adapter_.addClassToLabel('foo'); - assert.isOk(root.querySelector('.mdc-text-field__label').classList.contains('foo')); -}); - -test('#adapter.addClassToLabel does nothing if no label element present', () => { - const {root, component} = setupTest(); - root.removeChild(root.querySelector('.mdc-text-field__label')); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.addClassToLabel('foo')); -}); - -test('#adapter.removeClassFromLabel removes a class from the label element', () => { - const {root, component} = setupTest(); - const label = root.querySelector('.mdc-text-field__label'); - label.classList.add('foo'); - component.getDefaultFoundation().adapter_.removeClassFromLabel('foo'); - assert.isNotOk(label.classList.contains('foo')); -}); - -test('#adapter.removeClassFromLabel does nothing if no label element present', () => { - const {root, component} = setupTest(); - root.removeChild(root.querySelector('.mdc-text-field__label')); - assert.doesNotThrow(() => component.getDefaultFoundation().adapter_.removeClassFromLabel('foo')); -}); - test('#adapter.registerInputInteractionHandler adds a handler to the input element for a given event', () => { const {root, component} = setupTest(); const input = root.querySelector('.mdc-text-field__input'); From 878f28dab21aecf42776d3c6d1639584410058a4 Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Tue, 5 Dec 2017 13:36:19 -0800 Subject: [PATCH 03/12] Add label tests --- .../mdc-text-field-label-foundation.test.js | 62 +++++++++++++++++++ .../mdc-text-field-label.test.js | 49 +++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js create mode 100644 test/unit/mdc-textfield/mdc-text-field-label.test.js diff --git a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js new file mode 100644 index 00000000000..75a05af851f --- /dev/null +++ b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js @@ -0,0 +1,62 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {assert} from 'chai'; +import td from 'testdouble'; + +import {verifyDefaultAdapter} from '../helpers/foundation'; +import {setupFoundationTest} from '../helpers/setup'; +import MDCTextFieldLabelFoundation from '../../../packages/mdc-textfield/label/foundation'; + +const {cssClasses} = MDCTextFieldLabelFoundation; + +suite('MDCTextFieldLabelFoundation'); + +test('exports cssClasses', () => { + assert.isOk('cssClasses' in MDCTextFieldLabelFoundation); +}); + +test('defaultAdapter returns a complete adapter implementation', () => { + verifyDefaultAdapter(MDCTextFieldLabelFoundation, [ + 'addClass', 'removeClass', + ]); +}); + +const setupTest = () => setupFoundationTest(MDCTextFieldLabelFoundation); + +test('#floatAbove adds mdc-text-field__label--float-above class', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.floatAbove(); + td.verify(mockAdapter.addClass(cssClasses.LABEL_FLOAT_ABOVE)); +}); + +test('#deactivateFocus does not remove mdc-text-field__label--float-above class if hasEmptyInput is false', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.deactivateFocus(false); + td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); +}); + +test('#deactivateFocus removes mdc-text-field__label--float-above class if hasEmptyInput is true', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.deactivateFocus(true); + td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE)); +}); + +test('#changeValidity adds mdc-text-field__label--shake class if isValid is false', () => { + const {foundation, mockAdapter} = setupTest(); + foundation.changeValidity(false); + td.verify(mockAdapter.addClass(cssClasses.LABEL_SHAKE)); +}); diff --git a/test/unit/mdc-textfield/mdc-text-field-label.test.js b/test/unit/mdc-textfield/mdc-text-field-label.test.js new file mode 100644 index 00000000000..aee9b0ea93c --- /dev/null +++ b/test/unit/mdc-textfield/mdc-text-field-label.test.js @@ -0,0 +1,49 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import bel from 'bel'; +import {assert} from 'chai'; + +import {MDCTextFieldLabel} from '../../../packages/mdc-textfield/label'; + +const getFixture = () => bel` +
+`; + +suite('MDCTextFieldLabel'); + +test('attachTo returns an MDCTextFieldLabel instance', () => { + assert.isOk(MDCTextFieldLabel.attachTo(getFixture()) instanceof MDCTextFieldLabel); +}); + +function setupTest() { + const root = getFixture(); + const component = new MDCTextFieldLabel(root); + return {root, component}; +} + +test('#adapter.addClass adds a class to the element', () => { + const {root, component} = setupTest(); + component.getDefaultFoundation().adapter_.addClass('foo'); + assert.isTrue(root.classList.contains('foo')); +}); + +test('#adapter.removeClass removes a class from the element', () => { + const {root, component} = setupTest(); + root.classList.add('foo'); + component.getDefaultFoundation().adapter_.removeClass('foo'); + assert.isFalse(root.classList.contains('foo')); +}); From a7a182c6d9bb44e3097698988d767faf22e1279d Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Tue, 5 Dec 2017 13:49:18 -0800 Subject: [PATCH 04/12] Split out CSS --- .../label/mdc-text-field-label.scss | 91 +++++++++++++++++++ packages/mdc-textfield/mdc-text-field.scss | 64 +------------ 2 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 packages/mdc-textfield/label/mdc-text-field-label.scss diff --git a/packages/mdc-textfield/label/mdc-text-field-label.scss b/packages/mdc-textfield/label/mdc-text-field-label.scss new file mode 100644 index 00000000000..f3947142233 --- /dev/null +++ b/packages/mdc-textfield/label/mdc-text-field-label.scss @@ -0,0 +1,91 @@ +// +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@import "../variables"; +@import "@material/rtl/mixins"; +@import "@material/theme/variables"; +@import "@material/theme/mixins"; + +@include mdc-text-field-invalid-label-shake_keyframes_(standard, 100%); +@include mdc-text-field-invalid-label-shake_keyframes_(box, 50%); + +// postcss-bem-linter: define text-field-label +.mdc-text-field { + &__label { + position: absolute; + bottom: 8px; + left: 0; + transform-origin: left top; + transition: mdc-text-field-transition(transform), mdc-text-field-transition(color); + color: $mdc-text-field-underline-on-light-idle; + cursor: text; + + // stylelint-disable plugin/selector-bem-pattern + @include mdc-rtl(".mdc-text-field") { + right: 0; + left: auto; + transform-origin: right top; + } + // stylelint-enable plugin/selector-bem-pattern + + @include mdc-theme-dark(".mdc-text-field") { + @include mdc-theme-prop(color, $mdc-text-field-dark-label); + } + + &--float-above { + transform: translateY(-100%) scale(.75, .75); + cursor: auto; + } + } +} + +// stylelint-disable plugin/selector-bem-pattern + +.mdc-text-field__label--float-above { + &.mdc-text-field__label--shake { + animation: invalid-shake-float-above-standard 250ms 1; + } +} + +// Move label when text-field gets autofilled in Chrome +.mdc-text-field__input { + &:-webkit-autofill + .mdc-text-field__label { + transform: translateY(-100%) scale(.75, .75); + cursor: auto; + } +} + +// stylelint-enable plugin/selector-bem-pattern + +.mdc-text-field__input:required + .mdc-text-field__label::after { + margin-left: 1px; + content: "*"; + + .mdc-text-field--focused & { + color: $mdc-text-field-error-on-light; + + @include mdc-theme-dark(".mdc-text-field", true) { + color: $mdc-text-field-error-on-dark; + } + } +} + +// mdc-form-field tweaks to align text field label correctly +// stylelint-disable selector-max-type +.mdc-form-field > .mdc-text-field + label { + align-self: flex-start; +} +// stylelint-enable selector-max-type diff --git a/packages/mdc-textfield/mdc-text-field.scss b/packages/mdc-textfield/mdc-text-field.scss index 09913a264a7..42f6ffbe45b 100644 --- a/packages/mdc-textfield/mdc-text-field.scss +++ b/packages/mdc-textfield/mdc-text-field.scss @@ -26,6 +26,7 @@ @import "@material/typography/variables"; @import "./bottom-line/mdc-text-field-bottom-line"; @import "./helper-text/mdc-text-field-helper-text"; +@import "./label/mdc-text-field-label"; @include mdc-text-field-invalid-label-shake_keyframes_(standard, 100%); @include mdc-text-field-invalid-label-shake_keyframes_(box, 50%); @@ -100,43 +101,6 @@ } } } - - &__label { - position: absolute; - bottom: 8px; - left: 0; - transform-origin: left top; - transition: mdc-text-field-transition(transform), mdc-text-field-transition(color); - color: $mdc-text-field-underline-on-light-idle; - cursor: text; - - // stylelint-disable plugin/selector-bem-pattern - @include mdc-rtl(".mdc-text-field") { - right: 0; - left: auto; - transform-origin: right top; - } - // stylelint-enable plugin/selector-bem-pattern - - @include mdc-theme-dark(".mdc-text-field") { - @include mdc-theme-prop(color, $mdc-text-field-dark-label); - } - - &--float-above { - transform: translateY(-100%) scale(.75, .75); - cursor: auto; - } - } -} - -// Move label when text-field gets autofilled in Chrome -.mdc-text-field__input { - // stylelint-disable plugin/selector-bem-pattern - &:-webkit-autofill + .mdc-text-field__label { - transform: translateY(-100%) scale(.75, .75); - cursor: auto; - } - // stylelint-enable plugin/selector-bem-pattern } .mdc-text-field--box { @@ -349,15 +313,11 @@ } // stylelint-disable plugin/selector-bem-pattern + .mdc-text-field--invalid.mdc-text-field--textarea { border-color: $mdc-text-field-error-on-light; } -.mdc-text-field__label--float-above { - &.mdc-text-field__label--shake { - animation: invalid-shake-float-above-standard 250ms 1; - } -} // stylelint-enable plugin/selector-bem-pattern .mdc-text-field--dense { @@ -411,19 +371,6 @@ } } -.mdc-text-field__input:required + .mdc-text-field__label::after { - margin-left: 1px; - content: "*"; - - .mdc-text-field--focused & { - color: $mdc-text-field-error-on-light; - - @include mdc-theme-dark(".mdc-text-field", true) { - color: $mdc-text-field-error-on-dark; - } - } -} - .mdc-text-field--textarea { @include mdc-text-field-textarea-corner-radius($mdc-text-field-border-radius); @@ -650,10 +597,3 @@ } } } - -// mdc-form-field tweaks to align text field label correctly -// stylelint-disable selector-max-type -.mdc-form-field > .mdc-text-field + label { - align-self: flex-start; -} -// stylelint-enable selector-max-type From 7ee923c5120159b362462908c4b3280f5b3d068b Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Tue, 5 Dec 2017 13:52:17 -0800 Subject: [PATCH 05/12] Update READMEs --- packages/mdc-textfield/README.md | 2 - packages/mdc-textfield/label/README.md | 50 +++++++++++++++++++ .../mdc-text-field-label-foundation.test.js | 4 +- 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 packages/mdc-textfield/label/README.md diff --git a/packages/mdc-textfield/README.md b/packages/mdc-textfield/README.md index 1659b51389e..82f9b903d0c 100644 --- a/packages/mdc-textfield/README.md +++ b/packages/mdc-textfield/README.md @@ -325,8 +325,6 @@ complicated. | --- | --- | | addClass(className: string) => void | Adds a class to the root element | | removeClass(className: string) => void | Removes a class from the root element | -| addClassToLabel(className: string) => void | Adds a class to the label element. We recommend you add a conditional check here, and in `removeClassFromLabel` for whether or not the label is present so that the JS component could be used with text fields that don't require a label, such as the full-width text field. | -| removeClassFromLabel(className: string) => void | Removes a class from the label element | | eventTargetHasClass(target: HTMLElement, className: string) => boolean | Returns true if classname exists for a given target element | | registerTextFieldInteractionHandler(evtType: string, handler: EventListener) => void | Registers an event handler on the root element for a given event | | deregisterTextFieldInteractionHandler(evtType: string, handler: EventListener) => void | Deregisters an event handler on the root element for a given event | diff --git a/packages/mdc-textfield/label/README.md b/packages/mdc-textfield/label/README.md new file mode 100644 index 00000000000..8359bc379a3 --- /dev/null +++ b/packages/mdc-textfield/label/README.md @@ -0,0 +1,50 @@ + + +# Text Field Label + +The label is a text caption or description for the text field. + +## Design & API Documentation + + + + +## Usage + +#### MDCTextFieldLabel API + +##### MDCTextFieldLabel.foundation + +MDCTextFieldLabelFoundation. This allows the parent MDCTextField component to access the public methods on the MDCTextFieldLabelFoundation class. + +### Using the foundation class + +Method Signature | Description +--- | --- +addClass(className: string) => void | Adds a class to the label element +removeClass(className: string) => void | Removes a class from the label element + +#### The full foundation API + +##### MDCTextFieldLabelFoundation.floatAbove() + +Makes the label float above the text field. + +##### MDCTextFieldLabelFoundation.deactivateFocus(inputIsEmpty: boolean) + +Deactivates the label's focus state based on whether the text field input is empty. + +##### MDCTextFieldLabelFoundation.changeValidity(isValid: boolean) + +Updates the label's valid state based on the supplied validity. diff --git a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js index 75a05af851f..d331194d727 100644 --- a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js @@ -43,13 +43,13 @@ test('#floatAbove adds mdc-text-field__label--float-above class', () => { td.verify(mockAdapter.addClass(cssClasses.LABEL_FLOAT_ABOVE)); }); -test('#deactivateFocus does not remove mdc-text-field__label--float-above class if hasEmptyInput is false', () => { +test('#deactivateFocus does not remove mdc-text-field__label--float-above class if inputIsEmpty is false', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(false); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); }); -test('#deactivateFocus removes mdc-text-field__label--float-above class if hasEmptyInput is true', () => { +test('#deactivateFocus removes mdc-text-field__label--float-above class if inputIsEmpty is true', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(true); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE)); From f490e7a7531010aa4a6a9e7505dc74ed716198ca Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Wed, 6 Dec 2017 15:29:07 -0800 Subject: [PATCH 06/12] Flatten CSS selector --- .../label/mdc-text-field-label.scss | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/mdc-textfield/label/mdc-text-field-label.scss b/packages/mdc-textfield/label/mdc-text-field-label.scss index f3947142233..abae62f731b 100644 --- a/packages/mdc-textfield/label/mdc-text-field-label.scss +++ b/packages/mdc-textfield/label/mdc-text-field-label.scss @@ -23,32 +23,30 @@ @include mdc-text-field-invalid-label-shake_keyframes_(box, 50%); // postcss-bem-linter: define text-field-label -.mdc-text-field { - &__label { - position: absolute; - bottom: 8px; - left: 0; - transform-origin: left top; - transition: mdc-text-field-transition(transform), mdc-text-field-transition(color); - color: $mdc-text-field-underline-on-light-idle; - cursor: text; +.mdc-text-field__label { + position: absolute; + bottom: 8px; + left: 0; + transform-origin: left top; + transition: mdc-text-field-transition(transform), mdc-text-field-transition(color); + color: $mdc-text-field-underline-on-light-idle; + cursor: text; - // stylelint-disable plugin/selector-bem-pattern - @include mdc-rtl(".mdc-text-field") { - right: 0; - left: auto; - transform-origin: right top; - } - // stylelint-enable plugin/selector-bem-pattern + // stylelint-disable plugin/selector-bem-pattern + @include mdc-rtl(".mdc-text-field") { + right: 0; + left: auto; + transform-origin: right top; + } + // stylelint-enable plugin/selector-bem-pattern - @include mdc-theme-dark(".mdc-text-field") { - @include mdc-theme-prop(color, $mdc-text-field-dark-label); - } + @include mdc-theme-dark(".mdc-text-field") { + @include mdc-theme-prop(color, $mdc-text-field-dark-label); + } - &--float-above { - transform: translateY(-100%) scale(.75, .75); - cursor: auto; - } + &--float-above { + transform: translateY(-100%) scale(.75, .75); + cursor: auto; } } From 61b4e48115a59bae7580efe5871d30c213911646 Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Wed, 6 Dec 2017 15:49:50 -0800 Subject: [PATCH 07/12] Change inputIsEmpty name to inputIsEmptyAndValid --- test/unit/mdc-textfield/foundation.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index bb556fe2f64..a5c3ee5fb39 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -308,17 +308,18 @@ test('on blur removes mdc-text-field--focused class', () => { td.verify(mockAdapter.removeClass(cssClasses.FOCUSED)); }); -test('on blur removes mdc-text-field__label--float-above when no input value present', () => { +test('on blur deactivates label focus with inputIsEmptyAndValid=true when no input value present and ' + + 'validity checks pass', () => { const {blur, label} = setupBlurTest(); blur(); - td.verify(label.deactivateFocus(true /* inputIsEmpty */)); + td.verify(label.deactivateFocus(true /* inputIsEmptyAndValid */)); }); -test('on blur does not remove mdc-text-field__label--float-above if input has a value', () => { +test('on blur deactivates label focus with inputIsEmptyAndValid=false if input has a value', () => { const {blur, nativeInput, label} = setupBlurTest(); nativeInput.value = 'non-empty value'; blur(); - td.verify(label.deactivateFocus(false /* inputIsEmpty */)); + td.verify(label.deactivateFocus(false /* inputIsEmptyAndValid */)); }); test('on blur removes mdc-text-field--invalid if custom validity is false and' + From 81886dc49d0ff3af2d0c61239116ddaef12923fe Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Wed, 6 Dec 2017 15:50:37 -0800 Subject: [PATCH 08/12] More for prev commit, forgot to save --- packages/mdc-textfield/foundation.js | 6 +++--- packages/mdc-textfield/label/README.md | 4 ++-- packages/mdc-textfield/label/foundation.js | 8 ++++---- .../mdc-textfield/mdc-text-field-label-foundation.test.js | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index 135e09f150d..199accd0e2d 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -211,14 +211,14 @@ class MDCTextFieldFoundation extends MDCFoundation { deactivateFocus() { const {FOCUSED} = MDCTextFieldFoundation.cssClasses; const input = this.getNativeInput_(); - const inputIsEmpty = !input.value && !this.isBadInput_(); + const inputIsEmptyAndValid = !input.value && !this.isBadInput_(); this.isFocused_ = false; this.adapter_.removeClass(FOCUSED); if (this.label_) { - this.label_.deactivateFocus(inputIsEmpty); + this.label_.deactivateFocus(inputIsEmptyAndValid); } - if (inputIsEmpty) { + if (inputIsEmptyAndValid) { this.receivedUserInput_ = false; } diff --git a/packages/mdc-textfield/label/README.md b/packages/mdc-textfield/label/README.md index 8359bc379a3..95b93a45b70 100644 --- a/packages/mdc-textfield/label/README.md +++ b/packages/mdc-textfield/label/README.md @@ -41,9 +41,9 @@ removeClass(className: string) => void | Removes a class from the label element Makes the label float above the text field. -##### MDCTextFieldLabelFoundation.deactivateFocus(inputIsEmpty: boolean) +##### MDCTextFieldLabelFoundation.deactivateFocus(inputIsEmptyAndValid: boolean) -Deactivates the label's focus state based on whether the text field input is empty. +Deactivates the label's focus state based on whether the text field input is empty and passes validity checks. ##### MDCTextFieldLabelFoundation.changeValidity(isValid: boolean) diff --git a/packages/mdc-textfield/label/foundation.js b/packages/mdc-textfield/label/foundation.js index 2b677a2c141..613b73f6ab9 100644 --- a/packages/mdc-textfield/label/foundation.js +++ b/packages/mdc-textfield/label/foundation.js @@ -57,13 +57,13 @@ class MDCTextFieldLabelFoundation extends MDCFoundation { /** * Deactivates the label's focus state based on whether the text - * field input is empty. - * @param {boolean} inputIsEmpty + * field input is empty and passes validity checks. + * @param {boolean} inputIsEmptyAndValid */ - deactivateFocus(inputIsEmpty) { + deactivateFocus(inputIsEmptyAndValid) { this.adapter_.removeClass(cssClasses.LABEL_SHAKE); - if (inputIsEmpty) { + if (inputIsEmptyAndValid) { this.adapter_.removeClass(cssClasses.LABEL_FLOAT_ABOVE); } } diff --git a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js index d331194d727..c06877e8eb1 100644 --- a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js @@ -43,13 +43,13 @@ test('#floatAbove adds mdc-text-field__label--float-above class', () => { td.verify(mockAdapter.addClass(cssClasses.LABEL_FLOAT_ABOVE)); }); -test('#deactivateFocus does not remove mdc-text-field__label--float-above class if inputIsEmpty is false', () => { +test('#deactivateFocus does not remove mdc-text-field__label--float-above class if inputIsEmptyAndValid is false', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(false); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); }); -test('#deactivateFocus removes mdc-text-field__label--float-above class if inputIsEmpty is true', () => { +test('#deactivateFocus removes mdc-text-field__label--float-above class if inputIsEmptyAndValid is true', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(true); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE)); From 76eeea4926c58a16a4e3bea5b4e000532b0bdf88 Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Wed, 6 Dec 2017 15:56:01 -0800 Subject: [PATCH 09/12] Change changeValidity method name to setValidity --- packages/mdc-textfield/foundation.js | 2 +- packages/mdc-textfield/label/README.md | 2 +- packages/mdc-textfield/label/foundation.js | 2 +- test/unit/mdc-textfield/foundation.test.js | 2 +- .../mdc-textfield/mdc-text-field-label-foundation.test.js | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index 199accd0e2d..1a64a4f431e 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -243,7 +243,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.helperText_.setValidity(isValid); } if (this.label_) { - this.label_.changeValidity(isValid); + this.label_.setValidity(isValid); } } diff --git a/packages/mdc-textfield/label/README.md b/packages/mdc-textfield/label/README.md index 95b93a45b70..1c0dbe2bc5a 100644 --- a/packages/mdc-textfield/label/README.md +++ b/packages/mdc-textfield/label/README.md @@ -45,6 +45,6 @@ Makes the label float above the text field. Deactivates the label's focus state based on whether the text field input is empty and passes validity checks. -##### MDCTextFieldLabelFoundation.changeValidity(isValid: boolean) +##### MDCTextFieldLabelFoundation.setValidity(isValid: boolean) Updates the label's valid state based on the supplied validity. diff --git a/packages/mdc-textfield/label/foundation.js b/packages/mdc-textfield/label/foundation.js index 613b73f6ab9..f668fcefee6 100644 --- a/packages/mdc-textfield/label/foundation.js +++ b/packages/mdc-textfield/label/foundation.js @@ -72,7 +72,7 @@ class MDCTextFieldLabelFoundation extends MDCFoundation { * Updates the label's valid state based on the supplied validity. * @param {boolean} isValid */ - changeValidity(isValid) { + setValidity(isValid) { if (!isValid) { this.adapter_.addClass(cssClasses.LABEL_SHAKE); } diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index a5c3ee5fb39..208c6b02425 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -60,7 +60,7 @@ const setupTest = () => { const label = td.object({ floatAbove: () => {}, deactivateFocus: () => {}, - changeValidity: () => {}, + setValidity: () => {}, }); const foundationMap = { bottomLine: bottomLine, diff --git a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js index c06877e8eb1..2394fd1c283 100644 --- a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js @@ -43,20 +43,20 @@ test('#floatAbove adds mdc-text-field__label--float-above class', () => { td.verify(mockAdapter.addClass(cssClasses.LABEL_FLOAT_ABOVE)); }); -test('#deactivateFocus does not remove mdc-text-field__label--float-above class if inputIsEmptyAndValid is false', () => { +test('#deactivateFocus does not remove mdc-text-field__label--float-above class if inputIsEmptyAndValid=false', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(false); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); }); -test('#deactivateFocus removes mdc-text-field__label--float-above class if inputIsEmptyAndValid is true', () => { +test('#deactivateFocus removes mdc-text-field__label--float-above class if inputIsEmptyAndValid=true', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(true); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE)); }); -test('#changeValidity adds mdc-text-field__label--shake class if isValid is false', () => { +test('#setValidity adds mdc-text-field__label--shake class if isValid is false', () => { const {foundation, mockAdapter} = setupTest(); - foundation.changeValidity(false); + foundation.setValidity(false); td.verify(mockAdapter.addClass(cssClasses.LABEL_SHAKE)); }); From 85416afa4aabfc58306f6d1a5e0836b904de7681 Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Wed, 6 Dec 2017 16:01:45 -0800 Subject: [PATCH 10/12] Move some css back to toplevel --- .../label/mdc-text-field-label.scss | 30 ------------------- packages/mdc-textfield/mdc-text-field.scss | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/mdc-textfield/label/mdc-text-field-label.scss b/packages/mdc-textfield/label/mdc-text-field-label.scss index abae62f731b..cea5e25655e 100644 --- a/packages/mdc-textfield/label/mdc-text-field-label.scss +++ b/packages/mdc-textfield/label/mdc-text-field-label.scss @@ -51,39 +51,9 @@ } // stylelint-disable plugin/selector-bem-pattern - .mdc-text-field__label--float-above { &.mdc-text-field__label--shake { animation: invalid-shake-float-above-standard 250ms 1; } } - -// Move label when text-field gets autofilled in Chrome -.mdc-text-field__input { - &:-webkit-autofill + .mdc-text-field__label { - transform: translateY(-100%) scale(.75, .75); - cursor: auto; - } -} - // stylelint-enable plugin/selector-bem-pattern - -.mdc-text-field__input:required + .mdc-text-field__label::after { - margin-left: 1px; - content: "*"; - - .mdc-text-field--focused & { - color: $mdc-text-field-error-on-light; - - @include mdc-theme-dark(".mdc-text-field", true) { - color: $mdc-text-field-error-on-dark; - } - } -} - -// mdc-form-field tweaks to align text field label correctly -// stylelint-disable selector-max-type -.mdc-form-field > .mdc-text-field + label { - align-self: flex-start; -} -// stylelint-enable selector-max-type diff --git a/packages/mdc-textfield/mdc-text-field.scss b/packages/mdc-textfield/mdc-text-field.scss index 42f6ffbe45b..62471a08d2d 100644 --- a/packages/mdc-textfield/mdc-text-field.scss +++ b/packages/mdc-textfield/mdc-text-field.scss @@ -103,6 +103,16 @@ } } +// Move label when text-field gets autofilled in Chrome +.mdc-text-field__input { + // stylelint-disable plugin/selector-bem-pattern + &:-webkit-autofill + .mdc-text-field__label { + transform: translateY(-100%) scale(.75, .75); + cursor: auto; + } + // stylelint-enable plugin/selector-bem-pattern +} + .mdc-text-field--box { @include mdc-ripple-surface; @include mdc-states(text-primary-on-light, $has-nested-focusable-element: true); @@ -371,6 +381,19 @@ } } +.mdc-text-field__input:required + .mdc-text-field__label::after { + margin-left: 1px; + content: "*"; + + .mdc-text-field--focused & { + color: $mdc-text-field-error-on-light; + + @include mdc-theme-dark(".mdc-text-field", true) { + color: $mdc-text-field-error-on-dark; + } + } +} + .mdc-text-field--textarea { @include mdc-text-field-textarea-corner-radius($mdc-text-field-border-radius); @@ -597,3 +620,10 @@ } } } + +// mdc-form-field tweaks to align text field label correctly +// stylelint-disable selector-max-type +.mdc-form-field > .mdc-text-field + label { + align-self: flex-start; +} +// stylelint-enable selector-max-type From cd71ffd42df229b32143b81c43734a68535859aa Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Mon, 11 Dec 2017 13:24:50 -0800 Subject: [PATCH 11/12] Change inputIsEmptyAndValid variable name to shouldRemoveLabelFloat --- packages/mdc-textfield/foundation.js | 6 +++--- packages/mdc-textfield/label/README.md | 4 ++-- packages/mdc-textfield/label/foundation.js | 6 +++--- test/unit/mdc-textfield/foundation.test.js | 8 ++++---- .../mdc-textfield/mdc-text-field-label-foundation.test.js | 5 +++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.js index 1a64a4f431e..5e785fe457e 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.js @@ -211,14 +211,14 @@ class MDCTextFieldFoundation extends MDCFoundation { deactivateFocus() { const {FOCUSED} = MDCTextFieldFoundation.cssClasses; const input = this.getNativeInput_(); - const inputIsEmptyAndValid = !input.value && !this.isBadInput_(); + const shouldRemoveLabelFloat = !input.value && !this.isBadInput_(); this.isFocused_ = false; this.adapter_.removeClass(FOCUSED); if (this.label_) { - this.label_.deactivateFocus(inputIsEmptyAndValid); + this.label_.deactivateFocus(shouldRemoveLabelFloat); } - if (inputIsEmptyAndValid) { + if (shouldRemoveLabelFloat) { this.receivedUserInput_ = false; } diff --git a/packages/mdc-textfield/label/README.md b/packages/mdc-textfield/label/README.md index 1c0dbe2bc5a..d46e7d01d50 100644 --- a/packages/mdc-textfield/label/README.md +++ b/packages/mdc-textfield/label/README.md @@ -41,9 +41,9 @@ removeClass(className: string) => void | Removes a class from the label element Makes the label float above the text field. -##### MDCTextFieldLabelFoundation.deactivateFocus(inputIsEmptyAndValid: boolean) +##### MDCTextFieldLabelFoundation.deactivateFocus(shouldRemoveLabelFloat: boolean) -Deactivates the label's focus state based on whether the text field input is empty and passes validity checks. +Deactivates the label's focus state. ##### MDCTextFieldLabelFoundation.setValidity(isValid: boolean) diff --git a/packages/mdc-textfield/label/foundation.js b/packages/mdc-textfield/label/foundation.js index f668fcefee6..aae2942a361 100644 --- a/packages/mdc-textfield/label/foundation.js +++ b/packages/mdc-textfield/label/foundation.js @@ -58,12 +58,12 @@ class MDCTextFieldLabelFoundation extends MDCFoundation { /** * Deactivates the label's focus state based on whether the text * field input is empty and passes validity checks. - * @param {boolean} inputIsEmptyAndValid + * @param {boolean} shouldRemoveLabelFloat */ - deactivateFocus(inputIsEmptyAndValid) { + deactivateFocus(shouldRemoveLabelFloat) { this.adapter_.removeClass(cssClasses.LABEL_SHAKE); - if (inputIsEmptyAndValid) { + if (shouldRemoveLabelFloat) { this.adapter_.removeClass(cssClasses.LABEL_FLOAT_ABOVE); } } diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 208c6b02425..49dd6f386df 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -308,18 +308,18 @@ test('on blur removes mdc-text-field--focused class', () => { td.verify(mockAdapter.removeClass(cssClasses.FOCUSED)); }); -test('on blur deactivates label focus with inputIsEmptyAndValid=true when no input value present and ' +test('on blur deactivates label focus with shouldRemoveLabelFloat=true when no input value present and ' + 'validity checks pass', () => { const {blur, label} = setupBlurTest(); blur(); - td.verify(label.deactivateFocus(true /* inputIsEmptyAndValid */)); + td.verify(label.deactivateFocus(true /* shouldRemoveLabelFloat */)); }); -test('on blur deactivates label focus with inputIsEmptyAndValid=false if input has a value', () => { +test('on blur deactivates label focus with shouldRemoveLabelFloat=false if input has a value', () => { const {blur, nativeInput, label} = setupBlurTest(); nativeInput.value = 'non-empty value'; blur(); - td.verify(label.deactivateFocus(false /* inputIsEmptyAndValid */)); + td.verify(label.deactivateFocus(false /* shouldRemoveLabelFloat */)); }); test('on blur removes mdc-text-field--invalid if custom validity is false and' + diff --git a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js index 2394fd1c283..62233565d75 100644 --- a/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-label-foundation.test.js @@ -43,13 +43,14 @@ test('#floatAbove adds mdc-text-field__label--float-above class', () => { td.verify(mockAdapter.addClass(cssClasses.LABEL_FLOAT_ABOVE)); }); -test('#deactivateFocus does not remove mdc-text-field__label--float-above class if inputIsEmptyAndValid=false', () => { +test('#deactivateFocus does not remove mdc-text-field__label--float-above class' + + 'if shouldRemoveLabelFloat=false', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(false); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE), {times: 0}); }); -test('#deactivateFocus removes mdc-text-field__label--float-above class if inputIsEmptyAndValid=true', () => { +test('#deactivateFocus removes mdc-text-field__label--float-above class if shouldRemoveLabelFloat=true', () => { const {foundation, mockAdapter} = setupTest(); foundation.deactivateFocus(true); td.verify(mockAdapter.removeClass(cssClasses.LABEL_FLOAT_ABOVE)); From c7e9af997dde3857e469cffb269a2f40275ed6be Mon Sep 17 00:00:00 2001 From: Bonnie Zhou Date: Mon, 11 Dec 2017 14:51:11 -0800 Subject: [PATCH 12/12] Add comment to README --- packages/mdc-textfield/label/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mdc-textfield/label/README.md b/packages/mdc-textfield/label/README.md index d46e7d01d50..ba8db157b7d 100644 --- a/packages/mdc-textfield/label/README.md +++ b/packages/mdc-textfield/label/README.md @@ -43,7 +43,7 @@ Makes the label float above the text field. ##### MDCTextFieldLabelFoundation.deactivateFocus(shouldRemoveLabelFloat: boolean) -Deactivates the label's focus state. +Deactivates the label's focus state. `shouldRemoveLabelFloat` indicates whether to also reset the label's position and size. ##### MDCTextFieldLabelFoundation.setValidity(isValid: boolean)