Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

chore(text-field): Pass subelement foundations through MDCTextField super constructor #1684

Merged
merged 14 commits into from
Dec 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions packages/mdc-textfield/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ since it won't be added until that JS runs, adding it manually will prevent an i

### Validation

MDC TextField provides validity styling by using the `:invalid` and `:required` attributes provided
MDC Text Field provides validity styling by using the `:invalid` and `:required` attributes provided
by HTML5's form validation API.

```html
Expand All @@ -114,7 +114,7 @@ by HTML5's form validation API.
```

By default an input's validity is checked via `checkValidity()` on blur, and the styles are updated
accordingly. Set the MDCTextField.valid field to set the input's validity explicitly. MDC TextField
accordingly. Set the MDCTextField.valid field to set the input's validity explicitly. MDC Text Field
automatically appends an asterisk to the label text if the required attribute is set.

### Leading and Trailing Icons
Expand Down Expand Up @@ -175,8 +175,8 @@ do anything unexpected.
<div class="mdc-text-field mdc-text-field--fullwidth">
<input class="mdc-text-field__input"
type="text"
placeholder="Full-Width TextField"
aria-label="Full-Width TextField">
placeholder="Full-Width Text Field"
aria-label="Full-Width Text Field">
</div>

<div class="mdc-text-field mdc-text-field--fullwidth mdc-text-field--textarea">
Expand Down Expand Up @@ -227,7 +227,7 @@ This mixin customizes the border radius for a Text Field `textarea`.

### Using the JS component

MDC TextField ships with Component / Foundation classes which are used to provide a full-fidelity
MDC Text Field ships with Component / Foundation classes which are used to provide a full-fidelity
Material Design text field component.

#### Including in code
Expand Down Expand Up @@ -318,7 +318,7 @@ initializes when given an `mdc-text-field--box` root element. Otherwise, the fie

### Using the foundation class

Because MDC TextField is a feature-rich and relatively complex component, its adapter is a bit more
Because MDC Text Field is a feature-rich and relatively complex component, its adapter is a bit more
complicated.

| Method Signature | Description |
Expand All @@ -336,8 +336,8 @@ complicated.
| registerBottomLineEventHandler(evtType: string, handler: EventListener) => void | Registers an event listener on the bottom line element for a given event |
| deregisterBottomLineEventHandler(evtType: string, handler: EventListener) => void | Deregisters an event listener on the bottom line element for a given event |
| getNativeInput() => {value: string, disabled: boolean, badInput: boolean, checkValidity: () => boolean}? | Returns an object representing the native text input element, with a similar API shape. The object returned should include the `value`, `disabled` and `badInput` properties, as well as the `checkValidity()` function. We _never_ alter the value within our code, however we _do_ update the disabled property, so if you choose to duck-type the return value for this method in your implementation it's important to keep this in mind. Also note that this method can return null, which the foundation will handle gracefully. |
| getBottomLineFoundation() => MDCTextFieldBottomLineFoundation | Returns the instance of the bottom line element's foundation |
| getHelperTextFoundation() => MDCTextFieldHelperTextFoundation | Returns the instance of the helper text element's foundation |

MDC Text Field has multiple optional sub-elements: bottom line and helper text. The foundations of these sub-elements must be passed in as constructor arguments for the `MDCTextField` foundation. Since the `MDCTextField` component takes care of creating its foundation, we need to pass sub-element foundations through the `MDCTextField` component. This is typically done in the component's implementation of `getDefaultFoundation()`.

#### The full foundation API

Expand Down Expand Up @@ -376,7 +376,7 @@ Sets the content of the helper text, if it exists.

### Theming

MDC TextField components use the configured theme's primary color for its underline and label text
MDC Text Field components use the configured theme's primary color for its underline and label text
when the input is focused.

MDC TextField components support dark themes.
MDC Text Field components support dark themes.
24 changes: 9 additions & 15 deletions packages/mdc-textfield/adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ import MDCTextFieldHelperTextFoundation from './helper-text/foundation';
*/
let NativeInputType;

/**
* @typedef {{
* bottomLine: (!MDCTextFieldBottomLineFoundation|undefined),
* helperText: (!MDCTextFieldHelperTextFoundation|undefined)
* }}
*/
let FoundationMapType;

/**
* Adapter for MDC Text Field.
*
Expand Down Expand Up @@ -142,20 +150,6 @@ class MDCTextFieldAdapter {
* @return {?Element|?NativeInputType}
*/
getNativeInput() {}

/**
* Returns the foundation for the bottom line element. Returns undefined if
* there is no bottom line element.
* @return {?MDCTextFieldBottomLineFoundation}
*/
getBottomLineFoundation() {}

/**
* Returns the foundation for the helper text element. Returns undefined if
* there is no helper text element.
* @return {?MDCTextFieldHelperTextFoundation}
*/
getHelperTextFoundation() {}
}

export {MDCTextFieldAdapter, NativeInputType};
export {MDCTextFieldAdapter, NativeInputType, FoundationMapType};
2 changes: 1 addition & 1 deletion packages/mdc-textfield/bottom-line/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MDCTextFieldBottomLine extends MDCComponent {
}

/**
* @return {MDCTextFieldBottomLineFoundation}
* @return {!MDCTextFieldBottomLineFoundation}
*/
get foundation() {
return this.foundation_;
Expand Down
45 changes: 23 additions & 22 deletions packages/mdc-textfield/foundation.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
*/

import MDCFoundation from '@material/base/foundation';
import {MDCTextFieldAdapter, NativeInputType} from './adapter';
import {MDCTextFieldAdapter, NativeInputType, FoundationMapType} from './adapter';
import MDCTextFieldBottomLineFoundation from './bottom-line/foundation';
// eslint-disable-next-line no-unused-vars
import MDCTextFieldHelperTextFoundation from './helper-text/foundation';
import {cssClasses, strings} from './constants';


Expand Down Expand Up @@ -57,17 +59,22 @@ class MDCTextFieldFoundation extends MDCFoundation {
registerBottomLineEventHandler: () => {},
deregisterBottomLineEventHandler: () => {},
getNativeInput: () => {},
getBottomLineFoundation: () => {},
getHelperTextFoundation: () => {},
});
}

/**
* @param {!MDCTextFieldAdapter=} adapter
* @param {!FoundationMapType=} foundationMap Map from subcomponent names to their subfoundations.
*/
constructor(adapter = /** @type {!MDCTextFieldAdapter} */ ({})) {
constructor(adapter = /** @type {!MDCTextFieldAdapter} */ ({}),
foundationMap = /** @type {!FoundationMapType} */ ({})) {
super(Object.assign(MDCTextFieldFoundation.defaultAdapter, adapter));

/** @type {!MDCTextFieldBottomLineFoundation|undefined} */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this is the correct way to annotate an optional property/param. I talked to Brendan (our resident Closure Compiler expert) and crawled through the JS Style Guide and Closure Compiler JSDoc wiki, and they all seem to suggest that your style is the right one.

The rest of our code base uses a nullable type instead of undefined
(e.g., {?MDCTextFieldBottomLineFoundation}), but that's technically incorrect, and our internal Closure Compiler usually complains about it.

We should probably update the rest of our components to use your style of annotations at some point.

this.bottomLine_ = foundationMap.bottomLine;
/** @type {!MDCTextFieldHelperTextFoundation|undefined} */
this.helperText_ = foundationMap.helperText;

/** @private {boolean} */
this.isFocused_ = false;
/** @private {boolean} */
Expand Down Expand Up @@ -150,15 +157,13 @@ class MDCTextFieldFoundation extends MDCFoundation {
activateFocus() {
const {FOCUSED, LABEL_FLOAT_ABOVE, LABEL_SHAKE} = MDCTextFieldFoundation.cssClasses;
this.adapter_.addClass(FOCUSED);
const bottomLine = this.adapter_.getBottomLineFoundation();
if (bottomLine) {
bottomLine.activate();
if (this.bottomLine_) {
this.bottomLine_.activate();
}
this.adapter_.addClassToLabel(LABEL_FLOAT_ABOVE);
this.adapter_.removeClassFromLabel(LABEL_SHAKE);
const helperText = this.adapter_.getHelperTextFoundation();
if (helperText) {
helperText.showToScreenReader();
if (this.helperText_) {
this.helperText_.showToScreenReader();
}
this.isFocused_ = true;
}
Expand All @@ -169,9 +174,8 @@ class MDCTextFieldFoundation extends MDCFoundation {
* @param {!Event} evt
*/
setBottomLineTransformOrigin(evt) {
const bottomLine = this.adapter_.getBottomLineFoundation();
if (bottomLine) {
bottomLine.setTransformOrigin(evt);
if (this.bottomLine_) {
this.bottomLine_.setTransformOrigin(evt);
}
}

Expand All @@ -190,12 +194,11 @@ class MDCTextFieldFoundation extends MDCFoundation {
* for animations to finish.
*/
handleBottomLineAnimationEnd() {
const bottomLine = this.adapter_.getBottomLineFoundation();
// We need to wait for the bottom line to be entirely transparent
// before removing the class. If we do not, we see the line start to
// scale down before disappearing
if (!this.isFocused_ && bottomLine) {
bottomLine.deactivate();
if (!this.isFocused_ && this.bottomLine_) {
this.bottomLine_.deactivate();
}
}

Expand Down Expand Up @@ -233,9 +236,8 @@ class MDCTextFieldFoundation extends MDCFoundation {
this.adapter_.addClassToLabel(LABEL_SHAKE);
this.adapter_.addClass(INVALID);
}
const helperText = this.adapter_.getHelperTextFoundation();
if (helperText) {
helperText.setValidity(isValid);
if (this.helperText_) {
this.helperText_.setValidity(isValid);
}
}

Expand Down Expand Up @@ -275,9 +277,8 @@ class MDCTextFieldFoundation extends MDCFoundation {
* @param {string} content Sets the content of the helper text.
*/
setHelperTextContent(content) {
const helperText = this.adapter_.getHelperTextFoundation();
if (helperText) {
helperText.setContent(content);
if (this.helperText_) {
this.helperText_.setContent(content);
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mdc-textfield/helper-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MDCTextFieldHelperText extends MDCComponent {
}

/**
* @return {MDCTextFieldHelperTextFoundation}
* @return {!MDCTextFieldHelperTextFoundation}
*/
get foundation() {
return this.foundation_;
Expand Down
95 changes: 49 additions & 46 deletions packages/mdc-textfield/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import {getMatchesProperty} from '@material/ripple/util';


import {cssClasses, strings} from './constants';
import {MDCTextFieldAdapter} from './adapter';
import {MDCTextFieldAdapter, FoundationMapType} from './adapter';
import MDCTextFieldFoundation from './foundation';
import {MDCTextFieldBottomLine} from './bottom-line';
import {MDCTextFieldHelperText} from './helper-text';
/* eslint-disable no-unused-vars */
import {MDCTextFieldBottomLine, MDCTextFieldBottomLineFoundation} from './bottom-line';
import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text';
/* eslint-enable no-unused-vars */

/**
* @extends {MDCComponent<!MDCTextFieldFoundation>}
Expand Down Expand Up @@ -151,50 +153,40 @@ class MDCTextField extends MDCComponent {
* @return {!MDCTextFieldFoundation}
*/
getDefaultFoundation() {
return new MDCTextFieldFoundation(/** @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),
notifyIconAction: () => this.emit(MDCTextFieldFoundation.strings.ICON_EVENT, {}),
registerBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.listen(evtType, handler);
}
},
deregisterBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.unlisten(evtType, handler);
}
},
getBottomLineFoundation: () => {
if (this.bottomLine_) {
return this.bottomLine_.foundation;
}
return undefined;
return new MDCTextFieldFoundation(
/** @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),
notifyIconAction: () => this.emit(MDCTextFieldFoundation.strings.ICON_EVENT, {}),
registerBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.listen(evtType, handler);
}
},
deregisterBottomLineEventHandler: (evtType, handler) => {
if (this.bottomLine_) {
this.bottomLine_.unlisten(evtType, handler);
}
},
},
getHelperTextFoundation: () => {
if (this.helperText_) {
return this.helperText_.foundation;
}
return undefined;
},
},
this.getInputAdapterMethods_(),
this.getIconAdapterMethods_())));
this.getInputAdapterMethods_(),
this.getIconAdapterMethods_())),
this.getFoundationMap_());
}

/**
Expand Down Expand Up @@ -226,6 +218,17 @@ class MDCTextField extends MDCComponent {
getNativeInput: () => this.input_,
};
}

/**
* Returns a map of all subcomponents to subfoundations.
* @return {!FoundationMapType}
*/
getFoundationMap_() {
return {
bottomLine: this.bottomLine_ ? this.bottomLine_.foundation : undefined,
helperText: this.helperText_ ? this.helperText_.foundation : undefined,
};
}
}

export {MDCTextField, MDCTextFieldFoundation};
Loading