From f81a1dd03aa89b1cdc1bf142596fba151416cb01 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Thu, 7 Feb 2019 19:54:17 -0800 Subject: [PATCH 01/33] feat(text-field): Convert JS to TypeScript Refs #4225 --- .../mdc-textfield/{adapter.js => adapter.ts} | 97 +++------ .../{adapter.js => adapter.ts} | 22 +- .../{constants.js => constants.ts} | 2 - .../{foundation.js => foundation.ts} | 35 +-- .../character-counter/{index.js => index.ts} | 41 ++-- .../{constants.js => constants.ts} | 2 - .../{foundation.js => foundation.ts} | 199 ++++++++---------- .../helper-text/{adapter.js => adapter.ts} | 39 ++-- .../{constants.js => constants.ts} | 4 +- .../{foundation.js => foundation.ts} | 66 +++--- .../helper-text/{index.js => index.ts} | 40 ++-- .../icon/{adapter.js => adapter.ts} | 43 ++-- .../icon/{constants.js => constants.ts} | 1 - .../icon/{foundation.js => foundation.ts} | 67 +++--- .../mdc-textfield/icon/{index.js => index.ts} | 40 ++-- packages/mdc-textfield/{index.js => index.ts} | 183 +++++++--------- 16 files changed, 333 insertions(+), 548 deletions(-) rename packages/mdc-textfield/{adapter.js => adapter.ts} (62%) rename packages/mdc-textfield/character-counter/{adapter.js => adapter.ts} (71%) rename packages/mdc-textfield/character-counter/{constants.js => constants.ts} (96%) rename packages/mdc-textfield/character-counter/{foundation.js => foundation.ts} (67%) rename packages/mdc-textfield/character-counter/{index.js => index.ts} (59%) rename packages/mdc-textfield/{constants.js => constants.ts} (98%) rename packages/mdc-textfield/{foundation.js => foundation.ts} (73%) rename packages/mdc-textfield/helper-text/{adapter.js => adapter.ts} (67%) rename packages/mdc-textfield/helper-text/{constants.js => constants.ts} (97%) rename packages/mdc-textfield/helper-text/{foundation.js => foundation.ts} (68%) rename packages/mdc-textfield/helper-text/{index.js => index.ts} (67%) rename packages/mdc-textfield/icon/{adapter.js => adapter.ts} (65%) rename packages/mdc-textfield/icon/{constants.js => constants.ts} (98%) rename packages/mdc-textfield/icon/{foundation.js => foundation.ts} (65%) rename packages/mdc-textfield/icon/{index.js => index.ts} (71%) rename packages/mdc-textfield/{index.js => index.ts} (82%) diff --git a/packages/mdc-textfield/adapter.js b/packages/mdc-textfield/adapter.ts similarity index 62% rename from packages/mdc-textfield/adapter.js rename to packages/mdc-textfield/adapter.ts index 6d6102f8e1d..5f57ea89564 100644 --- a/packages/mdc-textfield/adapter.js +++ b/packages/mdc-textfield/adapter.ts @@ -21,13 +21,9 @@ * THE SOFTWARE. */ -/* eslint-disable no-unused-vars */ -import MDCTextFieldHelperTextFoundation from './helper-text/foundation'; -/* eslint-disable no-unused-vars */ -import MDCTextFieldCharacterCounterFoundation from './character-counter/foundation'; -import MDCTextFieldIconFoundation from './icon/foundation'; - -/* eslint no-unused-vars: [2, {"args": "none"}] */ +import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; +import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; +import {MDCTextFieldIconFoundation} from './icon/foundation'; /** * @typedef {{ @@ -53,76 +49,58 @@ let NativeInputType; let FoundationMapType; /** - * Adapter for MDC Text Field. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the Text Field into your framework. See - * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md - * for more information. - * - * @record + * Defines the shape of the adapter expected by the foundation. + * Implement this adapter for your framework of choice to delegate updates to + * the component in your framework of choice. See architecture documentation + * for more details. + * https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md */ -class MDCTextFieldAdapter { +interface MDCTextFieldAdapter { /** * Adds a class to the root Element. - * @param {string} className */ - addClass(className) {} + addClass(className: string): void; /** * Removes a class from the root Element. - * @param {string} className */ - removeClass(className) {} + removeClass(className: string): void; /** * Returns true if the root element contains the given class name. - * @param {string} className - * @return {boolean} */ - hasClass(className) {} + hasClass(className: string): boolean; /** * Registers an event handler on the root element for a given event. - * @param {string} type - * @param {function(!Event): undefined} handler */ - registerTextFieldInteractionHandler(type, handler) {} + registerTextFieldInteractionHandler(type: string, handler: EventListener): void; /** * Deregisters an event handler on the root element for a given event. - * @param {string} type - * @param {function(!Event): undefined} handler */ - deregisterTextFieldInteractionHandler(type, handler) {} + deregisterTextFieldInteractionHandler(type: string, handler: EventListener): void; /** * Registers an event listener on the native input element for a given event. - * @param {string} evtType - * @param {function(!Event): undefined} handler */ - registerInputInteractionHandler(evtType, handler) {} + registerInputInteractionHandler(evtType: string, handler: EventListener): void; /** * Deregisters an event listener on the native input element for a given event. - * @param {string} evtType - * @param {function(!Event): undefined} handler */ - deregisterInputInteractionHandler(evtType, handler) {} + deregisterInputInteractionHandler(evtType: string, handler: EventListener): void; /** * Registers a validation attribute change listener on the input element. * Handler accepts list of attribute names. - * @param {function(!Array): undefined} handler - * @return {!MutationObserver} */ - registerValidationAttributeChangeHandler(handler) {} + registerValidationAttributeChangeHandler(handler: (attributeNames: string[]) => void): MutationObserver; /** * Disconnects a validation attribute observer on the input element. - * @param {!MutationObserver} observer */ - deregisterValidationAttributeChangeHandler(observer) {} + deregisterValidationAttributeChangeHandler(observer: MutationObserver): void; /** * Returns an object representing the native text input element, with a @@ -132,77 +110,68 @@ class MDCTextFieldAdapter { * 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. - * @return {?Element|?NativeInputType} */ - getNativeInput() {} + getNativeInput(): Element | NativeInputType | null; /** * Returns true if the textfield is focused. * We achieve this via `document.activeElement === this.root_`. - * @return {boolean} */ - isFocused() {} + isFocused(): boolean; /** * Activates the line ripple. */ - activateLineRipple() {} + activateLineRipple(); /** * Deactivates the line ripple. */ - deactivateLineRipple() {} + deactivateLineRipple(); /** * Sets the transform origin of the line ripple. - * @param {number} normalizedX */ - setLineRippleTransformOrigin(normalizedX) {} + setLineRippleTransformOrigin(normalizedX: number): void; /** * Only implement if label exists. - * Shakes label if shouldShake is true. - * @param {boolean} shouldShake +Shakes label if shouldShake is true. */ - shakeLabel(shouldShake) {} + shakeLabel(shouldShake: boolean): void; /** * Only implement if label exists. - * Floats the label above the input element if shouldFloat is true. - * @param {boolean} shouldFloat +Floats the label above the input element if shouldFloat is true. */ - floatLabel(shouldFloat) {} + floatLabel(shouldFloat: boolean): void; /** * Returns true if label element exists, false if it doesn't. - * @return {boolean} */ - hasLabel() {} + hasLabel(): boolean; /** * Only implement if label exists. - * Returns width of label in pixels. - * @return {number} +Returns width of label in pixels. */ - getLabelWidth() {} + getLabelWidth(): number; /** * Returns true if outline element exists, false if it doesn't. - * @return {boolean} */ - hasOutline() {} + hasOutline(): boolean; /** * Only implement if outline element exists. - * @param {number} labelWidth */ - notchOutline(labelWidth) {} + notchOutline(labelWidth: number): void; /** * Only implement if outline element exists. * Closes notch in outline element. */ - closeOutline() {} + closeOutline(); } export {MDCTextFieldAdapter, NativeInputType, FoundationMapType}; diff --git a/packages/mdc-textfield/character-counter/adapter.js b/packages/mdc-textfield/character-counter/adapter.ts similarity index 71% rename from packages/mdc-textfield/character-counter/adapter.js rename to packages/mdc-textfield/character-counter/adapter.ts index 50326252124..74351c64cd8 100644 --- a/packages/mdc-textfield/character-counter/adapter.js +++ b/packages/mdc-textfield/character-counter/adapter.ts @@ -21,24 +21,18 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - /** - * Adapter for MDC Text Field Character Counter. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the TextField character counter into your framework. See - * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md - * for more information. - * - * @record + * Defines the shape of the adapter expected by the foundation. + * Implement this adapter for your framework of choice to delegate updates to + * the component in your framework of choice. See architecture documentation + * for more details. + * https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md */ -class MDCTextFieldCharacterCounterAdapter { +interface MDCTextFieldCharacterCounterAdapter { /** * Sets the text content of character counter element. - * @param {string} content */ - setContent(content) {} + setContent(content: string): void; } -export default MDCTextFieldCharacterCounterAdapter; +export {MDCTextFieldCharacterCounterAdapter as default, MDCTextFieldCharacterCounterAdapter}; diff --git a/packages/mdc-textfield/character-counter/constants.js b/packages/mdc-textfield/character-counter/constants.ts similarity index 96% rename from packages/mdc-textfield/character-counter/constants.js rename to packages/mdc-textfield/character-counter/constants.ts index 60d64e11d61..994fbbf5343 100644 --- a/packages/mdc-textfield/character-counter/constants.js +++ b/packages/mdc-textfield/character-counter/constants.ts @@ -21,12 +21,10 @@ * THE SOFTWARE. */ -/** @enum {string} */ const cssClasses = { ROOT: 'mdc-text-field-character-counter', }; -/** @enum {string} */ const strings = { ROOT_SELECTOR: `.${cssClasses.ROOT}`, }; diff --git a/packages/mdc-textfield/character-counter/foundation.js b/packages/mdc-textfield/character-counter/foundation.ts similarity index 67% rename from packages/mdc-textfield/character-counter/foundation.js rename to packages/mdc-textfield/character-counter/foundation.ts index 6b810d579af..1d551996502 100644 --- a/packages/mdc-textfield/character-counter/foundation.js +++ b/packages/mdc-textfield/character-counter/foundation.ts @@ -22,50 +22,35 @@ */ import {MDCFoundation} from '@material/base/foundation'; -import MDCTextFieldCharacterCounterAdapter from './adapter'; +import {MDCTextFieldCharacterCounterAdapter} from './adapter'; import {cssClasses, strings} from './constants'; -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTextFieldCharacterCounterFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTextFieldCharacterCounterFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } - /** @return enum {string} */ static get strings() { return strings; } /** - * {@see MDCTextFieldCharacterCounterAdapter} for typing information on parameters and return - * types. - * @return {!MDCTextFieldCharacterCounterAdapter} + * See {@link MDCTextFieldCharacterCounterAdapter} for typing information on parameters and return types. */ - static get defaultAdapter() { - return /** @type {!MDCTextFieldCharacterCounterAdapter} */ ({ - setContent: () => {}, - }); + static get defaultAdapter(): MDCTextFieldCharacterCounterAdapter { + return { + setContent: () => undefined, + }; } - /** - * @param {!MDCTextFieldCharacterCounterAdapter} adapter - */ - constructor(adapter) { + constructor(adapter: MDCTextFieldCharacterCounterAdapter) { super(Object.assign(MDCTextFieldCharacterCounterFoundation.defaultAdapter, adapter)); } - /** - * @param {number} currentLength - * @param {number} maxLength - */ - setCounterValue(currentLength, maxLength) { + setCounterValue(currentLength: number, maxLength: number): void { currentLength = Math.min(currentLength, maxLength); this.adapter_.setContent(`${currentLength} / ${maxLength}`); } } -export default MDCTextFieldCharacterCounterFoundation; +export {MDCTextFieldCharacterCounterFoundation as default, MDCTextFieldCharacterCounterFoundation}; diff --git a/packages/mdc-textfield/character-counter/index.js b/packages/mdc-textfield/character-counter/index.ts similarity index 59% rename from packages/mdc-textfield/character-counter/index.js rename to packages/mdc-textfield/character-counter/index.ts index 0195905032f..80b277869b5 100644 --- a/packages/mdc-textfield/character-counter/index.js +++ b/packages/mdc-textfield/character-counter/index.ts @@ -22,41 +22,26 @@ */ import {MDCComponent} from '@material/base/component'; +import {MDCTextFieldCharacterCounterFoundation} from './foundation'; -import MDCTextFieldCharacterCounterAdapter from './adapter'; -import MDCTextFieldCharacterCounterFoundation from './foundation'; - -/** - * @extends {MDCComponent} - * @final - */ -class MDCTextFieldCharacterCounter extends MDCComponent { - /** - * @param {!Element} root - * @return {!MDCTextFieldCharacterCounter} - */ - static attachTo(root) { +class MDCTextFieldCharacterCounter extends MDCComponent { + static attachTo(root: Element): MDCTextFieldCharacterCounter { return new MDCTextFieldCharacterCounter(root); } - /** - * @return {!MDCTextFieldCharacterCounterFoundation} - */ - get foundation() { + get foundation(): MDCTextFieldCharacterCounterFoundation { return this.foundation_; } - /** - * @return {!MDCTextFieldCharacterCounterFoundation} - */ - getDefaultFoundation() { - return new MDCTextFieldCharacterCounterFoundation( - /** @type {!MDCTextFieldCharacterCounterAdapter} */ (Object.assign({ - setContent: (content) => { - this.root_.textContent = content; - }, - }))); + getDefaultFoundation(): MDCTextFieldCharacterCounterFoundation { + return new MDCTextFieldCharacterCounterFoundation({ + setContent: (content) => { + this.root_.textContent = content; + }, + }); } } -export {MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation}; +export {MDCTextFieldCharacterCounter as default, MDCTextFieldCharacterCounter}; +export * from './adapter'; +export * from './foundation'; diff --git a/packages/mdc-textfield/constants.js b/packages/mdc-textfield/constants.ts similarity index 98% rename from packages/mdc-textfield/constants.js rename to packages/mdc-textfield/constants.ts index f26c0922394..0cf55442f43 100644 --- a/packages/mdc-textfield/constants.js +++ b/packages/mdc-textfield/constants.ts @@ -21,7 +21,6 @@ * THE SOFTWARE. */ -/** @enum {string} */ const strings = { ARIA_CONTROLS: 'aria-controls', INPUT_SELECTOR: '.mdc-text-field__input', @@ -31,7 +30,6 @@ const strings = { LINE_RIPPLE_SELECTOR: '.mdc-line-ripple', }; -/** @enum {string} */ const cssClasses = { ROOT: 'mdc-text-field', DISABLED: 'mdc-text-field--disabled', diff --git a/packages/mdc-textfield/foundation.js b/packages/mdc-textfield/foundation.ts similarity index 73% rename from packages/mdc-textfield/foundation.js rename to packages/mdc-textfield/foundation.ts index 185993ccdfe..c28c1f7325e 100644 --- a/packages/mdc-textfield/foundation.js +++ b/packages/mdc-textfield/foundation.ts @@ -23,29 +23,22 @@ import {MDCFoundation} from '@material/base/foundation'; /* eslint-disable no-unused-vars */ -import MDCTextFieldHelperTextFoundation from './helper-text/foundation'; -import MDCTextFieldCharacterCounterFoundation from './character-counter/foundation'; -import MDCTextFieldIconFoundation from './icon/foundation'; +import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; +import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; +import {MDCTextFieldIconFoundation} from './icon/foundation'; /* eslint-enable no-unused-vars */ import {MDCTextFieldAdapter, NativeInputType, FoundationMapType} from './adapter'; import {cssClasses, strings, numbers, VALIDATION_ATTR_WHITELIST, ALWAYS_FLOAT_TYPES} from './constants'; -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTextFieldFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTextFieldFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } - /** @return enum {string} */ static get strings() { return strings; } - /** @return enum {string} */ static get numbers() { return numbers; } @@ -56,10 +49,9 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @return {boolean} * @private */ - get shouldAlwaysFloat_() { + get shouldAlwaysFloat_(): boolean { const type = this.getNativeInput_().type; return ALWAYS_FLOAT_TYPES.indexOf(type) >= 0; } @@ -70,42 +62,41 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * {@see MDCTextFieldAdapter} for typing information on parameters and return - * types. - * @return {!MDCTextFieldAdapter} + * See {@link MDCTextFieldAdapter} for typing information on parameters and return +types. */ - static get defaultAdapter() { - return /** @type {!MDCTextFieldAdapter} */ ({ - addClass: () => {}, - removeClass: () => {}, - hasClass: () => {}, - registerTextFieldInteractionHandler: () => {}, - deregisterTextFieldInteractionHandler: () => {}, - registerInputInteractionHandler: () => {}, - deregisterInputInteractionHandler: () => {}, - registerValidationAttributeChangeHandler: () => {}, - deregisterValidationAttributeChangeHandler: () => {}, - getNativeInput: () => {}, - isFocused: () => {}, - activateLineRipple: () => {}, - deactivateLineRipple: () => {}, - setLineRippleTransformOrigin: () => {}, - shakeLabel: () => {}, - floatLabel: () => {}, - hasLabel: () => {}, - getLabelWidth: () => {}, - hasOutline: () => {}, - notchOutline: () => {}, - closeOutline: () => {}, - }); + static get defaultAdapter(): MDCTextFieldAdapter { + return { + addClass: () => undefined, + removeClass: () => undefined, + hasClass: () => undefined, + registerTextFieldInteractionHandler: () => undefined, + deregisterTextFieldInteractionHandler: () => undefined, + registerInputInteractionHandler: () => undefined, + deregisterInputInteractionHandler: () => undefined, + registerValidationAttributeChangeHandler: () => undefined, + deregisterValidationAttributeChangeHandler: () => undefined, + getNativeInput: () => undefined, + isFocused: () => undefined, + activateLineRipple: () => undefined, + deactivateLineRipple: () => undefined, + setLineRippleTransformOrigin: () => undefined, + shakeLabel: () => undefined, + floatLabel: () => undefined, + hasLabel: () => undefined, + getLabelWidth: () => undefined, + hasOutline: () => undefined, + notchOutline: () => undefined, + closeOutline: () => undefined, + }; } /** * @param {!MDCTextFieldAdapter} adapter * @param {!FoundationMapType=} foundationMap Map from subcomponent names to their subfoundations. */ - constructor(adapter, foundationMap = /** @type {!FoundationMapType} */ ({})) { - super(Object.assign(MDCTextFieldFoundation.defaultAdapter, adapter)); + constructor(adapter, foundationMap = {})) { + super(Object.assign(MDCTextFieldFoundation.defaultAdapter, adapter); /** @type {!MDCTextFieldHelperTextFoundation|undefined} */ this.helperText_ = foundationMap.helperText; @@ -116,29 +107,29 @@ class MDCTextFieldFoundation extends MDCFoundation { /** @type {!MDCTextFieldIconFoundation|undefined} */ this.trailingIcon_ = foundationMap.trailingIcon; - /** @private {boolean} */ + private isFocused_: boolean; this.isFocused_ = false; - /** @private {boolean} */ + private receivedUserInput_: boolean; this.receivedUserInput_ = false; - /** @private {boolean} */ + private useCustomValidityChecking_: boolean; this.useCustomValidityChecking_ = false; - /** @private {boolean} */ + private isValid_: boolean; this.isValid_ = true; - /** @private {boolean} */ + private useNativeValidation_: boolean; this.useNativeValidation_ = true; - /** @private {function(): undefined} */ + private inputFocusHandler_: function(): undefined; this.inputFocusHandler_ = () => this.activateFocus(); - /** @private {function(): undefined} */ + private inputBlurHandler_: function(): undefined; this.inputBlurHandler_ = () => this.deactivateFocus(); - /** @private {function(): undefined} */ + private inputInputHandler_: function(): undefined; this.inputInputHandler_ = () => this.handleInput(); - /** @private {function(!Event): undefined} */ + private setPointerXOffset_: EventListener; this.setPointerXOffset_ = (evt) => this.setTransformOrigin(evt); - /** @private {function(!Event): undefined} */ + private textFieldInteractionHandler_: EventListener; this.textFieldInteractionHandler_ = () => this.handleTextFieldInteraction(); - /** @private {function(!Array): undefined} */ + private validationAttributeChangeHandler_: function(!Array): undefined; this.validationAttributeChangeHandler_ = (attributesList) => this.handleValidationAttributeChange(attributesList); /** @private {!MutationObserver} */ @@ -192,9 +183,8 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Handles validation attribute changes - * @param {!Array} attributesList */ - handleValidationAttributeChange(attributesList) { + handleValidationAttributeChange(attributesList: Array): void { attributesList.some((attributeName) => { if (VALIDATION_ATTR_WHITELIST.indexOf(attributeName) > -1) { this.styleValidity_(true); @@ -245,10 +235,9 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Sets the line ripple's transform origin, so that the line ripple activate - * animation will animate out from the user's click location. - * @param {!Event} evt +animation will animate out from the user's click location. */ - setTransformOrigin(evt) { + setTransformOrigin(evt: Event): void { let targetEvent; if (evt.touches) { targetEvent = evt.touches[0]; @@ -297,17 +286,14 @@ class MDCTextFieldFoundation extends MDCFoundation { } } - /** - * @return {string} The value of the input Element. - */ - getValue() { + getValue(): string { return this.getNativeInput_().value; } /** - * @param {string} value The value to set on the input Element. + * @param value The value to set on the input Element. */ - setValue(value) { + setValue(value: string): void { // Prevent Safari from moving the caret to the end of the input when the value has not changed. if (this.getValue() !== value) { this.getNativeInput_().value = value; @@ -322,18 +308,17 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @return {boolean} If a custom validity is set, returns that value. - * Otherwise, returns the result of native validity checks. + * Otherwise, returns the result of native validity checks. */ - isValid() { + isValid(): boolean { return this.useNativeValidation_ ? this.isNativeInputValid_() : this.isValid_; } /** - * @param {boolean} isValid Sets the validity state of the Text Field. + * @param isValid Sets the validity state of the Text Field. */ - setValid(isValid) { + setValid(isValid: boolean): void { this.isValid_ = isValid; this.styleValidity_(isValid); @@ -345,31 +330,28 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * 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. + * @param useNativeValidation Set this to false to ignore native input validation. */ - setUseNativeValidation(useNativeValidation) { + setUseNativeValidation(useNativeValidation: boolean): void { this.useNativeValidation_ = useNativeValidation; } - /** - * @return {boolean} True if the Text Field is disabled. - */ - isDisabled() { + isDisabled(): boolean { return this.getNativeInput_().disabled; } /** - * @param {boolean} disabled Sets the text-field disabled or enabled. + * @param disabled Sets the text-field disabled or enabled. */ - setDisabled(disabled) { + setDisabled(disabled: boolean): void { this.getNativeInput_().disabled = disabled; this.styleDisabled_(disabled); } /** - * @param {string} content Sets the content of the helper text. + * @param content Sets the content of the helper text. */ - setHelperTextContent(content) { + setHelperTextContent(content: string): void { if (this.helperText_) { this.helperText_.setContent(content); } @@ -377,10 +359,10 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Sets character counter values that shows characters used and the total character limit. - * @param {number} currentLength - * @private + +@private */ - setCharacterCounter_(currentLength) { + setCharacterCounter_(currentLength: number): void { if (!this.characterCounter_) return; const maxLength = this.getNativeInput_().maxLength; @@ -393,9 +375,8 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Sets the aria label of the leading icon. - * @param {string} label */ - setLeadingIconAriaLabel(label) { + setLeadingIconAriaLabel(label: string): void { if (this.leadingIcon_) { this.leadingIcon_.setAriaLabel(label); } @@ -403,9 +384,8 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Sets the text content of the leading icon. - * @param {string} content */ - setLeadingIconContent(content) { + setLeadingIconContent(content: string): void { if (this.leadingIcon_) { this.leadingIcon_.setContent(content); } @@ -413,9 +393,8 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Sets the aria label of the trailing icon. - * @param {string} label */ - setTrailingIconAriaLabel(label) { + setTrailingIconAriaLabel(label: string): void { if (this.trailingIcon_) { this.trailingIcon_.setAriaLabel(label); } @@ -423,37 +402,34 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Sets the text content of the trailing icon. - * @param {string} content */ - setTrailingIconContent(content) { + setTrailingIconContent(content: string): void { if (this.trailingIcon_) { this.trailingIcon_.setContent(content); } } /** - * @return {boolean} True if the Text Field input fails in converting the - * user-supplied value. - * @private + * user-supplied value. +@private */ - isBadInput_() { + isBadInput_(): boolean { return this.getNativeInput_().validity.badInput; } /** - * @return {boolean} The result of native validity checking - * (ValidityState.valid). + * (ValidityState.valid). */ - isNativeInputValid_() { + isNativeInputValid_(): boolean { return this.getNativeInput_().validity.valid; } /** * Styles the component based on the validity state. - * @param {boolean} isValid - * @private + +@private */ - styleValidity_(isValid) { + styleValidity_(isValid: boolean): void { const {INVALID} = MDCTextFieldFoundation.cssClasses; if (isValid) { this.adapter_.removeClass(INVALID); @@ -467,10 +443,10 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Styles the component based on the focused state. - * @param {boolean} isFocused - * @private + +@private */ - styleFocused_(isFocused) { + styleFocused_(isFocused: boolean): void { const {FOCUSED} = MDCTextFieldFoundation.cssClasses; if (isFocused) { this.adapter_.addClass(FOCUSED); @@ -481,10 +457,10 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Styles the component based on the disabled state. - * @param {boolean} isDisabled - * @private + +@private */ - styleDisabled_(isDisabled) { + styleDisabled_(isDisabled: boolean): void { const {DISABLED, INVALID} = MDCTextFieldFoundation.cssClasses; if (isDisabled) { this.adapter_.addClass(DISABLED); @@ -503,21 +479,20 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @return {!Element|!NativeInputType} The native text input from the * host environment, or a dummy if none exists. - * @private +@private */ - getNativeInput_() { + getNativeInput_(): Element|!NativeInputType { return this.adapter_.getNativeInput() || - /** @type {!NativeInputType} */ ({ + { value: '', disabled: false, validity: { badInput: false, valid: true, }, - }); + }; } } -export default MDCTextFieldFoundation; +export {MDCTextFieldFoundation as default, MDCTextFieldFoundation}; diff --git a/packages/mdc-textfield/helper-text/adapter.js b/packages/mdc-textfield/helper-text/adapter.ts similarity index 67% rename from packages/mdc-textfield/helper-text/adapter.js rename to packages/mdc-textfield/helper-text/adapter.ts index a6cf4768833..bdc90c6c5d5 100644 --- a/packages/mdc-textfield/helper-text/adapter.js +++ b/packages/mdc-textfield/helper-text/adapter.ts @@ -21,56 +21,43 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - /** - * Adapter for MDC Text Field Helper Text. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the TextField helper text into your framework. See - * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md - * for more information. - * - * @record + * Defines the shape of the adapter expected by the foundation. + * Implement this adapter for your framework of choice to delegate updates to + * the component in your framework of choice. See architecture documentation + * for more details. + * https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md */ -class MDCTextFieldHelperTextAdapter { +interface MDCTextFieldHelperTextAdapter { /** * Adds a class to the helper text element. - * @param {string} className */ - addClass(className) {} + addClass(className: string): void; /** * Removes a class from the helper text element. - * @param {string} className */ - removeClass(className) {} + removeClass(className: string): void; /** * Returns whether or not the helper text element contains the given class. - * @param {string} className - * @return {boolean} */ - hasClass(className) {} + hasClass(className: string): boolean; /** * Sets an attribute with a given value on the helper text element. - * @param {string} attr - * @param {string} value */ - setAttr(attr, value) {} + setAttr(attr: string, value: string): void; /** * Removes an attribute from the helper text element. - * @param {string} attr */ - removeAttr(attr) {} + removeAttr(attr: string): void; /** * Sets the text content for the helper text element. - * @param {string} content */ - setContent(content) {} + setContent(content: string): void; } -export default MDCTextFieldHelperTextAdapter; +export {MDCTextFieldHelperTextAdapter as default, MDCTextFieldHelperTextAdapter}; diff --git a/packages/mdc-textfield/helper-text/constants.js b/packages/mdc-textfield/helper-text/constants.ts similarity index 97% rename from packages/mdc-textfield/helper-text/constants.js rename to packages/mdc-textfield/helper-text/constants.ts index ef091170cf8..8cf4ff39f05 100644 --- a/packages/mdc-textfield/helper-text/constants.js +++ b/packages/mdc-textfield/helper-text/constants.ts @@ -21,14 +21,12 @@ * THE SOFTWARE. */ -/** @enum {string} */ const cssClasses = { - ROOT: 'mdc-text-field-helper-text', HELPER_TEXT_PERSISTENT: 'mdc-text-field-helper-text--persistent', HELPER_TEXT_VALIDATION_MSG: 'mdc-text-field-helper-text--validation-msg', + ROOT: 'mdc-text-field-helper-text', }; -/** @enum {string} */ const strings = { ARIA_HIDDEN: 'aria-hidden', ROLE: 'role', diff --git a/packages/mdc-textfield/helper-text/foundation.js b/packages/mdc-textfield/helper-text/foundation.ts similarity index 68% rename from packages/mdc-textfield/helper-text/foundation.js rename to packages/mdc-textfield/helper-text/foundation.ts index 8f8860232d8..02ae4e54ce4 100644 --- a/packages/mdc-textfield/helper-text/foundation.js +++ b/packages/mdc-textfield/helper-text/foundation.ts @@ -22,58 +22,49 @@ */ import {MDCFoundation} from '@material/base/foundation'; -import MDCTextFieldHelperTextAdapter from './adapter'; +import {MDCTextFieldHelperTextAdapter} from './adapter'; import {cssClasses, strings} from './constants'; - -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTextFieldHelperTextFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTextFieldHelperTextFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } - /** @return enum {string} */ static get strings() { return strings; } /** - * {@see MDCTextFieldHelperTextAdapter} for typing information on parameters and return - * types. - * @return {!MDCTextFieldHelperTextAdapter} + * See {@link MDCTextFieldHelperTextAdapter} for typing information on parameters and return types. */ - static get defaultAdapter() { - return /** @type {!MDCTextFieldHelperTextAdapter} */ ({ - addClass: () => {}, - removeClass: () => {}, - hasClass: () => {}, - setAttr: () => {}, - removeAttr: () => {}, - setContent: () => {}, - }); + static get defaultAdapter(): MDCTextFieldHelperTextAdapter { + // tslint:disable:object-literal-sort-keys + return { + addClass: () => undefined, + removeClass: () => undefined, + hasClass: () => false, + setAttr: () => undefined, + removeAttr: () => undefined, + setContent: () => undefined, + }; + // tslint:enable:object-literal-sort-keys } - /** - * @param {!MDCTextFieldHelperTextAdapter} adapter - */ - constructor(adapter) { + constructor(adapter: MDCTextFieldHelperTextAdapter) { super(Object.assign(MDCTextFieldHelperTextFoundation.defaultAdapter, adapter)); } /** * Sets the content of the helper text field. - * @param {string} content */ - setContent(content) { + setContent(content: string): void { this.adapter_.setContent(content); } - /** @param {boolean} isPersistent Sets the persistency of the helper text. */ - setPersistent(isPersistent) { + /** + * @param isPersistent Sets the persistency of the helper text. + */ + setPersistent(isPersistent: boolean) { if (isPersistent) { this.adapter_.addClass(cssClasses.HELPER_TEXT_PERSISTENT); } else { @@ -82,10 +73,9 @@ class MDCTextFieldHelperTextFoundation extends MDCFoundation { } /** - * @param {boolean} isValidation True to make the helper text act as an - * error validation message. + * @param isValidation True to make the helper text act as an error validation message. */ - setValidation(isValidation) { + setValidation(isValidation: boolean): void { if (isValidation) { this.adapter_.addClass(cssClasses.HELPER_TEXT_VALIDATION_MSG); } else { @@ -93,16 +83,17 @@ class MDCTextFieldHelperTextFoundation extends MDCFoundation { } } - /** Makes the helper text visible to the screen reader. */ + /** + * Makes the helper text visible to the screen reader. + */ showToScreenReader() { this.adapter_.removeAttr(strings.ARIA_HIDDEN); } /** * Sets the validity of the helper text based on the input validity. - * @param {boolean} inputIsValid */ - setValidity(inputIsValid) { + setValidity(inputIsValid: boolean): void { const helperTextIsPersistent = this.adapter_.hasClass(cssClasses.HELPER_TEXT_PERSISTENT); const helperTextIsValidationMsg = this.adapter_.hasClass(cssClasses.HELPER_TEXT_VALIDATION_MSG); const validationMsgNeedsDisplay = helperTextIsValidationMsg && !inputIsValid; @@ -120,11 +111,10 @@ class MDCTextFieldHelperTextFoundation extends MDCFoundation { /** * Hides the help text from screen readers. - * @private */ - hide_() { + private hide_() { this.adapter_.setAttr(strings.ARIA_HIDDEN, 'true'); } } -export default MDCTextFieldHelperTextFoundation; +export {MDCTextFieldHelperTextFoundation as default, MDCTextFieldHelperTextFoundation}; diff --git a/packages/mdc-textfield/helper-text/index.js b/packages/mdc-textfield/helper-text/index.ts similarity index 67% rename from packages/mdc-textfield/helper-text/index.js rename to packages/mdc-textfield/helper-text/index.ts index 7d59dfe190c..be43b63a917 100644 --- a/packages/mdc-textfield/helper-text/index.js +++ b/packages/mdc-textfield/helper-text/index.ts @@ -22,45 +22,31 @@ */ import {MDCComponent} from '@material/base/component'; +import {MDCTextFieldHelperTextFoundation} from './foundation'; -import MDCTextFieldHelperTextAdapter from './adapter'; -import MDCTextFieldHelperTextFoundation from './foundation'; - -/** - * @extends {MDCComponent} - * @final - */ -class MDCTextFieldHelperText extends MDCComponent { - /** - * @param {!Element} root - * @return {!MDCTextFieldHelperText} - */ - static attachTo(root) { +class MDCTextFieldHelperText extends MDCComponent { + static attachTo(root: Element): MDCTextFieldHelperText { return new MDCTextFieldHelperText(root); } - /** - * @return {!MDCTextFieldHelperTextFoundation} - */ - get foundation() { + get foundation(): MDCTextFieldHelperTextFoundation { return this.foundation_; } - /** - * @return {!MDCTextFieldHelperTextFoundation} - */ - getDefaultFoundation() { - return new MDCTextFieldHelperTextFoundation(/** @type {!MDCTextFieldHelperTextAdapter} */ (Object.assign({ + getDefaultFoundation(): MDCTextFieldHelperTextFoundation { + // tslint:disable:object-literal-sort-keys + return new MDCTextFieldHelperTextFoundation({ addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), hasClass: (className) => this.root_.classList.contains(className), setAttr: (attr, value) => this.root_.setAttribute(attr, value), removeAttr: (attr) => this.root_.removeAttribute(attr), - setContent: (content) => { - this.root_.textContent = content; - }, - }))); + setContent: (content) => this.root_.textContent = content, + }); + // tslint:enable:object-literal-sort-keys } } -export {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation}; +export {MDCTextFieldHelperText as default, MDCTextFieldHelperText}; +export * from './adapter'; +export * from './foundation'; diff --git a/packages/mdc-textfield/icon/adapter.js b/packages/mdc-textfield/icon/adapter.ts similarity index 65% rename from packages/mdc-textfield/icon/adapter.js rename to packages/mdc-textfield/icon/adapter.ts index 82e705a9cf4..7f83f22d888 100644 --- a/packages/mdc-textfield/icon/adapter.js +++ b/packages/mdc-textfield/icon/adapter.ts @@ -21,63 +21,48 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - /** - * Adapter for MDC Text Field Icon. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the text field icon into your framework. See - * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md - * for more information. - * - * @record + * Defines the shape of the adapter expected by the foundation. + * Implement this adapter for your framework of choice to delegate updates to + * the component in your framework of choice. See architecture documentation + * for more details. + * https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md */ -class MDCTextFieldIconAdapter { +interface MDCTextFieldIconAdapter { /** * Gets the value of an attribute on the icon element. - * @param {string} attr - * @return {string} */ - getAttr(attr) {} + getAttr(attr: string): string | null; /** * Sets an attribute on the icon element. - * @param {string} attr - * @param {string} value */ - setAttr(attr, value) {} + setAttr(attr: string, value: string): void; /** * Removes an attribute from the icon element. - * @param {string} attr */ - removeAttr(attr) {} + removeAttr(attr: string): void; /** * Sets the text content of the icon element. - * @param {string} content */ - setContent(content) {} + setContent(content: string): void; /** * Registers an event listener on the icon element for a given event. - * @param {string} evtType - * @param {function(!Event): undefined} handler */ - registerInteractionHandler(evtType, handler) {} + registerInteractionHandler(evtType: string, handler: EventListener): void; /** * Deregisters an event listener on the icon element for a given event. - * @param {string} evtType - * @param {function(!Event): undefined} handler */ - deregisterInteractionHandler(evtType, handler) {} + deregisterInteractionHandler(evtType: string, handler: EventListener): void; /** * Emits a custom event "MDCTextField:icon" denoting a user has clicked the icon. */ - notifyIconAction() {} + notifyIconAction(): void; } -export default MDCTextFieldIconAdapter; +export {MDCTextFieldIconAdapter as default, MDCTextFieldIconAdapter}; diff --git a/packages/mdc-textfield/icon/constants.js b/packages/mdc-textfield/icon/constants.ts similarity index 98% rename from packages/mdc-textfield/icon/constants.js rename to packages/mdc-textfield/icon/constants.ts index e29697c6193..81712f35b57 100644 --- a/packages/mdc-textfield/icon/constants.js +++ b/packages/mdc-textfield/icon/constants.ts @@ -21,7 +21,6 @@ * THE SOFTWARE. */ -/** @enum {string} */ const strings = { ICON_EVENT: 'MDCTextField:icon', ICON_ROLE: 'button', diff --git a/packages/mdc-textfield/icon/foundation.js b/packages/mdc-textfield/icon/foundation.ts similarity index 65% rename from packages/mdc-textfield/icon/foundation.js rename to packages/mdc-textfield/icon/foundation.ts index 5dd28c66aee..dfca43e95ea 100644 --- a/packages/mdc-textfield/icon/foundation.js +++ b/packages/mdc-textfield/icon/foundation.ts @@ -22,47 +22,38 @@ */ import {MDCFoundation} from '@material/base/foundation'; -import MDCTextFieldIconAdapter from './adapter'; +import {MDCTextFieldIconAdapter} from './adapter'; import {strings} from './constants'; - -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTextFieldIconFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTextFieldIconFoundation extends MDCFoundation { static get strings() { return strings; } /** - * {@see MDCTextFieldIconAdapter} for typing information on parameters and return - * types. - * @return {!MDCTextFieldIconAdapter} + * See {@link MDCTextFieldIconAdapter} for typing information on parameters and return types. */ - static get defaultAdapter() { - return /** @type {!MDCTextFieldIconAdapter} */ ({ - getAttr: () => {}, - setAttr: () => {}, - removeAttr: () => {}, - setContent: () => {}, - registerInteractionHandler: () => {}, - deregisterInteractionHandler: () => {}, - notifyIconAction: () => {}, - }); + static get defaultAdapter(): MDCTextFieldIconAdapter { + // tslint:disable:object-literal-sort-keys + return { + getAttr: () => null, + setAttr: () => undefined, + removeAttr: () => undefined, + setContent: () => undefined, + registerInteractionHandler: () => undefined, + deregisterInteractionHandler: () => undefined, + notifyIconAction: () => undefined, + }; + // tslint:enable:object-literal-sort-keys } - /** - * @param {!MDCTextFieldIconAdapter} adapter - */ - constructor(adapter) { + private savedTabIndex_: string | null; + private readonly interactionHandler_: EventListener; + + constructor(adapter: MDCTextFieldIconAdapter) { super(Object.assign(MDCTextFieldIconFoundation.defaultAdapter, adapter)); - /** @private {string?} */ this.savedTabIndex_ = null; - - /** @private {function(!Event): undefined} */ this.interactionHandler_ = (evt) => this.handleInteraction(evt); } @@ -80,8 +71,7 @@ class MDCTextFieldIconFoundation extends MDCFoundation { }); } - /** @param {boolean} disabled */ - setDisabled(disabled) { + setDisabled(disabled: boolean) { if (!this.savedTabIndex_) { return; } @@ -95,25 +85,20 @@ class MDCTextFieldIconFoundation extends MDCFoundation { } } - /** @param {string} label */ - setAriaLabel(label) { + setAriaLabel(label: string) { this.adapter_.setAttr('aria-label', label); } - /** @param {string} content */ - setContent(content) { + setContent(content: string) { this.adapter_.setContent(content); } - /** - * Handles an interaction event - * @param {!Event} evt - */ - handleInteraction(evt) { - if (evt.type === 'click' || evt.key === 'Enter' || evt.keyCode === 13) { + handleInteraction(evt: Event): void { + const isEnterKey = (evt as KeyboardEvent).key === 'Enter' || (evt as KeyboardEvent).keyCode === 13; + if (evt.type === 'click' || isEnterKey) { this.adapter_.notifyIconAction(); } } } -export default MDCTextFieldIconFoundation; +export {MDCTextFieldIconFoundation as default, MDCTextFieldIconFoundation}; diff --git a/packages/mdc-textfield/icon/index.js b/packages/mdc-textfield/icon/index.ts similarity index 71% rename from packages/mdc-textfield/icon/index.js rename to packages/mdc-textfield/icon/index.ts index 08c0d9c94bf..514d296aac9 100644 --- a/packages/mdc-textfield/icon/index.js +++ b/packages/mdc-textfield/icon/index.ts @@ -22,47 +22,33 @@ */ import {MDCComponent} from '@material/base/component'; +import {MDCTextFieldIconFoundation} from './foundation'; -import MDCTextFieldIconAdapter from './adapter'; -import MDCTextFieldIconFoundation from './foundation'; - -/** - * @extends {MDCComponent} - * @final - */ -class MDCTextFieldIcon extends MDCComponent { - /** - * @param {!Element} root - * @return {!MDCTextFieldIcon} - */ - static attachTo(root) { +class MDCTextFieldIcon extends MDCComponent { + static attachTo(root: Element): MDCTextFieldIcon { return new MDCTextFieldIcon(root); } - /** - * @return {!MDCTextFieldIconFoundation} - */ - get foundation() { + get foundation(): MDCTextFieldIconFoundation { return this.foundation_; } - /** - * @return {!MDCTextFieldIconFoundation} - */ - getDefaultFoundation() { - return new MDCTextFieldIconFoundation(/** @type {!MDCTextFieldIconAdapter} */ (Object.assign({ + getDefaultFoundation(): MDCTextFieldIconFoundation { + // tslint:disable:object-literal-sort-keys + return new MDCTextFieldIconFoundation({ getAttr: (attr) => this.root_.getAttribute(attr), setAttr: (attr, value) => this.root_.setAttribute(attr, value), removeAttr: (attr) => this.root_.removeAttribute(attr), - setContent: (content) => { - this.root_.textContent = content; - }, + setContent: (content) => this.root_.textContent = content, registerInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler), deregisterInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler), notifyIconAction: () => this.emit( MDCTextFieldIconFoundation.strings.ICON_EVENT, {} /* evtData */, true /* shouldBubble */), - }))); + }); + // tslint:enable:object-literal-sort-keys } } -export {MDCTextFieldIcon, MDCTextFieldIconFoundation}; +export {MDCTextFieldIcon as default, MDCTextFieldIcon}; +export * from './adapter'; +export * from './foundation'; diff --git a/packages/mdc-textfield/index.js b/packages/mdc-textfield/index.ts similarity index 82% rename from packages/mdc-textfield/index.js rename to packages/mdc-textfield/index.ts index a47cf2d16c2..b6e4dfc8b79 100644 --- a/packages/mdc-textfield/index.js +++ b/packages/mdc-textfield/index.ts @@ -30,7 +30,7 @@ import {getMatchesProperty} from '@material/ripple/util'; import {cssClasses, strings} from './constants'; import {MDCTextFieldAdapter, FoundationMapType} from './adapter'; -import MDCTextFieldFoundation from './foundation'; +import {MDCTextFieldFoundation} from './foundation'; /* eslint-disable no-unused-vars */ import {MDCLineRipple, MDCLineRippleFoundation} from '@material/line-ripple/index'; import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text/index'; @@ -40,11 +40,7 @@ import {MDCFloatingLabel, MDCFloatingLabelFoundation} from '@material/floating-l import {MDCNotchedOutline, MDCNotchedOutlineFoundation} from '@material/notched-outline/index'; /* eslint-enable no-unused-vars */ -/** - * @extends {MDCComponent} - * @final - */ -class MDCTextField extends MDCComponent { +class MDCTextField extends MDCComponent { /** * @param {...?} args */ @@ -70,11 +66,7 @@ class MDCTextField extends MDCComponent { this.outline_; } - /** - * @param {!Element} root - * @return {!MDCTextField} - */ - static attachTo(root) { + static attachTo(root: Element): MDCTextField { return new MDCTextField(root); } @@ -155,11 +147,11 @@ class MDCTextField extends MDCComponent { if (!this.root_.classList.contains(cssClasses.TEXTAREA) && !this.root_.classList.contains(cssClasses.OUTLINED)) { const MATCHES = getMatchesProperty(HTMLElement.prototype); const adapter = - Object.assign(MDCRipple.createAdapter(/** @type {!RippleCapableSurface} */ (this)), { + Object.assign(MDCRipple.createAdapter(this)), { isSurfaceActive: () => this.input_[MATCHES](':active'), registerInteractionHandler: (type, handler) => this.input_.addEventListener(type, handler), deregisterInteractionHandler: (type, handler) => this.input_.removeEventListener(type, handler), - }); + }; const foundation = new MDCRippleFoundation(adapter); this.ripple = rippleFactory(this.root_, foundation); } @@ -201,101 +193,80 @@ class MDCTextField extends MDCComponent { this.disabled = this.input_.disabled; } - /** - * @return {string} The value of the input. - */ - get value() { + get value(): string { return this.foundation_.getValue(); } /** - * @param {string} value The value to set on the input. + * @param value The value to set on the input. */ - set value(value) { + set value(value: string): void { this.foundation_.setValue(value); } - /** - * @return {boolean} True if the Text Field is disabled. - */ - get disabled() { + get disabled(): boolean { return this.foundation_.isDisabled(); } /** - * @param {boolean} disabled Sets the Text Field disabled or enabled. + * @param disabled Sets the Text Field disabled or enabled. */ - set disabled(disabled) { + set disabled(disabled: boolean): void { this.foundation_.setDisabled(disabled); } - /** - * @return {boolean} valid True if the Text Field is valid. - */ - get valid() { + get valid(): boolean { return this.foundation_.isValid(); } /** - * @param {boolean} valid Sets the Text Field valid or invalid. + * @param valid Sets the Text Field valid or invalid. */ - set valid(valid) { + set valid(valid: boolean): void { this.foundation_.setValid(valid); } - /** - * @return {boolean} True if the Text Field is required. - */ - get required() { + get required(): boolean { return this.input_.required; } /** - * @param {boolean} required Sets the Text Field to required. + * @param required Sets the Text Field to required. */ - set required(required) { + set required(required: boolean): void { this.input_.required = required; } - /** - * @return {string} The input element's validation pattern. - */ - get pattern() { + get pattern(): string { return this.input_.pattern; } /** - * @param {string} pattern Sets the input element's validation pattern. + * @param pattern Sets the input element's validation pattern. */ - set pattern(pattern) { + set pattern(pattern: string): void { this.input_.pattern = pattern; } - /** - * @return {number} The input element's minLength. - */ - get minLength() { + get minLength(): number { return this.input_.minLength; } /** - * @param {number} minLength Sets the input element's minLength. + * @param minLength Sets the input element's minLength. */ - set minLength(minLength) { + set minLength(minLength: number): void { this.input_.minLength = minLength; } - /** - * @return {number} The input element's maxLength. - */ - get maxLength() { + get maxLength(): number { return this.input_.maxLength; } /** - * @param {number} maxLength Sets the input element's maxLength. + * @param maxLength Sets the input element's maxLength. */ - set maxLength(maxLength) { + set maxLength(maxLength: number): void { // Chrome throws exception if maxLength is set < 0 if (maxLength < 0) { this.input_.removeAttribute('maxLength'); @@ -304,93 +275,79 @@ class MDCTextField extends MDCComponent { } } - /** - * @return {string} The input element's min. - */ - get min() { + get min(): string { return this.input_.min; } /** - * @param {string} min Sets the input element's min. + * @param min Sets the input element's min. */ - set min(min) { + set min(min: string): void { this.input_.min = min; } - /** - * @return {string} The input element's max. - */ - get max() { + get max(): string { return this.input_.max; } /** - * @param {string} max Sets the input element's max. + * @param max Sets the input element's max. */ - set max(max) { + set max(max: string): void { this.input_.max = max; } - /** - * @return {string} The input element's step. - */ - get step() { + get step(): string { return this.input_.step; } /** - * @param {string} step Sets the input element's step. + * @param step Sets the input element's step. */ - set step(step) { + set step(step: string): void { this.input_.step = step; } /** * Sets the helper text element content. - * @param {string} content */ - set helperTextContent(content) { + set helperTextContent(content: string): void { this.foundation_.setHelperTextContent(content); } /** * Sets the aria label of the leading icon. - * @param {string} label */ - set leadingIconAriaLabel(label) { + set leadingIconAriaLabel(label: string): void { this.foundation_.setLeadingIconAriaLabel(label); } /** * Sets the text content of the leading icon. - * @param {string} content */ - set leadingIconContent(content) { + set leadingIconContent(content: string): void { this.foundation_.setLeadingIconContent(content); } /** * Sets the aria label of the trailing icon. - * @param {string} label */ - set trailingIconAriaLabel(label) { + set trailingIconAriaLabel(label: string): void { this.foundation_.setTrailingIconAriaLabel(label); } /** * Sets the text content of the trailing icon. - * @param {string} content */ - set trailingIconContent(content) { + set trailingIconContent(content: string): void { this.foundation_.setTrailingIconContent(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. + * @param useNativeValidation Set this to false to ignore native input validation. */ - set useNativeValidation(useNativeValidation) { + set useNativeValidation(useNativeValidation: boolean): void { this.foundation_.setUseNativeValidation(useNativeValidation); } @@ -409,19 +366,16 @@ class MDCTextField extends MDCComponent { this.foundation_.notchOutline(openNotch); } - /** - * @return {!MDCTextFieldFoundation} - */ - getDefaultFoundation() { + getDefaultFoundation(): MDCTextFieldFoundation { return new MDCTextFieldFoundation( - /** @type {!MDCTextFieldAdapter} */ (Object.assign({ + Object.assign({ addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), hasClass: (className) => this.root_.classList.contains(className), registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler), deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler), registerValidationAttributeChangeHandler: (handler) => { - const getAttributesList = (mutationsList) => mutationsList.map((mutation) => mutation.attributeName); + const getAttributesList = (mutationsList) => mutationsList.map((mutation) => mutation.attributeName; const observer = new MutationObserver((mutationsList) => handler(getAttributesList(mutationsList))); const targetNode = this.root_.querySelector(strings.INPUT_SELECTOR); const config = {attributes: true}; @@ -441,14 +395,18 @@ class MDCTextField extends MDCComponent { } /** - * @return {!{ + * shakeLabel: function(boolean): undefined, + floatLabel: function(boolean): undefined, + hasLabel: function(): boolean, + getLabelWidth: function(): number, +}} + */ + getLabelAdapterMethods_(): { * shakeLabel: function(boolean): undefined, * floatLabel: function(boolean): undefined, * hasLabel: function(): boolean, * getLabelWidth: function(): number, - * }} - */ - getLabelAdapterMethods_() { + * { return { shakeLabel: (shouldShake) => this.label_.shake(shouldShake), floatLabel: (shouldFloat) => this.label_.float(shouldFloat), @@ -458,13 +416,16 @@ class MDCTextField extends MDCComponent { } /** - * @return {!{ + * activateLineRipple: function(): undefined, + deactivateLineRipple: function(): undefined, + setLineRippleTransformOrigin: function(number): undefined, +}} + */ + getLineRippleAdapterMethods_(): { * activateLineRipple: function(): undefined, * deactivateLineRipple: function(): undefined, * setLineRippleTransformOrigin: function(number): undefined, - * }} - */ - getLineRippleAdapterMethods_() { + * { return { activateLineRipple: () => { if (this.lineRipple_) { @@ -485,12 +446,14 @@ class MDCTextField extends MDCComponent { } /** - * @return {!{ + * notchOutline: function(number, boolean): undefined, + hasOutline: function(): boolean, +}} + */ + getOutlineAdapterMethods_(): { * notchOutline: function(number, boolean): undefined, * hasOutline: function(): boolean, - * }} - */ - getOutlineAdapterMethods_() { + * { return { notchOutline: (labelWidth) => this.outline_.notch(labelWidth), closeOutline: () => this.outline_.closeNotch(), @@ -499,13 +462,16 @@ class MDCTextField extends MDCComponent { } /** - * @return {!{ + * registerInputInteractionHandler: function(string, function()): undefined, + deregisterInputInteractionHandler: function(string, function()): undefined, + getNativeInput: function(): ?Element, +}} + */ + getInputAdapterMethods_(): { * registerInputInteractionHandler: function(string, function()): undefined, * deregisterInputInteractionHandler: function(string, function()): undefined, * getNativeInput: function(): ?Element, - * }} - */ - getInputAdapterMethods_() { + * { return { registerInputInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler), deregisterInputInteractionHandler: (evtType, handler) => this.input_.removeEventListener(evtType, handler), @@ -515,9 +481,8 @@ class MDCTextField extends MDCComponent { /** * Returns a map of all subcomponents to subfoundations. - * @return {!FoundationMapType} */ - getFoundationMap_() { + getFoundationMap_(): FoundationMapType { return { helperText: this.helperText_ ? this.helperText_.foundation : undefined, characterCounter: this.characterCounter_ ? this.characterCounter_.foundation : undefined, From 63985d2293f3bff030c1cb00f264955ef03466ea Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Thu, 7 Feb 2019 20:35:08 -0800 Subject: [PATCH 02/33] WIP: Iterate --- packages/mdc-textfield/adapter.ts | 66 +++------- packages/mdc-textfield/foundation.ts | 176 ++++++++++++--------------- packages/mdc-textfield/types.ts | 44 +++++++ 3 files changed, 139 insertions(+), 147 deletions(-) create mode 100644 packages/mdc-textfield/types.ts diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index 5f57ea89564..06d322f10cc 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -21,32 +21,7 @@ * THE SOFTWARE. */ -import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; -import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; -import {MDCTextFieldIconFoundation} from './icon/foundation'; - -/** - * @typedef {{ - * value: string, - * disabled: boolean, - * badInput: boolean, - * validity: { - * badInput: boolean, - * valid: boolean, - * }, - * }} - */ -let NativeInputType; - -/** - * @typedef {{ - * helperText: (!MDCTextFieldHelperTextFoundation|undefined), - * characterCounter: (!MDCTextFieldCharacterCounterFoundation|undefined), - * leadingIcon: (!MDCTextFieldIconFoundation|undefined), - * trailingIcon: (!MDCTextFieldIconFoundation|undefined), - * }} - */ -let FoundationMapType; +import {NativeInputType} from './types'; /** * Defines the shape of the adapter expected by the foundation. @@ -67,7 +42,7 @@ interface MDCTextFieldAdapter { removeClass(className: string): void; /** - * Returns true if the root element contains the given class name. + * @return true if the root element contains the given class name. */ hasClass(className: string): boolean; @@ -103,31 +78,30 @@ interface MDCTextFieldAdapter { deregisterValidationAttributeChangeHandler(observer: MutationObserver): void; /** - * 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. + * @return 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. */ - getNativeInput(): Element | NativeInputType | null; + getNativeInput(): HTMLInputElement | NativeInputType | null; /** - * Returns true if the textfield is focused. - * We achieve this via `document.activeElement === this.root_`. + * @return true if the textfield is focused. We achieve this via `document.activeElement === this.root_`. */ isFocused(): boolean; /** * Activates the line ripple. */ - activateLineRipple(); + activateLineRipple(): void; /** * Deactivates the line ripple. */ - deactivateLineRipple(); + deactivateLineRipple(): void; /** * Sets the transform origin of the line ripple. @@ -136,29 +110,29 @@ interface MDCTextFieldAdapter { /** * Only implement if label exists. -Shakes label if shouldShake is true. + * Shakes label if shouldShake is true. */ shakeLabel(shouldShake: boolean): void; /** * Only implement if label exists. -Floats the label above the input element if shouldFloat is true. + * Floats the label above the input element if shouldFloat is true. */ floatLabel(shouldFloat: boolean): void; /** - * Returns true if label element exists, false if it doesn't. + * @return true if label element exists, false if it doesn't. */ hasLabel(): boolean; /** * Only implement if label exists. -Returns width of label in pixels. + * @return width of label in pixels. */ getLabelWidth(): number; /** - * Returns true if outline element exists, false if it doesn't. + * @return true if outline element exists, false if it doesn't. */ hasOutline(): boolean; @@ -171,7 +145,7 @@ Returns width of label in pixels. * Only implement if outline element exists. * Closes notch in outline element. */ - closeOutline(); + closeOutline(): void; } -export {MDCTextFieldAdapter, NativeInputType, FoundationMapType}; +export {MDCTextFieldAdapter as default, MDCTextFieldAdapter}; diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index c28c1f7325e..070b452eadd 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -22,13 +22,12 @@ */ import {MDCFoundation} from '@material/base/foundation'; -/* eslint-disable no-unused-vars */ -import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; -import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; -import {MDCTextFieldIconFoundation} from './icon/foundation'; -/* eslint-enable no-unused-vars */ -import {MDCTextFieldAdapter, NativeInputType, FoundationMapType} from './adapter'; -import {cssClasses, strings, numbers, VALIDATION_ATTR_WHITELIST, ALWAYS_FLOAT_TYPES} from './constants'; +import {MDCTextFieldAdapter} from './adapter'; +import {ALWAYS_FLOAT_TYPES, cssClasses, numbers, strings, VALIDATION_ATTR_WHITELIST} from './constants'; +import {FoundationMapType, NativeInputType} from './types'; +import {MDCTextFieldHelperTextFoundation} from './helper-text'; +import {MDCTextFieldCharacterCounterFoundation} from './character-counter'; +import {MDCTextFieldIconFoundation} from './icon'; class MDCTextFieldFoundation extends MDCFoundation { static get cssClasses() { @@ -43,97 +42,86 @@ class MDCTextFieldFoundation extends MDCFoundation { return numbers; } - /** @return {boolean} */ - get shouldShake() { + get shouldShake(): boolean { return !this.isValid() && !this.isFocused_ && !!this.getValue(); } - /** - * @private - */ - get shouldAlwaysFloat_(): boolean { + private get shouldAlwaysFloat_(): boolean { const type = this.getNativeInput_().type; return ALWAYS_FLOAT_TYPES.indexOf(type) >= 0; } - /** @return {boolean} */ - get shouldFloat() { + get shouldFloat(): boolean { return this.shouldAlwaysFloat_ || this.isFocused_ || !!this.getValue() || this.isBadInput_(); } /** - * See {@link MDCTextFieldAdapter} for typing information on parameters and return -types. + * See {@link MDCTextFieldAdapter} for typing information on parameters and return types. */ static get defaultAdapter(): MDCTextFieldAdapter { + // tslint:disable:object-literal-sort-keys return { addClass: () => undefined, removeClass: () => undefined, - hasClass: () => undefined, + hasClass: () => true, registerTextFieldInteractionHandler: () => undefined, deregisterTextFieldInteractionHandler: () => undefined, registerInputInteractionHandler: () => undefined, deregisterInputInteractionHandler: () => undefined, - registerValidationAttributeChangeHandler: () => undefined, + registerValidationAttributeChangeHandler: () => new MutationObserver(() => undefined), deregisterValidationAttributeChangeHandler: () => undefined, - getNativeInput: () => undefined, - isFocused: () => undefined, + getNativeInput: () => null, + isFocused: () => false, activateLineRipple: () => undefined, deactivateLineRipple: () => undefined, setLineRippleTransformOrigin: () => undefined, shakeLabel: () => undefined, floatLabel: () => undefined, - hasLabel: () => undefined, - getLabelWidth: () => undefined, - hasOutline: () => undefined, + hasLabel: () => false, + getLabelWidth: () => 0, + hasOutline: () => false, notchOutline: () => undefined, closeOutline: () => undefined, }; + // tslint:enable:object-literal-sort-keys } + private isFocused_ = false; + private receivedUserInput_ = false; + private isValid_ = true; + private useNativeValidation_ = true; + + private readonly inputFocusHandler_: () => void; + private readonly inputBlurHandler_: EventListener; + private readonly inputInputHandler_: EventListener; + private readonly setPointerXOffset_: EventListener; + private readonly textFieldInteractionHandler_: EventListener; + private readonly validationAttributeChangeHandler_: (attributesList: string[]) => void; + private validationObserver_!: MutationObserver; // assigned in init() + + private readonly helperText_: MDCTextFieldHelperTextFoundation | undefined; + private readonly characterCounter_: MDCTextFieldCharacterCounterFoundation | undefined; + private readonly leadingIcon_: MDCTextFieldIconFoundation | undefined; + private readonly trailingIcon_: MDCTextFieldIconFoundation | undefined; + /** - * @param {!MDCTextFieldAdapter} adapter - * @param {!FoundationMapType=} foundationMap Map from subcomponent names to their subfoundations. + * @param adapter + * @param foundationMap Map from subcomponent names to their subfoundations. */ - constructor(adapter, foundationMap = {})) { - super(Object.assign(MDCTextFieldFoundation.defaultAdapter, adapter); + constructor(adapter: MDCTextFieldAdapter, foundationMap: Partial = {}) { + super({...MDCTextFieldFoundation.defaultAdapter, ...adapter}); - /** @type {!MDCTextFieldHelperTextFoundation|undefined} */ this.helperText_ = foundationMap.helperText; - /** @type {!MDCTextFieldCharacterCounterFoundation|undefined} */ this.characterCounter_ = foundationMap.characterCounter; - /** @type {!MDCTextFieldIconFoundation|undefined} */ this.leadingIcon_ = foundationMap.leadingIcon; - /** @type {!MDCTextFieldIconFoundation|undefined} */ this.trailingIcon_ = foundationMap.trailingIcon; - private isFocused_: boolean; - this.isFocused_ = false; - private receivedUserInput_: boolean; - this.receivedUserInput_ = false; - private useCustomValidityChecking_: boolean; - this.useCustomValidityChecking_ = false; - private isValid_: boolean; - this.isValid_ = true; - - private useNativeValidation_: boolean; - this.useNativeValidation_ = true; - - private inputFocusHandler_: function(): undefined; this.inputFocusHandler_ = () => this.activateFocus(); - private inputBlurHandler_: function(): undefined; this.inputBlurHandler_ = () => this.deactivateFocus(); - private inputInputHandler_: function(): undefined; this.inputInputHandler_ = () => this.handleInput(); - private setPointerXOffset_: EventListener; this.setPointerXOffset_ = (evt) => this.setTransformOrigin(evt); - private textFieldInteractionHandler_: EventListener; this.textFieldInteractionHandler_ = () => this.handleTextFieldInteraction(); - private validationAttributeChangeHandler_: function(!Array): undefined; this.validationAttributeChangeHandler_ = (attributesList) => this.handleValidationAttributeChange(attributesList); - - /** @private {!MutationObserver} */ - this.validationObserver_; } init() { @@ -175,7 +163,8 @@ types. * Handles user interactions with the Text Field. */ handleTextFieldInteraction() { - if (this.adapter_.getNativeInput().disabled) { + const nativeInput = this.adapter_.getNativeInput(); + if (nativeInput && nativeInput.disabled) { return; } this.receivedUserInput_ = true; @@ -184,12 +173,13 @@ types. /** * Handles validation attribute changes */ - handleValidationAttributeChange(attributesList: Array): void { + handleValidationAttributeChange(attributesList: string[]): void { attributesList.some((attributeName) => { if (VALIDATION_ATTR_WHITELIST.indexOf(attributeName) > -1) { this.styleValidity_(true); return true; } + return false; }); if (attributesList.indexOf('maxlength') > -1) { @@ -199,9 +189,8 @@ types. /** * Opens/closes the notched outline. - * @param {boolean} openNotch */ - notchOutline(openNotch) { + notchOutline(openNotch: boolean) { if (!this.adapter_.hasOutline()) { return; } @@ -235,17 +224,13 @@ types. /** * Sets the line ripple's transform origin, so that the line ripple activate -animation will animate out from the user's click location. + * animation will animate out from the user's click location. */ setTransformOrigin(evt: Event): void { - let targetEvent; - if (evt.touches) { - targetEvent = evt.touches[0]; - } else { - targetEvent = evt; - } - const targetClientRect = targetEvent.target.getBoundingClientRect(); - const normalizedX = targetEvent.clientX - targetClientRect.left; + const touches = (evt as TouchEvent).touches; + const targetEvent = touches ? touches[0] : evt; + const targetClientRect = (targetEvent.target as Element).getBoundingClientRect(); + const normalizedX = (targetEvent as MouseEvent).clientX - targetClientRect.left; this.adapter_.setLineRippleTransformOrigin(normalizedX); } @@ -357,22 +342,6 @@ animation will animate out from the user's click location. } } - /** - * Sets character counter values that shows characters used and the total character limit. - -@private - */ - setCharacterCounter_(currentLength: number): void { - if (!this.characterCounter_) return; - - const maxLength = this.getNativeInput_().maxLength; - if (maxLength === -1) { - throw new Error('MDCTextFieldFoundation: Expected maxlength html property on text input or textarea.'); - } - - this.characterCounter_.setCounterValue(currentLength, maxLength); - } - /** * Sets the aria label of the leading icon. */ @@ -410,26 +379,37 @@ animation will animate out from the user's click location. } /** - * user-supplied value. -@private + * Sets character counter values that shows characters used and the total character limit. */ - isBadInput_(): boolean { + private setCharacterCounter_(currentLength: number): void { + if (!this.characterCounter_) return; + + const maxLength = this.getNativeInput_().maxLength; + if (maxLength === -1) { + throw new Error('MDCTextFieldFoundation: Expected maxlength html property on text input or textarea.'); + } + + this.characterCounter_.setCounterValue(currentLength, maxLength); + } + + /** + * user-supplied value + */ + private isBadInput_(): boolean { return this.getNativeInput_().validity.badInput; } /** * (ValidityState.valid). */ - isNativeInputValid_(): boolean { + private isNativeInputValid_(): boolean { return this.getNativeInput_().validity.valid; } /** * Styles the component based on the validity state. - -@private */ - styleValidity_(isValid: boolean): void { + private styleValidity_(isValid: boolean): void { const {INVALID} = MDCTextFieldFoundation.cssClasses; if (isValid) { this.adapter_.removeClass(INVALID); @@ -443,10 +423,8 @@ animation will animate out from the user's click location. /** * Styles the component based on the focused state. - -@private */ - styleFocused_(isFocused: boolean): void { + private styleFocused_(isFocused: boolean): void { const {FOCUSED} = MDCTextFieldFoundation.cssClasses; if (isFocused) { this.adapter_.addClass(FOCUSED); @@ -457,10 +435,8 @@ animation will animate out from the user's click location. /** * Styles the component based on the disabled state. - -@private */ - styleDisabled_(isDisabled: boolean): void { + private styleDisabled_(isDisabled: boolean): void { const {DISABLED, INVALID} = MDCTextFieldFoundation.cssClasses; if (isDisabled) { this.adapter_.addClass(DISABLED); @@ -479,18 +455,16 @@ animation will animate out from the user's click location. } /** - * host environment, or a dummy if none exists. -@private + * host environment, or a dummy if none exists */ - getNativeInput_(): Element|!NativeInputType { - return this.adapter_.getNativeInput() || - { - value: '', + private getNativeInput_(): HTMLInputElement | NativeInputType { + return this.adapter_.getNativeInput() || { disabled: false, validity: { badInput: false, valid: true, }, + value: '', }; } } diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts new file mode 100644 index 00000000000..adf91c339ae --- /dev/null +++ b/packages/mdc-textfield/types.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright 2019 Google Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; +import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; +import {MDCTextFieldIconFoundation} from './icon/foundation'; + +export interface NativeInputType { + badInput?: boolean; + disabled: boolean; + maxLength?: number; + validity: { + badInput: boolean; + valid: boolean; + }; + value: string; +} + +export interface FoundationMapType { + helperText: MDCTextFieldHelperTextFoundation; + characterCounter: MDCTextFieldCharacterCounterFoundation; + leadingIcon: MDCTextFieldIconFoundation; + trailingIcon: MDCTextFieldIconFoundation; +} From 7e06a000843f80203cb154447e7780ca41a9235d Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Thu, 7 Feb 2019 20:48:15 -0800 Subject: [PATCH 03/33] WIP: Iterate --- packages/mdc-textfield/README.md | 6 +++--- packages/mdc-textfield/adapter.ts | 6 ++---- packages/mdc-textfield/constants.ts | 25 ++++++++++++++----------- packages/mdc-textfield/foundation.ts | 8 +++++--- packages/mdc-textfield/types.ts | 6 +++--- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/mdc-textfield/README.md b/packages/mdc-textfield/README.md index 81983048228..e4b7180ea62 100644 --- a/packages/mdc-textfield/README.md +++ b/packages/mdc-textfield/README.md @@ -374,9 +374,9 @@ Method Signature | Description `deregisterTextFieldInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event handler on the root element for a given event. `registerInputInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event listener on the native input element for a given event. `deregisterInputInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the native input element for a given event. -`registerValidationAttributeChangeHandler(handler: function(!Array) => undefined) => !MutationObserver` | Registers a validation attribute change listener on the input element. Handler accepts list of attribute changes. +`registerValidationAttributeChangeHandler(handler: (attributeNames: string[]) => void) => MutationObserver` | Registers a validation attribute change listener on the input element. Handler accepts list of attribute changes. `deregisterValidationAttributeChangeHandler(!MutationObserver) => void` | Disconnects a validation attribute observer on the input element. -`getNativeInput() => {value: string, disabled: boolean, badInput: boolean, checkValidity: () => boolean}?` | Returns an object representing the native text input element, with a similar API shape. +`getNativeInput() => HTMLInputElement \| NativeInputType` | Returns an object representing the native text input element, with a similar API shape. `isFocused() => boolean` | Returns whether the input is focused. `hasOutline() => boolean` | Returns whether there is an outline element. `notchOutline(labelWidth: number) => void` | Updates the notched outline path to open the notch and update the notch width for the label element. @@ -384,7 +384,7 @@ Method Signature | Description #### `MDCTextFieldAdapter.getNativeInput()` -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. +Returns an object representing the native text input element, with a similar API shape. 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. #### `MDCTextFieldAdapter.getIdleOutlineStyleValue(propertyName: string)` diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index 06d322f10cc..ecb614d6806 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -78,10 +78,8 @@ interface MDCTextFieldAdapter { deregisterValidationAttributeChangeHandler(observer: MutationObserver): void; /** - * @return 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 + * @return An object representing the native text input element, with a similar API shape. + * 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. diff --git a/packages/mdc-textfield/constants.ts b/packages/mdc-textfield/constants.ts index 0cf55442f43..9ba0a9495b3 100644 --- a/packages/mdc-textfield/constants.ts +++ b/packages/mdc-textfield/constants.ts @@ -23,38 +23,41 @@ const strings = { ARIA_CONTROLS: 'aria-controls', + ICON_SELECTOR: '.mdc-text-field__icon', INPUT_SELECTOR: '.mdc-text-field__input', LABEL_SELECTOR: '.mdc-floating-label', - ICON_SELECTOR: '.mdc-text-field__icon', - OUTLINE_SELECTOR: '.mdc-notched-outline', LINE_RIPPLE_SELECTOR: '.mdc-line-ripple', + OUTLINE_SELECTOR: '.mdc-notched-outline', }; const cssClasses = { - ROOT: 'mdc-text-field', - DISABLED: 'mdc-text-field--disabled', DENSE: 'mdc-text-field--dense', + DISABLED: 'mdc-text-field--disabled', FOCUSED: 'mdc-text-field--focused', + HELPER_LINE: 'mdc-text-field-helper-line', INVALID: 'mdc-text-field--invalid', - TEXTAREA: 'mdc-text-field--textarea', OUTLINED: 'mdc-text-field--outlined', + ROOT: 'mdc-text-field', + TEXTAREA: 'mdc-text-field--textarea', WITH_LEADING_ICON: 'mdc-text-field--with-leading-icon', - HELPER_LINE: 'mdc-text-field-helper-line', }; -/** @enum {number} */ const numbers = { - LABEL_SCALE: 0.75, DENSE_LABEL_SCALE: 0.923, + LABEL_SCALE: 0.75, }; -// whitelist based off of https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation -// under section: `Validation-related attributes` +/** + * Whitelist based off of https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation + * under the "Validation-related attributes" section. + */ const VALIDATION_ATTR_WHITELIST = [ 'pattern', 'min', 'max', 'required', 'step', 'minlength', 'maxlength', ]; -// Label should always float for these types as they show some UI even if value is empty. +/** + * Label should always float for these types as they show some UI even if value is empty. + */ const ALWAYS_FLOAT_TYPES = [ 'color', 'date', 'datetime-local', 'month', 'range', 'time', 'week', ]; diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 070b452eadd..54336c20d7c 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -23,11 +23,11 @@ import {MDCFoundation} from '@material/base/foundation'; import {MDCTextFieldAdapter} from './adapter'; +import {MDCTextFieldCharacterCounterFoundation} from './character-counter'; import {ALWAYS_FLOAT_TYPES, cssClasses, numbers, strings, VALIDATION_ATTR_WHITELIST} from './constants'; -import {FoundationMapType, NativeInputType} from './types'; import {MDCTextFieldHelperTextFoundation} from './helper-text'; -import {MDCTextFieldCharacterCounterFoundation} from './character-counter'; import {MDCTextFieldIconFoundation} from './icon'; +import {FoundationMapType, NativeInputType} from './types'; class MDCTextFieldFoundation extends MDCFoundation { static get cssClasses() { @@ -244,7 +244,7 @@ class MDCTextFieldFoundation extends MDCFoundation { /** * Activates the Text Field's focus state in cases when the input value - * changes without user input (e.g. programatically). + * changes without user input (e.g. programmatically). */ autoCompleteFocus() { if (!this.receivedUserInput_) { @@ -460,6 +460,8 @@ class MDCTextFieldFoundation extends MDCFoundation { private getNativeInput_(): HTMLInputElement | NativeInputType { return this.adapter_.getNativeInput() || { disabled: false, + maxLength: -1, + type: 'input', validity: { badInput: false, valid: true, diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts index adf91c339ae..68e240a6f48 100644 --- a/packages/mdc-textfield/types.ts +++ b/packages/mdc-textfield/types.ts @@ -26,10 +26,10 @@ import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; import {MDCTextFieldIconFoundation} from './icon/foundation'; export interface NativeInputType { - badInput?: boolean; disabled: boolean; - maxLength?: number; - validity: { + maxLength: number; + type: string; + validity: ValidityState | { badInput: boolean; valid: boolean; }; From c4e80b858a49dea917e3ed1ee2f948de809f0c70 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Thu, 7 Feb 2019 21:18:37 -0800 Subject: [PATCH 04/33] WIP: Iterate --- packages/mdc-textfield/index.ts | 275 ++++++++++++---------------- packages/mdc-textfield/package.json | 1 + packages/mdc-textfield/types.ts | 12 ++ 3 files changed, 129 insertions(+), 159 deletions(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index b6e4dfc8b79..1dd69ef1e8a 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -22,79 +22,54 @@ */ import {MDCComponent} from '@material/base/component'; -/* eslint-disable no-unused-vars */ +import * as ponyfill from '@material/dom/ponyfill'; +import {MDCFloatingLabel} from '@material/floating-label/index'; +import {MDCLineRipple} from '@material/line-ripple/index'; +import {MDCNotchedOutline} from '@material/notched-outline/index'; import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple/index'; -/* eslint-enable no-unused-vars */ -import {getMatchesProperty} from '@material/ripple/util'; - - +import {MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation} from './character-counter/index'; import {cssClasses, strings} from './constants'; -import {MDCTextFieldAdapter, FoundationMapType} from './adapter'; import {MDCTextFieldFoundation} from './foundation'; -/* eslint-disable no-unused-vars */ -import {MDCLineRipple, MDCLineRippleFoundation} from '@material/line-ripple/index'; import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text/index'; -import {MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation} from './character-counter/index'; -import {MDCTextFieldIcon, MDCTextFieldIconFoundation} from './icon/index'; -import {MDCFloatingLabel, MDCFloatingLabelFoundation} from '@material/floating-label/index'; -import {MDCNotchedOutline, MDCNotchedOutlineFoundation} from '@material/notched-outline/index'; -/* eslint-enable no-unused-vars */ - -class MDCTextField extends MDCComponent { - /** - * @param {...?} args - */ - constructor(...args) { - super(...args); - /** @private {?Element} */ - this.input_; - /** @type {?MDCRipple} */ - this.ripple; - /** @private {?MDCLineRipple} */ - this.lineRipple_; - /** @private {?MDCTextFieldHelperText} */ - this.helperText_; - /** @private {?MDCTextFieldCharacterCounter} */ - this.characterCounter_; - /** @private {?MDCTextFieldIcon} */ - this.leadingIcon_; - /** @private {?MDCTextFieldIcon} */ - this.trailingIcon_; - /** @private {?MDCFloatingLabel} */ - this.label_; - /** @private {?MDCNotchedOutline} */ - this.outline_; - } - +import {MDCTextFieldIcon} from './icon/index'; +import {FoundationMapType} from './types'; +import { + CharacterCounterFactory, + HelperTextFactory, + IconFactory, + LabelFactory, + LineRippleFactory, + OutlineFactory, RippleFactory, +} from './types'; + +class MDCTextField extends MDCComponent implements RippleCapableSurface { static attachTo(root: Element): MDCTextField { return new MDCTextField(root); } - /** - * @param {(function(!Element, MDCRippleFoundation): !MDCRipple)=} rippleFactory A function which - * creates a new MDCRipple. - * @param {(function(!Element): !MDCLineRipple)=} lineRippleFactory A function which - * creates a new MDCLineRipple. - * @param {(function(!Element): !MDCTextFieldHelperText)=} helperTextFactory A function which - * creates a new MDCTextFieldHelperText. - * @param {(function(!Element): !MDCTextFieldCharacterCounter)=} characterCounterFactory A function which - * creates a new MDCTextFieldCharacterCounter. - * @param {(function(!Element): !MDCTextFieldIcon)=} iconFactory A function which - * creates a new MDCTextFieldIcon. - * @param {(function(!Element): !MDCFloatingLabel)=} labelFactory A function which - * creates a new MDCFloatingLabel. - * @param {(function(!Element): !MDCNotchedOutline)=} outlineFactory A function which - * creates a new MDCNotchedOutline. - */ + // Public visibility for these properties is required by RippleCapableSurface. + root_!: HTMLElement; // assigned in MDCComponent constructor + ripple!: MDCRipple | null; // assigned in initialize() + + private input_!: HTMLInputElement; // assigned in initialize() + private lineRipple_!: MDCLineRipple; // assigned in initialize() + private helperText_!: MDCTextFieldHelperText; // assigned in initialize() + private characterCounter_!: MDCTextFieldCharacterCounter; // assigned in initialize() + private leadingIcon_!: MDCTextFieldIcon; // assigned in initialize() + private trailingIcon_!: MDCTextFieldIcon; // assigned in initialize() + private label_!: MDCFloatingLabel; // assigned in initialize() + private outline_!: MDCNotchedOutline; // assigned in initialize() + initialize( - rippleFactory = (el, foundation) => new MDCRipple(el, foundation), - lineRippleFactory = (el) => new MDCLineRipple(el), - helperTextFactory = (el) => new MDCTextFieldHelperText(el), - characterCounterFactory = (el) => new MDCTextFieldCharacterCounter(el), - iconFactory = (el) => new MDCTextFieldIcon(el), - labelFactory = (el) => new MDCFloatingLabel(el), - outlineFactory = (el) => new MDCNotchedOutline(el)) { - this.input_ = this.root_.querySelector(strings.INPUT_SELECTOR); + rippleFactory: RippleFactory = (el, foundation) => new MDCRipple(el, foundation), + lineRippleFactory: LineRippleFactory = (el) => new MDCLineRipple(el), + helperTextFactory: HelperTextFactory = (el) => new MDCTextFieldHelperText(el), + characterCounterFactory: CharacterCounterFactory = (el) => new MDCTextFieldCharacterCounter(el), + iconFactory: IconFactory = (el) => new MDCTextFieldIcon(el), + labelFactory: LabelFactory = (el) => new MDCFloatingLabel(el), + outlineFactory: OutlineFactory = (el) => new MDCNotchedOutline(el), + ) { + this.input_ = this.root_.querySelector(strings.INPUT_SELECTOR)!; const labelElement = this.root_.querySelector(strings.LABEL_SELECTOR); if (labelElement) { this.label_ = labelFactory(labelElement); @@ -112,7 +87,8 @@ class MDCTextField extends MDCComponent { const helperTextStrings = MDCTextFieldHelperTextFoundation.strings; const nextElementSibling = this.root_.nextElementSibling; const hasHelperLine = (nextElementSibling && nextElementSibling.classList.contains(cssClasses.HELPER_LINE)); - const helperTextEl = hasHelperLine && nextElementSibling.querySelector(helperTextStrings.ROOT_SELECTOR); + const helperTextEl = + hasHelperLine && nextElementSibling && nextElementSibling.querySelector(helperTextStrings.ROOT_SELECTOR); if (helperTextEl) { this.helperText_ = helperTextFactory(helperTextEl); } @@ -121,7 +97,7 @@ class MDCTextField extends MDCComponent { const characterCounterStrings = MDCTextFieldCharacterCounterFoundation.strings; let characterCounterEl = this.root_.querySelector(characterCounterStrings.ROOT_SELECTOR); // If character counter is not found in root element search in sibling element. - if (!characterCounterEl && hasHelperLine) { + if (!characterCounterEl && hasHelperLine && nextElementSibling) { characterCounterEl = nextElementSibling.querySelector(characterCounterStrings.ROOT_SELECTOR); } @@ -145,13 +121,20 @@ class MDCTextField extends MDCComponent { this.ripple = null; if (!this.root_.classList.contains(cssClasses.TEXTAREA) && !this.root_.classList.contains(cssClasses.OUTLINED)) { - const MATCHES = getMatchesProperty(HTMLElement.prototype); - const adapter = - Object.assign(MDCRipple.createAdapter(this)), { - isSurfaceActive: () => this.input_[MATCHES](':active'), - registerInteractionHandler: (type, handler) => this.input_.addEventListener(type, handler), - deregisterInteractionHandler: (type, handler) => this.input_.removeEventListener(type, handler), - }; + const adapter = { + ...MDCRipple.createAdapter(this), + ...({ + // tslint:disable:object-literal-sort-keys + isSurfaceActive: () => ponyfill.matches(this.input_, ':active'), + registerInteractionHandler: (type: string, handler: EventListener) => { + return this.input_.addEventListener(type, handler); + }, + deregisterInteractionHandler: (type: string, handler: EventListener) => { + return this.input_.removeEventListener(type, handler); + }, + // tslint:enable:object-literal-sort-keys + }), + }; const foundation = new MDCRippleFoundation(adapter); this.ripple = rippleFactory(this.root_, foundation); } @@ -186,7 +169,7 @@ class MDCTextField extends MDCComponent { } /** - * Initiliazes the Text Field's internal state based on the environment's + * Initializes the Text Field's internal state based on the environment's * state. */ initialSyncWithDom() { @@ -200,7 +183,7 @@ class MDCTextField extends MDCComponent { /** * @param value The value to set on the input. */ - set value(value: string): void { + set value(value: string) { this.foundation_.setValue(value); } @@ -211,7 +194,7 @@ class MDCTextField extends MDCComponent { /** * @param disabled Sets the Text Field disabled or enabled. */ - set disabled(disabled: boolean): void { + set disabled(disabled: boolean) { this.foundation_.setDisabled(disabled); } @@ -222,7 +205,7 @@ class MDCTextField extends MDCComponent { /** * @param valid Sets the Text Field valid or invalid. */ - set valid(valid: boolean): void { + set valid(valid: boolean) { this.foundation_.setValid(valid); } @@ -233,7 +216,7 @@ class MDCTextField extends MDCComponent { /** * @param required Sets the Text Field to required. */ - set required(required: boolean): void { + set required(required: boolean) { this.input_.required = required; } @@ -244,7 +227,7 @@ class MDCTextField extends MDCComponent { /** * @param pattern Sets the input element's validation pattern. */ - set pattern(pattern: string): void { + set pattern(pattern: string) { this.input_.pattern = pattern; } @@ -255,7 +238,7 @@ class MDCTextField extends MDCComponent { /** * @param minLength Sets the input element's minLength. */ - set minLength(minLength: number): void { + set minLength(minLength: number) { this.input_.minLength = minLength; } @@ -266,8 +249,8 @@ class MDCTextField extends MDCComponent { /** * @param maxLength Sets the input element's maxLength. */ - set maxLength(maxLength: number): void { - // Chrome throws exception if maxLength is set < 0 + set maxLength(maxLength: number) { + // Chrome throws exception if maxLength is se 0 if (maxLength < 0) { this.input_.removeAttribute('maxLength'); } else { @@ -282,7 +265,7 @@ class MDCTextField extends MDCComponent { /** * @param min Sets the input element's min. */ - set min(min: string): void { + set min(min: string) { this.input_.min = min; } @@ -293,7 +276,7 @@ class MDCTextField extends MDCComponent { /** * @param max Sets the input element's max. */ - set max(max: string): void { + set max(max: string) { this.input_.max = max; } @@ -304,42 +287,42 @@ class MDCTextField extends MDCComponent { /** * @param step Sets the input element's step. */ - set step(step: string): void { + set step(step: string) { this.input_.step = step; } /** * Sets the helper text element content. */ - set helperTextContent(content: string): void { + set helperTextContent(content: string) { this.foundation_.setHelperTextContent(content); } /** * Sets the aria label of the leading icon. */ - set leadingIconAriaLabel(label: string): void { + set leadingIconAriaLabel(label: string) { this.foundation_.setLeadingIconAriaLabel(label); } /** * Sets the text content of the leading icon. */ - set leadingIconContent(content: string): void { + set leadingIconContent(content: string) { this.foundation_.setLeadingIconContent(content); } /** * Sets the aria label of the trailing icon. */ - set trailingIconAriaLabel(label: string): void { + set trailingIconAriaLabel(label: string) { this.foundation_.setTrailingIconAriaLabel(label); } /** * Sets the text content of the trailing icon. */ - set trailingIconContent(content: string): void { + set trailingIconContent(content: string) { this.foundation_.setTrailingIconContent(content); } @@ -347,7 +330,7 @@ class MDCTextField extends MDCComponent { * Enables or disables the use of native validation. Use this for custom validation. * @param useNativeValidation Set this to false to ignore native input validation. */ - set useNativeValidation(useNativeValidation: boolean): void { + set useNativeValidation(useNativeValidation: boolean) { this.foundation_.setUseNativeValidation(useNativeValidation); } @@ -367,17 +350,20 @@ class MDCTextField extends MDCComponent { } getDefaultFoundation(): MDCTextFieldFoundation { - return new MDCTextFieldFoundation( - Object.assign({ + return new MDCTextFieldFoundation({ + ...({ + // tslint:disable:object-literal-sort-keys addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), hasClass: (className) => this.root_.classList.contains(className), registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler), deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler), registerValidationAttributeChangeHandler: (handler) => { - const getAttributesList = (mutationsList) => mutationsList.map((mutation) => mutation.attributeName; + const getAttributesList = (mutationsList: MutationRecord[]) => { + return mutationsList.map((mutation) => mutation.attributeName!).filter((attributeName) => attributeName); + }; const observer = new MutationObserver((mutationsList) => handler(getAttributesList(mutationsList))); - const targetNode = this.root_.querySelector(strings.INPUT_SELECTOR); + const targetNode = this.root_.querySelector(strings.INPUT_SELECTOR)!; const config = {attributes: true}; observer.observe(targetNode, config); return observer; @@ -386,46 +372,26 @@ class MDCTextField extends MDCComponent { isFocused: () => { return document.activeElement === this.root_.querySelector(strings.INPUT_SELECTOR); }, - }, - this.getInputAdapterMethods_(), - this.getLabelAdapterMethods_(), - this.getLineRippleAdapterMethods_(), - this.getOutlineAdapterMethods_())), - this.getFoundationMap_()); + // tslint:enable:object-literal-sort-keys + }), + ...this.getInputAdapterMethods_(), + ...this.getLabelAdapterMethods_(), + ...this.getLineRippleAdapterMethods_(), + ...this.getOutlineAdapterMethods_(), + ...this.getFoundationMap_(), + }); } - /** - * shakeLabel: function(boolean): undefined, - floatLabel: function(boolean): undefined, - hasLabel: function(): boolean, - getLabelWidth: function(): number, -}} - */ - getLabelAdapterMethods_(): { - * shakeLabel: function(boolean): undefined, - * floatLabel: function(boolean): undefined, - * hasLabel: function(): boolean, - * getLabelWidth: function(): number, - * { + getLabelAdapterMethods_() { return { - shakeLabel: (shouldShake) => this.label_.shake(shouldShake), - floatLabel: (shouldFloat) => this.label_.float(shouldFloat), - hasLabel: () => !!this.label_, + floatLabel: (shouldFloat: boolean) => this.label_.float(shouldFloat), getLabelWidth: () => this.label_ ? this.label_.getWidth() : 0, + hasLabel: () => !!this.label_, + shakeLabel: (shouldShake: boolean) => this.label_.shake(shouldShake), }; } - /** - * activateLineRipple: function(): undefined, - deactivateLineRipple: function(): undefined, - setLineRippleTransformOrigin: function(number): undefined, -}} - */ - getLineRippleAdapterMethods_(): { - * activateLineRipple: function(): undefined, - * deactivateLineRipple: function(): undefined, - * setLineRippleTransformOrigin: function(number): undefined, - * { + getLineRippleAdapterMethods_() { return { activateLineRipple: () => { if (this.lineRipple_) { @@ -437,7 +403,7 @@ class MDCTextField extends MDCComponent { this.lineRipple_.deactivate(); } }, - setLineRippleTransformOrigin: (normalizedX) => { + setLineRippleTransformOrigin: (normalizedX: number) => { if (this.lineRipple_) { this.lineRipple_.setRippleCenter(normalizedX); } @@ -445,54 +411,45 @@ class MDCTextField extends MDCComponent { }; } - /** - * notchOutline: function(number, boolean): undefined, - hasOutline: function(): boolean, -}} - */ - getOutlineAdapterMethods_(): { - * notchOutline: function(number, boolean): undefined, - * hasOutline: function(): boolean, - * { + getOutlineAdapterMethods_() { return { - notchOutline: (labelWidth) => this.outline_.notch(labelWidth), closeOutline: () => this.outline_.closeNotch(), hasOutline: () => !!this.outline_, + notchOutline: (labelWidth: number) => this.outline_.notch(labelWidth), }; } - /** - * registerInputInteractionHandler: function(string, function()): undefined, - deregisterInputInteractionHandler: function(string, function()): undefined, - getNativeInput: function(): ?Element, -}} - */ - getInputAdapterMethods_(): { - * registerInputInteractionHandler: function(string, function()): undefined, - * deregisterInputInteractionHandler: function(string, function()): undefined, - * getNativeInput: function(): ?Element, - * { + getInputAdapterMethods_() { + // tslint:disable:object-literal-sort-keys return { - registerInputInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler), - deregisterInputInteractionHandler: (evtType, handler) => this.input_.removeEventListener(evtType, handler), + registerInputInteractionHandler: (evtType: string, handler: EventListener) => { + return this.input_.addEventListener(evtType, handler); + }, + deregisterInputInteractionHandler: (evtType: string, handler: EventListener) => { + return this.input_.removeEventListener(evtType, handler); + }, getNativeInput: () => this.input_, }; + // tslint:enable:object-literal-sort-keys } /** - * Returns a map of all subcomponents to subfoundations. + * @return A map of all subcomponents to subfoundations. */ - getFoundationMap_(): FoundationMapType { + getFoundationMap_(): Partial { return { - helperText: this.helperText_ ? this.helperText_.foundation : undefined, characterCounter: this.characterCounter_ ? this.characterCounter_.foundation : undefined, + helperText: this.helperText_ ? this.helperText_.foundation : undefined, leadingIcon: this.leadingIcon_ ? this.leadingIcon_.foundation : undefined, trailingIcon: this.trailingIcon_ ? this.trailingIcon_.foundation : undefined, }; } } -export {MDCTextField, MDCTextFieldFoundation, - MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation, - MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation, - MDCTextFieldIcon, MDCTextFieldIconFoundation}; +export {MDCTextField as default, MDCTextField}; +export * from './adapter'; +export * from './foundation'; +export * from './types'; +export * from './character-counter/index'; +export * from './helper-text/index'; +export * from './icon/index'; diff --git a/packages/mdc-textfield/package.json b/packages/mdc-textfield/package.json index aab6c6027cf..4b5aa7e97ff 100644 --- a/packages/mdc-textfield/package.json +++ b/packages/mdc-textfield/package.json @@ -17,6 +17,7 @@ "dependencies": { "@material/animation": "^0.41.0", "@material/base": "^0.41.0", + "@material/dom": "^0.41.0", "@material/floating-label": "^0.44.0", "@material/line-ripple": "^0.43.0", "@material/notched-outline": "^0.44.0", diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts index 68e240a6f48..38d6d771a87 100644 --- a/packages/mdc-textfield/types.ts +++ b/packages/mdc-textfield/types.ts @@ -21,8 +21,12 @@ * THE SOFTWARE. */ +import {MDCRipple, MDCRippleFoundation} from '@material/ripple'; +import {MDCTextFieldCharacterCounter} from './character-counter'; import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; +import {MDCTextFieldHelperText} from './helper-text'; import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; +import {MDCTextFieldIcon} from './icon'; import {MDCTextFieldIconFoundation} from './icon/foundation'; export interface NativeInputType { @@ -42,3 +46,11 @@ export interface FoundationMapType { leadingIcon: MDCTextFieldIconFoundation; trailingIcon: MDCTextFieldIconFoundation; } + +export type RippleFactory = (el: Element, foundation: MDCRippleFoundation) => MDCRipple; +export type LineRippleFactory = (el: Element) => MDCLineRipple; +export type HelperTextFactory = (el: Element) => MDCTextFieldHelperText; +export type CharacterCounterFactory = (el: Element) => MDCTextFieldCharacterCounter; +export type IconFactory = (el: Element) => MDCTextFieldIcon; +export type LabelFactory = (el: Element) => MDCFloatingLabel; +export type OutlineFactory = (el: Element) => MDCNotchedOutline; From fb56327374bb1b10f62732af5ca727fb54485b1c Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 11:09:29 -0800 Subject: [PATCH 05/33] WIP: Add missing imports --- packages/mdc-textfield/types.ts | 11 +++++++---- scripts/webpack/js-bundle-factory.js | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts index 38d6d771a87..b3f2e3316f7 100644 --- a/packages/mdc-textfield/types.ts +++ b/packages/mdc-textfield/types.ts @@ -21,13 +21,16 @@ * THE SOFTWARE. */ -import {MDCRipple, MDCRippleFoundation} from '@material/ripple'; -import {MDCTextFieldCharacterCounter} from './character-counter'; +import {MDCFloatingLabel} from '@material/floating-label/index'; +import {MDCLineRipple} from '@material/line-ripple/index'; +import {MDCNotchedOutline} from '@material/notched-outline/index'; +import {MDCRipple, MDCRippleFoundation} from '@material/ripple/index'; import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; -import {MDCTextFieldHelperText} from './helper-text'; +import {MDCTextFieldCharacterCounter} from './character-counter/index'; import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; -import {MDCTextFieldIcon} from './icon'; +import {MDCTextFieldHelperText} from './helper-text/index'; import {MDCTextFieldIconFoundation} from './icon/foundation'; +import {MDCTextFieldIcon} from './icon/index'; export interface NativeInputType { disabled: boolean; diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index 32fcef28849..4b7645d12de 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -183,7 +183,7 @@ class JsBundleFactory { tabIndicator: getAbsolutePath('/packages/mdc-tab-indicator/index.js'), tabScroller: getAbsolutePath('/packages/mdc-tab-scroller/index.js'), tabs: getAbsolutePath('/packages/mdc-tabs/index.js'), - textfield: getAbsolutePath('/packages/mdc-textfield/index.js'), + textfield: getAbsolutePath('/packages/mdc-textfield/index.ts'), toolbar: getAbsolutePath('/packages/mdc-toolbar/index.js'), topAppBar: getAbsolutePath('/packages/mdc-top-app-bar/index.js'), }, From 4f49d42a2751388514fe8965af7f4e679a2e9276 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 13:16:47 -0800 Subject: [PATCH 06/33] WIP: Address review feedback --- packages/mdc-textfield/adapter.ts | 9 ++++--- .../character-counter/foundation.ts | 2 +- packages/mdc-textfield/foundation.ts | 26 +++++++++++-------- .../mdc-textfield/helper-text/foundation.ts | 6 ++--- packages/mdc-textfield/icon/adapter.ts | 6 +++-- packages/mdc-textfield/icon/foundation.ts | 2 +- packages/mdc-textfield/index.ts | 26 +++++++++++-------- 7 files changed, 44 insertions(+), 33 deletions(-) diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index ecb614d6806..36b1ee66070 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -22,6 +22,7 @@ */ import {NativeInputType} from './types'; +import {EventType, SpecificEventListener} from '@material/base/index'; /** * Defines the shape of the adapter expected by the foundation. @@ -49,22 +50,22 @@ interface MDCTextFieldAdapter { /** * Registers an event handler on the root element for a given event. */ - registerTextFieldInteractionHandler(type: string, handler: EventListener): void; + registerTextFieldInteractionHandler(evtType: E, handler: SpecificEventListener): void; /** * Deregisters an event handler on the root element for a given event. */ - deregisterTextFieldInteractionHandler(type: string, handler: EventListener): void; + deregisterTextFieldInteractionHandler(evtType: E, handler: SpecificEventListener): void; /** * Registers an event listener on the native input element for a given event. */ - registerInputInteractionHandler(evtType: string, handler: EventListener): void; + registerInputInteractionHandler(evtType: E, handler: SpecificEventListener): void; /** * Deregisters an event listener on the native input element for a given event. */ - deregisterInputInteractionHandler(evtType: string, handler: EventListener): void; + deregisterInputInteractionHandler(evtType: E, handler: SpecificEventListener): void; /** * Registers a validation attribute change listener on the input element. diff --git a/packages/mdc-textfield/character-counter/foundation.ts b/packages/mdc-textfield/character-counter/foundation.ts index 1d551996502..71f6b88a0bd 100644 --- a/packages/mdc-textfield/character-counter/foundation.ts +++ b/packages/mdc-textfield/character-counter/foundation.ts @@ -47,7 +47,7 @@ class MDCTextFieldCharacterCounterFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; @@ -43,7 +47,7 @@ class MDCTextFieldFoundation extends MDCFoundation { } get shouldShake(): boolean { - return !this.isValid() && !this.isFocused_ && !!this.getValue(); + return !this.isFocused_ && !this.isValid() && Boolean(this.getValue()); } private get shouldAlwaysFloat_(): boolean { @@ -52,7 +56,7 @@ class MDCTextFieldFoundation extends MDCFoundation { } get shouldFloat(): boolean { - return this.shouldAlwaysFloat_ || this.isFocused_ || !!this.getValue() || this.isBadInput_(); + return this.shouldAlwaysFloat_ || this.isFocused_ || Boolean(this.getValue()) || this.isBadInput_(); } /** @@ -135,10 +139,10 @@ class MDCTextFieldFoundation extends MDCFoundation { this.adapter_.registerInputInteractionHandler('focus', this.inputFocusHandler_); this.adapter_.registerInputInteractionHandler('blur', this.inputBlurHandler_); this.adapter_.registerInputInteractionHandler('input', this.inputInputHandler_); - ['mousedown', 'touchstart'].forEach((evtType) => { + MOUSEDOWN_TOUCHSTART_EVENTS.forEach((evtType) => { this.adapter_.registerInputInteractionHandler(evtType, this.setPointerXOffset_); }); - ['click', 'keydown'].forEach((evtType) => { + CLICK_KEYDOWN_EVENTS.forEach((evtType) => { this.adapter_.registerTextFieldInteractionHandler(evtType, this.textFieldInteractionHandler_); }); this.validationObserver_ = @@ -150,10 +154,10 @@ class MDCTextFieldFoundation extends MDCFoundation { this.adapter_.deregisterInputInteractionHandler('focus', this.inputFocusHandler_); this.adapter_.deregisterInputInteractionHandler('blur', this.inputBlurHandler_); this.adapter_.deregisterInputInteractionHandler('input', this.inputInputHandler_); - ['mousedown', 'touchstart'].forEach((evtType) => { + MOUSEDOWN_TOUCHSTART_EVENTS.forEach((evtType) => { this.adapter_.deregisterInputInteractionHandler(evtType, this.setPointerXOffset_); }); - ['click', 'keydown'].forEach((evtType) => { + CLICK_KEYDOWN_EVENTS.forEach((evtType) => { this.adapter_.deregisterTextFieldInteractionHandler(evtType, this.textFieldInteractionHandler_); }); this.adapter_.deregisterValidationAttributeChangeHandler(this.validationObserver_); @@ -293,7 +297,7 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * Otherwise, returns the result of native validity checks. + * @return The custom validity state, if set; otherwise, the result of a native validity check. */ isValid(): boolean { return this.useNativeValidation_ @@ -301,7 +305,7 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @param isValid Sets the validity state of the Text Field. + * @param isValid Sets the custom validity state of the Text Field. */ setValid(isValid: boolean): void { this.isValid_ = isValid; @@ -393,14 +397,14 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * user-supplied value + * @return True if the Text Field input fails in converting the user-supplied value. */ private isBadInput_(): boolean { return this.getNativeInput_().validity.badInput; } /** - * (ValidityState.valid). + * @return The result of native validity checking (ValidityState.valid). */ private isNativeInputValid_(): boolean { return this.getNativeInput_().validity.valid; @@ -455,7 +459,7 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * host environment, or a dummy if none exists + * @return The native text input from the host environment, or a dummy if none exists. */ private getNativeInput_(): HTMLInputElement | NativeInputType { return this.adapter_.getNativeInput() || { diff --git a/packages/mdc-textfield/helper-text/foundation.ts b/packages/mdc-textfield/helper-text/foundation.ts index 02ae4e54ce4..0955d0d6f47 100644 --- a/packages/mdc-textfield/helper-text/foundation.ts +++ b/packages/mdc-textfield/helper-text/foundation.ts @@ -57,7 +57,7 @@ class MDCTextFieldHelperTextFoundation extends MDCFoundation(evtType: E, handler: SpecificEventListener): void; /** * Deregisters an event listener on the icon element for a given event. */ - deregisterInteractionHandler(evtType: string, handler: EventListener): void; + deregisterInteractionHandler(evtType: E, handler: SpecificEventListener): void; /** * Emits a custom event "MDCTextField:icon" denoting a user has clicked the icon. diff --git a/packages/mdc-textfield/icon/foundation.ts b/packages/mdc-textfield/icon/foundation.ts index dfca43e95ea..8df7a814827 100644 --- a/packages/mdc-textfield/icon/foundation.ts +++ b/packages/mdc-textfield/icon/foundation.ts @@ -93,7 +93,7 @@ class MDCTextFieldIconFoundation extends MDCFoundation this.adapter_.setContent(content); } - handleInteraction(evt: Event): void { + handleInteraction(evt: Event) { const isEnterKey = (evt as KeyboardEvent).key === 'Enter' || (evt as KeyboardEvent).keyCode === 13; if (evt.type === 'click' || isEnterKey) { this.adapter_.notifyIconAction(); diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 1dd69ef1e8a..027a4cd7506 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -22,16 +22,17 @@ */ import {MDCComponent} from '@material/base/component'; +import {EventType, SpecificEventListener} from '@material/base/index'; import * as ponyfill from '@material/dom/ponyfill'; import {MDCFloatingLabel} from '@material/floating-label/index'; import {MDCLineRipple} from '@material/line-ripple/index'; import {MDCNotchedOutline} from '@material/notched-outline/index'; import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple/index'; -import {MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation} from './character-counter/index'; +import {MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation} from './character-counter'; import {cssClasses, strings} from './constants'; import {MDCTextFieldFoundation} from './foundation'; -import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text/index'; -import {MDCTextFieldIcon} from './icon/index'; +import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text'; +import {MDCTextFieldIcon} from './icon'; import {FoundationMapType} from './types'; import { CharacterCounterFactory, @@ -120,17 +121,20 @@ class MDCTextField extends MDCComponent implements Rippl } this.ripple = null; - if (!this.root_.classList.contains(cssClasses.TEXTAREA) && !this.root_.classList.contains(cssClasses.OUTLINED)) { + + const isTextArea = this.root_.classList.contains(cssClasses.TEXTAREA); + const isOutlined = this.root_.classList.contains(cssClasses.OUTLINED); + if (!isTextArea && !isOutlined) { const adapter = { ...MDCRipple.createAdapter(this), ...({ // tslint:disable:object-literal-sort-keys isSurfaceActive: () => ponyfill.matches(this.input_, ':active'), - registerInteractionHandler: (type: string, handler: EventListener) => { - return this.input_.addEventListener(type, handler); + registerInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + return this.input_.addEventListener(evtType, handler); }, - deregisterInteractionHandler: (type: string, handler: EventListener) => { - return this.input_.removeEventListener(type, handler); + deregisterInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + return this.input_.removeEventListener(evtType, handler); }, // tslint:enable:object-literal-sort-keys }), @@ -250,7 +254,7 @@ class MDCTextField extends MDCComponent implements Rippl * @param maxLength Sets the input element's maxLength. */ set maxLength(maxLength: number) { - // Chrome throws exception if maxLength is se 0 + // Chrome throws exception if maxLength is set to a value less than zero if (maxLength < 0) { this.input_.removeAttribute('maxLength'); } else { @@ -422,10 +426,10 @@ class MDCTextField extends MDCComponent implements Rippl getInputAdapterMethods_() { // tslint:disable:object-literal-sort-keys return { - registerInputInteractionHandler: (evtType: string, handler: EventListener) => { + registerInputInteractionHandler: (evtType: E, handler: SpecificEventListener) => { return this.input_.addEventListener(evtType, handler); }, - deregisterInputInteractionHandler: (evtType: string, handler: EventListener) => { + deregisterInputInteractionHandler: (evtType: E, handler: SpecificEventListener) => { return this.input_.removeEventListener(evtType, handler); }, getNativeInput: () => this.input_, From edbe19da192b01e8a03dc90b7a2302014e4cf43f Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 13:25:17 -0800 Subject: [PATCH 07/33] WIP: Fix type errors --- .../mdc-textfield/character-counter/foundation.ts | 4 ++-- packages/mdc-textfield/helper-text/foundation.ts | 4 ++-- packages/mdc-textfield/icon/adapter.ts | 2 +- packages/mdc-textfield/icon/foundation.ts | 11 +++++++---- packages/mdc-textfield/icon/index.ts | 9 +++++++-- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/mdc-textfield/character-counter/foundation.ts b/packages/mdc-textfield/character-counter/foundation.ts index 71f6b88a0bd..0e47aef0f91 100644 --- a/packages/mdc-textfield/character-counter/foundation.ts +++ b/packages/mdc-textfield/character-counter/foundation.ts @@ -43,8 +43,8 @@ class MDCTextFieldCharacterCounterFoundation extends MDCFoundation = {}) { + super({...MDCTextFieldCharacterCounterFoundation.defaultAdapter, ...adapter}); } setCounterValue(currentLength: number, maxLength: number) { diff --git a/packages/mdc-textfield/helper-text/foundation.ts b/packages/mdc-textfield/helper-text/foundation.ts index 0955d0d6f47..aa2ce690b4a 100644 --- a/packages/mdc-textfield/helper-text/foundation.ts +++ b/packages/mdc-textfield/helper-text/foundation.ts @@ -50,8 +50,8 @@ class MDCTextFieldHelperTextFoundation extends MDCFoundation = {}) { + super({...MDCTextFieldHelperTextFoundation.defaultAdapter, ...adapter}); } /** diff --git a/packages/mdc-textfield/icon/adapter.ts b/packages/mdc-textfield/icon/adapter.ts index 0f2271b6d9e..e0059d34226 100644 --- a/packages/mdc-textfield/icon/adapter.ts +++ b/packages/mdc-textfield/icon/adapter.ts @@ -54,7 +54,7 @@ interface MDCTextFieldIconAdapter { /** * Registers an event listener on the icon element for a given event. */ - registerInteractionHandle(evtType: E, handler: SpecificEventListener): void; + registerInteractionHandler(evtType: E, handler: SpecificEventListener): void; /** * Deregisters an event listener on the icon element for a given event. diff --git a/packages/mdc-textfield/icon/foundation.ts b/packages/mdc-textfield/icon/foundation.ts index 8df7a814827..b8a9dbc6bb0 100644 --- a/packages/mdc-textfield/icon/foundation.ts +++ b/packages/mdc-textfield/icon/foundation.ts @@ -22,9 +22,12 @@ */ import {MDCFoundation} from '@material/base/foundation'; +import {EventType} from '@material/base/index'; import {MDCTextFieldIconAdapter} from './adapter'; import {strings} from './constants'; +const CLICK_KEYDOWN_EVENTS: EventType[] = ['click', 'keydown']; + class MDCTextFieldIconFoundation extends MDCFoundation { static get strings() { return strings; @@ -50,8 +53,8 @@ class MDCTextFieldIconFoundation extends MDCFoundation private savedTabIndex_: string | null; private readonly interactionHandler_: EventListener; - constructor(adapter: MDCTextFieldIconAdapter) { - super(Object.assign(MDCTextFieldIconFoundation.defaultAdapter, adapter)); + constructor(adapter: Partial = {}) { + super({...MDCTextFieldIconFoundation.defaultAdapter, ...adapter}); this.savedTabIndex_ = null; this.interactionHandler_ = (evt) => this.handleInteraction(evt); @@ -60,13 +63,13 @@ class MDCTextFieldIconFoundation extends MDCFoundation init() { this.savedTabIndex_ = this.adapter_.getAttr('tabindex'); - ['click', 'keydown'].forEach((evtType) => { + CLICK_KEYDOWN_EVENTS.forEach((evtType) => { this.adapter_.registerInteractionHandler(evtType, this.interactionHandler_); }); } destroy() { - ['click', 'keydown'].forEach((evtType) => { + CLICK_KEYDOWN_EVENTS.forEach((evtType) => { this.adapter_.deregisterInteractionHandler(evtType, this.interactionHandler_); }); } diff --git a/packages/mdc-textfield/icon/index.ts b/packages/mdc-textfield/icon/index.ts index 514d296aac9..17dcda75b0b 100644 --- a/packages/mdc-textfield/icon/index.ts +++ b/packages/mdc-textfield/icon/index.ts @@ -23,6 +23,7 @@ import {MDCComponent} from '@material/base/component'; import {MDCTextFieldIconFoundation} from './foundation'; +import {EventType, SpecificEventListener} from '@material/base/index'; class MDCTextFieldIcon extends MDCComponent { static attachTo(root: Element): MDCTextFieldIcon { @@ -40,8 +41,12 @@ class MDCTextFieldIcon extends MDCComponent { setAttr: (attr, value) => this.root_.setAttribute(attr, value), removeAttr: (attr) => this.root_.removeAttribute(attr), setContent: (content) => this.root_.textContent = content, - registerInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler), - deregisterInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler), + registerInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + this.root_.addEventListener(evtType, handler); + }, + deregisterInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + this.root_.removeEventListener(evtType, handler); + }, notifyIconAction: () => this.emit( MDCTextFieldIconFoundation.strings.ICON_EVENT, {} /* evtData */, true /* shouldBubble */), }); From cb8620a8d13c549ab7e5b99e88951d8e62258446 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 14:23:17 -0800 Subject: [PATCH 08/33] WIP: Style --- packages/mdc-textfield/foundation.ts | 2 +- test/unit/mdc-textfield/mdc-text-field.test.js | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 01cff4d5057..4b5e48b5082 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -112,7 +112,7 @@ class MDCTextFieldFoundation extends MDCFoundation { * @param adapter * @param foundationMap Map from subcomponent names to their subfoundations. */ - constructor(adapter: MDCTextFieldAdapter, foundationMap: Partial = {}) { + constructor(adapter?: Partial, foundationMap: Partial = {}) { super({...MDCTextFieldFoundation.defaultAdapter, ...adapter}); this.helperText_ = foundationMap.helperText; diff --git a/test/unit/mdc-textfield/mdc-text-field.test.js b/test/unit/mdc-textfield/mdc-text-field.test.js index 543fd12d157..435aa5d0a7b 100644 --- a/test/unit/mdc-textfield/mdc-text-field.test.js +++ b/test/unit/mdc-textfield/mdc-text-field.test.js @@ -30,8 +30,9 @@ import {MDCRipple} from '../../../packages/mdc-ripple/index'; import {MDCLineRipple} from '../../../packages/mdc-line-ripple/index'; import {MDCFloatingLabel} from '../../../packages/mdc-floating-label/index'; import {MDCNotchedOutline} from '../../../packages/mdc-notched-outline/index'; -import {MDCTextField, MDCTextFieldFoundation, MDCTextFieldHelperText, MDCTextFieldCharacterCounter, - MDCTextFieldIcon} from '../../../packages/mdc-textfield/index'; +import { + MDCTextField, MDCTextFieldFoundation, MDCTextFieldHelperText, MDCTextFieldCharacterCounter, MDCTextFieldIcon, +} from '../../../packages/mdc-textfield/index'; import {cssClasses as helperTextCssClasses} from '../../../packages/mdc-textfield/helper-text/constants'; import {cssClasses as characterCounterCssClasses} from '../../../packages/mdc-textfield/character-counter/constants'; @@ -467,9 +468,7 @@ test('#adapter.setLineRippleTransformOrigin calls the setRippleCenter method on function setupMockFoundationTest(root = getFixture()) { const MockFoundationConstructor = td.constructor(MDCTextFieldFoundation); const mockFoundation = new MockFoundationConstructor(); - const component = new MDCTextField( - root, - mockFoundation); + const component = new MDCTextField(root, mockFoundation); return {root, component, mockFoundation}; } From 3e01b8cb56bae3ddc4d0479c1f840309b19efb2b Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 14:35:08 -0800 Subject: [PATCH 09/33] WIP: Fix remaining unit test failures --- packages/mdc-textfield/foundation.ts | 6 +++++- test/unit/mdc-textfield/foundation.test.js | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 4b5e48b5082..7873835a2ce 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -462,7 +462,11 @@ class MDCTextFieldFoundation extends MDCFoundation { * @return The native text input from the host environment, or a dummy if none exists. */ private getNativeInput_(): HTMLInputElement | NativeInputType { - return this.adapter_.getNativeInput() || { + // adapter_ can be undefined in foundation unit tests. This happens when testdouble is creating a mock object and + // invokes the shouldShake/shouldFloat getters (which in turn call getValue(), which calls this method) before + // init() has been called in the MDCTextField constructor. + const nativeInput = this.adapter_ ? this.adapter_.getNativeInput() : null; + return nativeInput || { disabled: false, maxLength: -1, type: 'input', diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 178462377d5..55924ef0a9b 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -56,16 +56,16 @@ test('defaultAdapter returns a complete adapter implementation', () => { ]); }); -const setupTest = () => { +const setupTest = ({characterCounter = false} = {}) => { const mockAdapter = td.object(MDCTextFieldFoundation.defaultAdapter); const helperText = td.object({ setContent: () => {}, showToScreenReader: () => {}, setValidity: () => {}, }); - const characterCounter = td.object({ + characterCounter = characterCounter === true ? td.object({ setCounterValue: () => {}, - }); + }) : undefined; const leadingIcon = td.object({ setDisabled: () => {}, setAriaLabel: () => {}, @@ -866,7 +866,7 @@ test('#handleInput activates focus state', () => { }); test('#handleInput updates character counter on text input', () => { - const {foundation, mockAdapter, characterCounter} = setupTest(); + const {foundation, mockAdapter, characterCounter} = setupTest({characterCounter: true}); const nativeInput = { type: 'text', @@ -884,7 +884,7 @@ test('#handleInput updates character counter on text input', () => { test('#handleInput throws error when maxLength HTML attribute is not found in input element', () => { - const {foundation, mockAdapter} = setupTest(); + const {foundation, mockAdapter} = setupTest({characterCounter: true}); const nativeInput = { type: 'text', @@ -901,7 +901,7 @@ test('#handleInput throws error when maxLength HTML attribute is not found in in test('#handleValidationAttributeChange sets character counter when maxlength attribute value is changed in input ' + 'element', () => { - const {foundation, mockAdapter, characterCounter} = setupTest(); + const {foundation, mockAdapter, characterCounter} = setupTest({characterCounter: true}); const nativeInput = { type: 'text', From c390b61ffd81ec9b7ec912e598e47ddb2c5d2aaa Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 14:47:53 -0800 Subject: [PATCH 10/33] WIP: FIx linter errors --- packages/mdc-textfield/adapter.ts | 2 +- packages/mdc-textfield/icon/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index 36b1ee66070..4e69648807d 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -21,8 +21,8 @@ * THE SOFTWARE. */ -import {NativeInputType} from './types'; import {EventType, SpecificEventListener} from '@material/base/index'; +import {NativeInputType} from './types'; /** * Defines the shape of the adapter expected by the foundation. diff --git a/packages/mdc-textfield/icon/index.ts b/packages/mdc-textfield/icon/index.ts index 17dcda75b0b..cce3657f5b7 100644 --- a/packages/mdc-textfield/icon/index.ts +++ b/packages/mdc-textfield/icon/index.ts @@ -22,8 +22,8 @@ */ import {MDCComponent} from '@material/base/component'; -import {MDCTextFieldIconFoundation} from './foundation'; import {EventType, SpecificEventListener} from '@material/base/index'; +import {MDCTextFieldIconFoundation} from './foundation'; class MDCTextFieldIcon extends MDCComponent { static attachTo(root: Element): MDCTextFieldIcon { From 3bcff48ff56c011b60a81d2a70d647cee5484875 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 14:48:28 -0800 Subject: [PATCH 11/33] WIP: Mark private fields as nullable --- packages/mdc-textfield/index.ts | 91 +++++++++++++++------------------ 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 027a4cd7506..5df0ab2eb9e 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -33,14 +33,15 @@ import {cssClasses, strings} from './constants'; import {MDCTextFieldFoundation} from './foundation'; import {MDCTextFieldHelperText, MDCTextFieldHelperTextFoundation} from './helper-text'; import {MDCTextFieldIcon} from './icon'; -import {FoundationMapType} from './types'; import { CharacterCounterFactory, + FoundationMapType, HelperTextFactory, IconFactory, LabelFactory, LineRippleFactory, - OutlineFactory, RippleFactory, + OutlineFactory, + RippleFactory, } from './types'; class MDCTextField extends MDCComponent implements RippleCapableSurface { @@ -52,14 +53,14 @@ class MDCTextField extends MDCComponent implements Rippl root_!: HTMLElement; // assigned in MDCComponent constructor ripple!: MDCRipple | null; // assigned in initialize() + private characterCounter_!: MDCTextFieldCharacterCounter | null; // assigned in initialize() + private helperText_!: MDCTextFieldHelperText | null; // assigned in initialize() private input_!: HTMLInputElement; // assigned in initialize() - private lineRipple_!: MDCLineRipple; // assigned in initialize() - private helperText_!: MDCTextFieldHelperText; // assigned in initialize() - private characterCounter_!: MDCTextFieldCharacterCounter; // assigned in initialize() - private leadingIcon_!: MDCTextFieldIcon; // assigned in initialize() - private trailingIcon_!: MDCTextFieldIcon; // assigned in initialize() - private label_!: MDCFloatingLabel; // assigned in initialize() - private outline_!: MDCNotchedOutline; // assigned in initialize() + private label_!: MDCFloatingLabel | null; // assigned in initialize() + private leadingIcon_!: MDCTextFieldIcon | null; // assigned in initialize() + private lineRipple_!: MDCLineRipple | null; // assigned in initialize() + private outline_!: MDCNotchedOutline | null; // assigned in initialize() + private trailingIcon_!: MDCTextFieldIcon | null; // assigned in initialize() initialize( rippleFactory: RippleFactory = (el, foundation) => new MDCRipple(el, foundation), @@ -71,18 +72,15 @@ class MDCTextField extends MDCComponent implements Rippl outlineFactory: OutlineFactory = (el) => new MDCNotchedOutline(el), ) { this.input_ = this.root_.querySelector(strings.INPUT_SELECTOR)!; + const labelElement = this.root_.querySelector(strings.LABEL_SELECTOR); - if (labelElement) { - this.label_ = labelFactory(labelElement); - } + this.label_ = labelElement ? labelFactory(labelElement) : null; + const lineRippleElement = this.root_.querySelector(strings.LINE_RIPPLE_SELECTOR); - if (lineRippleElement) { - this.lineRipple_ = lineRippleFactory(lineRippleElement); - } + this.lineRipple_ = lineRippleElement ? lineRippleFactory(lineRippleElement) : null; + const outlineElement = this.root_.querySelector(strings.OUTLINE_SELECTOR); - if (outlineElement) { - this.outline_ = outlineFactory(outlineElement); - } + this.outline_ = outlineElement ? outlineFactory(outlineElement) : null; // Helper text const helperTextStrings = MDCTextFieldHelperTextFoundation.strings; @@ -90,9 +88,7 @@ class MDCTextField extends MDCComponent implements Rippl const hasHelperLine = (nextElementSibling && nextElementSibling.classList.contains(cssClasses.HELPER_LINE)); const helperTextEl = hasHelperLine && nextElementSibling && nextElementSibling.querySelector(helperTextStrings.ROOT_SELECTOR); - if (helperTextEl) { - this.helperText_ = helperTextFactory(helperTextEl); - } + this.helperText_ = helperTextEl ? helperTextFactory(helperTextEl) : null; // Character counter const characterCounterStrings = MDCTextFieldCharacterCounterFoundation.strings; @@ -101,11 +97,10 @@ class MDCTextField extends MDCComponent implements Rippl if (!characterCounterEl && hasHelperLine && nextElementSibling) { characterCounterEl = nextElementSibling.querySelector(characterCounterStrings.ROOT_SELECTOR); } + this.characterCounter_ = characterCounterEl ? characterCounterFactory(characterCounterEl) : null; - if (characterCounterEl) { - this.characterCounter_ = characterCounterFactory(characterCounterEl); - } - + this.leadingIcon_ = null; + this.trailingIcon_ = null; const iconElements = this.root_.querySelectorAll(strings.ICON_SELECTOR); if (iconElements.length > 0) { if (iconElements.length > 1) { // Has both icons. @@ -120,28 +115,22 @@ class MDCTextField extends MDCComponent implements Rippl } } - this.ripple = null; - const isTextArea = this.root_.classList.contains(cssClasses.TEXTAREA); const isOutlined = this.root_.classList.contains(cssClasses.OUTLINED); - if (!isTextArea && !isOutlined) { - const adapter = { - ...MDCRipple.createAdapter(this), - ...({ - // tslint:disable:object-literal-sort-keys - isSurfaceActive: () => ponyfill.matches(this.input_, ':active'), - registerInteractionHandler: (evtType: E, handler: SpecificEventListener) => { - return this.input_.addEventListener(evtType, handler); - }, - deregisterInteractionHandler: (evtType: E, handler: SpecificEventListener) => { - return this.input_.removeEventListener(evtType, handler); - }, - // tslint:enable:object-literal-sort-keys - }), - }; - const foundation = new MDCRippleFoundation(adapter); - this.ripple = rippleFactory(this.root_, foundation); - } + this.ripple = (isTextArea || isOutlined) ? null : rippleFactory(this.root_, new MDCRippleFoundation({ + ...MDCRipple.createAdapter(this), + ...({ + // tslint:disable:object-literal-sort-keys + isSurfaceActive: () => ponyfill.matches(this.input_, ':active'), + registerInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + return this.input_.addEventListener(evtType, handler); + }, + deregisterInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + return this.input_.removeEventListener(evtType, handler); + }, + // tslint:enable:object-literal-sort-keys + }), + })); } destroy() { @@ -388,10 +377,10 @@ class MDCTextField extends MDCComponent implements Rippl getLabelAdapterMethods_() { return { - floatLabel: (shouldFloat: boolean) => this.label_.float(shouldFloat), + floatLabel: (shouldFloat: boolean) => this.label_ && this.label_.float(shouldFloat), getLabelWidth: () => this.label_ ? this.label_.getWidth() : 0, - hasLabel: () => !!this.label_, - shakeLabel: (shouldShake: boolean) => this.label_.shake(shouldShake), + hasLabel: () => Boolean(this.label_), + shakeLabel: (shouldShake: boolean) => this.label_ && this.label_.shake(shouldShake), }; } @@ -417,9 +406,9 @@ class MDCTextField extends MDCComponent implements Rippl getOutlineAdapterMethods_() { return { - closeOutline: () => this.outline_.closeNotch(), - hasOutline: () => !!this.outline_, - notchOutline: (labelWidth: number) => this.outline_.notch(labelWidth), + closeOutline: () => this.outline_ && this.outline_.closeNotch(), + hasOutline: () => Boolean(this.outline_), + notchOutline: (labelWidth: number) => this.outline_ && this.outline_.notch(labelWidth), }; } From 4d657d7e89877527a4211241667214388d860152 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 14:54:17 -0800 Subject: [PATCH 12/33] WIP: Remove unnecessary `querySelector()` calls and non-nullable `!` assertions --- packages/mdc-textfield/index.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 5df0ab2eb9e..60f9e189c01 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -352,19 +352,18 @@ class MDCTextField extends MDCComponent implements Rippl registerTextFieldInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler), deregisterTextFieldInteractionHandler: (evtType, handler) => this.root_.removeEventListener(evtType, handler), registerValidationAttributeChangeHandler: (handler) => { - const getAttributesList = (mutationsList: MutationRecord[]) => { - return mutationsList.map((mutation) => mutation.attributeName!).filter((attributeName) => attributeName); + const getAttributesList = (mutationsList: MutationRecord[]): string[] => { + return mutationsList + .map((mutation) => mutation.attributeName) + .filter((attributeName) => attributeName) as string[]; }; const observer = new MutationObserver((mutationsList) => handler(getAttributesList(mutationsList))); - const targetNode = this.root_.querySelector(strings.INPUT_SELECTOR)!; const config = {attributes: true}; - observer.observe(targetNode, config); + observer.observe(this.input_, config); return observer; }, deregisterValidationAttributeChangeHandler: (observer) => observer.disconnect(), - isFocused: () => { - return document.activeElement === this.root_.querySelector(strings.INPUT_SELECTOR); - }, + isFocused: () => document.activeElement === this.input_, // tslint:enable:object-literal-sort-keys }), ...this.getInputAdapterMethods_(), From 5af3c648674a50f8f212c61a04f5b81d162d32f8 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 15:15:41 -0800 Subject: [PATCH 13/33] WIP: Remove unnecessary types --- packages/mdc-textfield/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 60f9e189c01..b6e370eb989 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -122,10 +122,10 @@ class MDCTextField extends MDCComponent implements Rippl ...({ // tslint:disable:object-literal-sort-keys isSurfaceActive: () => ponyfill.matches(this.input_, ':active'), - registerInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + registerInteractionHandler: (evtType, handler) => { return this.input_.addEventListener(evtType, handler); }, - deregisterInteractionHandler: (evtType: E, handler: SpecificEventListener) => { + deregisterInteractionHandler: (evtType, handler) => { return this.input_.removeEventListener(evtType, handler); }, // tslint:enable:object-literal-sort-keys From f30a1d7865a711119ead827323122550febea684 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 15:35:09 -0800 Subject: [PATCH 14/33] WIP: Increase foundation unit test coverage to 99% --- test/unit/mdc-textfield/foundation.test.js | 118 ++++++++++++++------- 1 file changed, 78 insertions(+), 40 deletions(-) diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 55924ef0a9b..6958b0adf1a 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -56,32 +56,37 @@ test('defaultAdapter returns a complete adapter implementation', () => { ]); }); -const setupTest = ({characterCounter = false} = {}) => { +const setupTest = ({ + useCharacterCounter = false, + useHelperText = false, + useLeadingIcon = false, + useTrailingIcon = false, +} = {}) => { const mockAdapter = td.object(MDCTextFieldFoundation.defaultAdapter); - const helperText = td.object({ + const helperText = useHelperText === true ? td.object({ setContent: () => {}, showToScreenReader: () => {}, setValidity: () => {}, - }); - characterCounter = characterCounter === true ? td.object({ + }) : undefined; + const characterCounter = useCharacterCounter === true ? td.object({ setCounterValue: () => {}, }) : undefined; - const leadingIcon = td.object({ + const leadingIcon = useLeadingIcon === true ? td.object({ setDisabled: () => {}, setAriaLabel: () => {}, setContent: () => {}, registerInteractionHandler: () => {}, deregisterInteractionHandler: () => {}, handleInteraction: () => {}, - }); - const trailingIcon = td.object({ + }) : undefined; + const trailingIcon = useTrailingIcon === true ? td.object({ setDisabled: () => {}, setAriaLabel: () => {}, setContent: () => {}, registerInteractionHandler: () => {}, deregisterInteractionHandler: () => {}, handleInteraction: () => {}, - }); + }) : undefined; const foundationMap = { helperText, characterCounter, @@ -97,13 +102,24 @@ test('#constructor sets disabled to false', () => { assert.isNotOk(foundation.isDisabled()); }); -const setupValueTest = (value, optIsValid, optIsBadInput, hasLabel) => { - const {foundation, mockAdapter, helperText} = setupTest(); +const setupValueTest = ({ + value, + optIsValid, + optIsBadInput, + hasLabel, + useCharacterCounter = false, + useHelperText = false, + useLeadingIcon = false, + useTrailingIcon = false, +} = {}) => { + const {foundation, mockAdapter, helperText} = setupTest({ + useCharacterCounter, useHelperText, useLeadingIcon, useTrailingIcon, + }); const nativeInput = { value: value, validity: { - valid: optIsValid === undefined ? true : !!optIsValid, - badInput: optIsBadInput === undefined ? false : !!optIsBadInput, + valid: optIsValid === undefined ? true : Boolean(optIsValid), + badInput: optIsBadInput === undefined ? false : Boolean(optIsBadInput), }, }; if (hasLabel) { @@ -126,7 +142,7 @@ test('#getValue returns the field\'s value', () => { test('#setValue with non-empty value styles the label', () => { const value = 'new value'; - const {foundation, nativeInput, mockAdapter} = setupValueTest('', undefined, undefined, true); + const {foundation, nativeInput, mockAdapter} = setupValueTest({value: '', hasLabel: true}); // Initial empty value should not float label. td.verify(mockAdapter.floatLabel(false), {times: 0}); nativeInput.value = value; @@ -136,7 +152,7 @@ test('#setValue with non-empty value styles the label', () => { }); test('#setValue with empty value styles the label', () => { - const {foundation, nativeInput, mockAdapter} = setupValueTest('old value', undefined, undefined, true); + const {foundation, nativeInput, mockAdapter} = setupValueTest({value: 'old value', hasLabel: true}); // Initial value should float the label. td.verify(mockAdapter.floatLabel(true)); nativeInput.value = ''; @@ -147,7 +163,7 @@ test('#setValue with empty value styles the label', () => { test('#setValue valid and invalid input', () => { const {foundation, mockAdapter, nativeInput, helperText} = - setupValueTest('', /* isValid */ false, undefined, true); + setupValueTest({value: '', optIsValid: false, hasLabel: true, useHelperText: true}); foundation.setValue('invalid'); td.verify(mockAdapter.addClass(cssClasses.INVALID)); @@ -165,7 +181,7 @@ test('#setValue valid and invalid input', () => { test('#setValue with invalid status and empty value does not shake the label', () => { const {foundation, mockAdapter, helperText} = - setupValueTest('', /* isValid */ false, undefined, true); + setupValueTest({value: '', optIsValid: false, hasLabel: true, useHelperText: true}); foundation.setValue(''); td.verify(mockAdapter.addClass(cssClasses.INVALID)); @@ -175,14 +191,14 @@ test('#setValue with invalid status and empty value does not shake the label', ( }); test('#setValue does not affect focused state', () => { - const {foundation, mockAdapter} = setupValueTest(''); + const {foundation, mockAdapter} = setupValueTest({value: ''}); foundation.setValue(''); td.verify(mockAdapter.addClass(cssClasses.FOCUSED), {times: 0}); td.verify(mockAdapter.removeClass(cssClasses.FOCUSED), {times: 0}); }); test('#setValue does not affect disabled state', () => { - const {foundation, mockAdapter} = setupValueTest(''); + const {foundation, mockAdapter} = setupValueTest({value: ''}); foundation.setValue(''); td.verify(mockAdapter.addClass(cssClasses.DISABLED), {times: 0}); td.verify(mockAdapter.removeClass(cssClasses.DISABLED), {times: 0}); @@ -191,7 +207,7 @@ test('#setValue does not affect disabled state', () => { }); test('#isValid for native validation', () => { - const {foundation, nativeInput} = setupValueTest('', /* isValid */ true); + const {foundation, nativeInput} = setupValueTest({value: '', optIsValid: true}); assert.isOk(foundation.isValid()); nativeInput.validity.valid = false; @@ -199,7 +215,7 @@ test('#isValid for native validation', () => { }); test('#setValid overrides native validation when useNativeValidation set to false', () => { - const {foundation, nativeInput} = setupValueTest('', /* isValid */ false); + const {foundation, nativeInput} = setupValueTest({value: '', optIsValid: false}); foundation.setUseNativeValidation(false); foundation.setValid(true); assert.isOk(foundation.isValid()); @@ -210,7 +226,7 @@ test('#setValid overrides native validation when useNativeValidation set to fals }); test('#setValid updates classes', () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText} = setupTest({useHelperText: true}); td.when(mockAdapter.hasLabel()).thenReturn(true); foundation.setValid(false); @@ -231,7 +247,7 @@ test('#setValid updates classes', () => { }); test('#setValid updates classes, but not label methods when hasLabel is false', () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText} = setupTest({useHelperText: true}); foundation.setValid(false); td.verify(mockAdapter.addClass(cssClasses.INVALID)); @@ -296,13 +312,13 @@ test('#setDisabled removes mdc-text-field--disabled when set to false', () => { }); test('#setDisabled sets disabled on leading icon', () => { - const {foundation, leadingIcon} = setupTest(); + const {foundation, leadingIcon} = setupTest({useLeadingIcon: true}); foundation.setDisabled(true); td.verify(leadingIcon.setDisabled(true)); }); test('#setDisabled sets disabled on trailing icon', () => { - const {foundation, trailingIcon} = setupTest(); + const {foundation, trailingIcon} = setupTest({useTrailingIcon: true}); foundation.setDisabled(true); td.verify(trailingIcon.setDisabled(true)); }); @@ -322,15 +338,17 @@ test('#setValid removes mdc-textfied--invalid when set to true', () => { test('#init focuses on input if adapter.isFocused is true', () => { const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.isFocused()).thenReturn(true); + foundation.activateFocus = td.func('activateFocus'); foundation.init(); - td.verify(foundation.inputFocusHandler_()); + td.verify(foundation.activateFocus(), {times: 1}); }); test('#init does not focus if adapter.isFocused is false', () => { const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.isFocused()).thenReturn(false); foundation.init(); - td.verify(foundation.inputFocusHandler_(), {times: 0}); + foundation.activateFocus = td.func('activateFocus'); + td.verify(foundation.activateFocus(), {times: 0}); }); test('#init adds event listeners', () => { @@ -376,7 +394,7 @@ test('#init floats label if the input contains a value', () => { td.verify(mockAdapter.floatLabel(true)); }); -test('#init doesnot call floatLabel if there is no label and the input contains a value', () => { +test('#init does not call floatLabel if there is no label and the input contains a value', () => { const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.getNativeInput()).thenReturn({ value: 'Pre-filled value', @@ -403,35 +421,55 @@ test('#init does not float label if the input does not contain a value', () => { }); test('#setHelperTextContent sets the content of the helper text element', () => { - const {foundation, helperText} = setupTest(); + const {foundation, helperText} = setupTest({useHelperText: true}); foundation.setHelperTextContent('foo'); td.verify(helperText.setContent('foo')); }); test('#setLeadingIconAriaLabel sets the aria-label of the leading icon element', () => { - const {foundation, leadingIcon} = setupTest(); + const {foundation, leadingIcon} = setupTest({useLeadingIcon: true}); foundation.setLeadingIconAriaLabel('foo'); td.verify(leadingIcon.setAriaLabel('foo')); }); +test('#setLeadingIconAriaLabel does nothing if element is not present', () => { + const {foundation} = setupTest({useLeadingIcon: false}); + assert.doesNotThrow(() => foundation.setLeadingIconAriaLabel('foo')); +}); + test('#setLeadingIconContent sets the content of the leading icon element', () => { - const {foundation, leadingIcon} = setupTest(); + const {foundation, leadingIcon} = setupTest({useLeadingIcon: true}); foundation.setLeadingIconContent('foo'); td.verify(leadingIcon.setContent('foo')); }); +test('#setLeadingIconContent sets does nothing if element is not present', () => { + const {foundation} = setupTest({useLeadingIcon: false}); + assert.doesNotThrow(() => foundation.setLeadingIconContent('foo')); +}); + test('#setTrailingIconAriaLabel sets the aria-label of the trailing icon element', () => { - const {foundation, trailingIcon} = setupTest(); + const {foundation, trailingIcon} = setupTest({useTrailingIcon: true}); foundation.setTrailingIconAriaLabel('foo'); td.verify(trailingIcon.setAriaLabel('foo')); }); +test('#setTrailingIconAriaLabel does nothing if element is not present', () => { + const {foundation} = setupTest({useTrailingIcon: false}); + assert.doesNotThrow(() => foundation.setTrailingIconAriaLabel('foo')); +}); + test('#setTrailingIconContent sets the content of the trailing icon element', () => { - const {foundation, trailingIcon} = setupTest(); + const {foundation, trailingIcon} = setupTest({useTrailingIcon: true}); foundation.setTrailingIconContent('foo'); td.verify(trailingIcon.setContent('foo')); }); +test('#setTrailingIconContent sets does nothing if element is not present', () => { + const {foundation} = setupTest({useTrailingIcon: false}); + assert.doesNotThrow(() => foundation.setTrailingIconContent('foo')); +}); + test('#notchOutline updates the width of the outline element', () => { const {foundation, mockAdapter} = setupTest(); td.when(mockAdapter.getLabelWidth()).thenReturn(LABEL_WIDTH); @@ -593,7 +631,7 @@ test('on focus do not styles label if hasLabel is false', () => { }); test('on focus makes helper text visible to the screen reader', () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText} = setupTest({useHelperText: true}); let focus; td.when(mockAdapter.registerInputInteractionHandler('focus', td.matchers.isA(Function))) .thenDo((evtType, handler) => { @@ -605,7 +643,7 @@ test('on focus makes helper text visible to the screen reader', () => { }); const setupBlurTest = () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText} = setupTest({useHelperText: true}); let blur; td.when(mockAdapter.registerInputInteractionHandler('blur', td.matchers.isA(Function))).thenDo((evtType, handler) => { blur = handler; @@ -804,7 +842,7 @@ test('touchstart on the input sets the line ripple origin', () => { }); test('on validation attribute change calls styleValidity_', () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText} = setupTest({useHelperText: true}); let attributeChange; td.when(mockAdapter.registerValidationAttributeChangeHandler(td.matchers.isA(Function))) .thenDo((handler) => attributeChange = handler); @@ -821,7 +859,7 @@ test('on validation attribute change calls styleValidity_', () => { }); test('should not call styleValidity_ on non-whitelisted attribute change', () => { - const {foundation, mockAdapter, helperText} = setupTest(); + const {foundation, mockAdapter, helperText} = setupTest({useHelperText: true}); let attributeChange; td.when(mockAdapter.registerValidationAttributeChangeHandler(td.matchers.isA(Function))) .thenDo((handler) => attributeChange = handler); @@ -838,7 +876,7 @@ test('should not call styleValidity_ on non-whitelisted attribute change', () => }); test('label floats on invalid input even if value is empty', () => { - const {mockAdapter} = setupValueTest('', false, true, true); + const {mockAdapter} = setupValueTest({value: '', optIsValid: false, optIsBadInput: true, hasLabel: true}); td.verify(mockAdapter.floatLabel(true)); }); @@ -866,7 +904,7 @@ test('#handleInput activates focus state', () => { }); test('#handleInput updates character counter on text input', () => { - const {foundation, mockAdapter, characterCounter} = setupTest({characterCounter: true}); + const {foundation, mockAdapter, characterCounter} = setupTest({useCharacterCounter: true}); const nativeInput = { type: 'text', @@ -884,7 +922,7 @@ test('#handleInput updates character counter on text input', () => { test('#handleInput throws error when maxLength HTML attribute is not found in input element', () => { - const {foundation, mockAdapter} = setupTest({characterCounter: true}); + const {foundation, mockAdapter} = setupTest({useCharacterCounter: true}); const nativeInput = { type: 'text', @@ -901,7 +939,7 @@ test('#handleInput throws error when maxLength HTML attribute is not found in in test('#handleValidationAttributeChange sets character counter when maxlength attribute value is changed in input ' + 'element', () => { - const {foundation, mockAdapter, characterCounter} = setupTest({characterCounter: true}); + const {foundation, mockAdapter, characterCounter} = setupTest({useCharacterCounter: true}); const nativeInput = { type: 'text', From fb19ebad5c28b56e0306f89d56baf68ccceedd22 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 16:40:46 -0800 Subject: [PATCH 15/33] WIP: Add missing `private` visibility modifiers --- packages/mdc-textfield/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index b6e370eb989..6e6efe36373 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -374,7 +374,7 @@ class MDCTextField extends MDCComponent implements Rippl }); } - getLabelAdapterMethods_() { + private getLabelAdapterMethods_() { return { floatLabel: (shouldFloat: boolean) => this.label_ && this.label_.float(shouldFloat), getLabelWidth: () => this.label_ ? this.label_.getWidth() : 0, @@ -383,7 +383,7 @@ class MDCTextField extends MDCComponent implements Rippl }; } - getLineRippleAdapterMethods_() { + private getLineRippleAdapterMethods_() { return { activateLineRipple: () => { if (this.lineRipple_) { @@ -403,7 +403,7 @@ class MDCTextField extends MDCComponent implements Rippl }; } - getOutlineAdapterMethods_() { + private getOutlineAdapterMethods_() { return { closeOutline: () => this.outline_ && this.outline_.closeNotch(), hasOutline: () => Boolean(this.outline_), @@ -411,7 +411,7 @@ class MDCTextField extends MDCComponent implements Rippl }; } - getInputAdapterMethods_() { + private getInputAdapterMethods_() { // tslint:disable:object-literal-sort-keys return { registerInputInteractionHandler: (evtType: E, handler: SpecificEventListener) => { @@ -428,7 +428,7 @@ class MDCTextField extends MDCComponent implements Rippl /** * @return A map of all subcomponents to subfoundations. */ - getFoundationMap_(): Partial { + private getFoundationMap_(): Partial { return { characterCounter: this.characterCounter_ ? this.characterCounter_.foundation : undefined, helperText: this.helperText_ ? this.helperText_.foundation : undefined, From d3a4efa0ebfa8b4b372649e5805c7420cbdf71d5 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 16:54:26 -0800 Subject: [PATCH 16/33] WIP: Increase unit test coverage to 99% --- ...field-character-counter-foundation.test.js | 6 +- ...-text-field-helper-text-foundation.test.js | 6 +- .../mdc-text-field-icon-foundation.test.js | 9 ++- .../unit/mdc-textfield/mdc-text-field.test.js | 77 ++++++++++++++++++- 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js index 0025a472847..a91f687eb28 100644 --- a/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js @@ -53,7 +53,9 @@ test('#setContent sets the content of the character counter element', () => { }); test('#setContent current length does not exceed character count limit', () => { - const {foundation, mockAdapter} = setupTest(); + const foundation = new MDCTextFieldCharacterCounterFoundation(); + const adapter = foundation.adapter_; + adapter.setContent = td.func('setContent'); foundation.setCounterValue(24, 20); - td.verify(mockAdapter.setContent('20 / 20')); + td.verify(adapter.setContent('20 / 20')); }); diff --git a/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js index a4560fd79b8..1d4509b4b1b 100644 --- a/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js @@ -49,9 +49,11 @@ test('defaultAdapter returns a complete adapter implementation', () => { const setupTest = () => setupFoundationTest(MDCTextFieldHelperTextFoundation); test('#setContent sets the content of the helper text element', () => { - const {foundation, mockAdapter} = setupTest(); + const foundation = new MDCTextFieldHelperTextFoundation(); + const adapter = foundation.adapter_; + adapter.setContent = td.func('setContent'); foundation.setContent('foo'); - td.verify(mockAdapter.setContent('foo')); + td.verify(adapter.setContent('foo')); }); test('#setPersistent toggles the persistent class', () => { diff --git a/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js index 4bcaa7ba49a..409bd515dad 100644 --- a/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js @@ -45,11 +45,14 @@ test('defaultAdapter returns a complete adapter implementation', () => { const setupTest = () => setupFoundationTest(MDCTextFieldIconFoundation); test('#init adds event listeners', () => { - const {foundation, mockAdapter} = setupTest(); + const foundation = new MDCTextFieldIconFoundation(); + const adapter = foundation.adapter_; + adapter.registerInteractionHandler = td.func('registerInteractionHandler'); + foundation.init(); - td.verify(mockAdapter.registerInteractionHandler('click', td.matchers.isA(Function))); - td.verify(mockAdapter.registerInteractionHandler('keydown', td.matchers.isA(Function))); + td.verify(adapter.registerInteractionHandler('click', td.matchers.isA(Function))); + td.verify(adapter.registerInteractionHandler('keydown', td.matchers.isA(Function))); }); test('#destroy removes event listeners', () => { diff --git a/test/unit/mdc-textfield/mdc-text-field.test.js b/test/unit/mdc-textfield/mdc-text-field.test.js index 435aa5d0a7b..ccb3a353fcf 100644 --- a/test/unit/mdc-textfield/mdc-text-field.test.js +++ b/test/unit/mdc-textfield/mdc-text-field.test.js @@ -219,6 +219,50 @@ test('#constructor handles undefined optional sub-elements gracefully', () => { assert.doesNotThrow(() => new MDCTextField(root)); }); +test('default adapter methods handle sub-elements when present', () => { + const root = getFixture(); + const component = new MDCTextField(root); + const adapter = component.getDefaultFoundation().adapter_; + assert.isFalse(adapter.hasClass('foo')); + assert.equal(adapter.getLabelWidth(), 0); + assert.doesNotThrow(() => adapter.floatLabel(true)); +}); + +test('default adapter methods handle undefined optional sub-elements gracefully', () => { + const root = bel` +
+ +
+ `; + const component = new MDCTextField(root); + const adapter = component.getDefaultFoundation().adapter_; + assert.equal(adapter.getLabelWidth(), 0); + assert.isFalse(adapter.hasLabel()); + assert.isFalse(adapter.hasOutline()); + assert.doesNotThrow(() => adapter.floatLabel(true)); + assert.doesNotThrow(() => adapter.shakeLabel(true)); + assert.doesNotThrow(() => adapter.activateLineRipple()); + assert.doesNotThrow(() => adapter.deactivateLineRipple()); + assert.doesNotThrow(() => adapter.setLineRippleTransformOrigin(0)); + assert.doesNotThrow(() => adapter.closeOutline()); + assert.doesNotThrow(() => adapter.notchOutline(0)); +}); + +/** + * @param {!HTMLElement=} root + * @return {{ + * root: HTMLElement, + * component: MDCTextField, + * foundation: MDCTextFieldFoundation, + * adapter: MDCTextFieldAdapter, + * outline: MDCNotchedOutline, + * icon: MDCTextFieldIcon, + * lineRipple: MDCLineRipple, + * label: MDCFloatingLabel, + * helperText: MDCTextFieldHelperText, + * characterCounter: MDCTextFieldCharacterCounter, + * }} + */ function setupTest(root = getFixture()) { const lineRipple = new FakeLineRipple(); const helperText = new FakeHelperText(); @@ -226,6 +270,7 @@ function setupTest(root = getFixture()) { const icon = new FakeIcon(); const label = new FakeLabel(); const outline = new FakeOutline(); + const component = new MDCTextField( root, undefined, @@ -237,7 +282,11 @@ function setupTest(root = getFixture()) { () => label, () => outline ); - return {root, component, lineRipple, helperText, characterCounter, icon, label, outline}; + + const foundation = component.foundation_; + const adapter = foundation.adapter_; + + return {root, component, foundation, adapter, lineRipple, helperText, characterCounter, icon, label, outline}; } test('#destroy cleans up the ripple if present', () => { @@ -308,6 +357,25 @@ test('#destroy handles undefined optional sub-elements gracefully', () => { assert.doesNotThrow(() => component.destroy()); }); +test('#destroy handles undefined optional ripple gracefully', () => { + const root = getFixture(); + const component = new MDCTextField(root); + component.ripple = null; + assert.doesNotThrow(() => component.destroy()); +}); + +test('#destroy calls destroy for both icon elements if present', () => { + const root = getFixture(true); + root.classList.add('mdc-text-field--with-trailing-icon'); + root.appendChild(bel`3d_rotations`); + const component = new MDCTextField(root); + component.leadingIcon_.destroy = td.func('leadingIcon_.destroy'); + component.trailingIcon_.destroy = td.func('trailingIcon_.destroy'); + component.destroy(); + td.verify(component.leadingIcon_.destroy()); + td.verify(component.trailingIcon_.destroy()); +}); + test('#initialSyncWithDom sets disabled if input element is not disabled', () => { const {component} = setupTest(); component.initialSyncWithDom(); @@ -375,6 +443,13 @@ test('#adapter.addClass adds a class to the root element', () => { assert.isOk(root.classList.contains('foo')); }); +test('layout calls foundation notchOutline', () => { + const {component, foundation} = setupTest(); + foundation.notchOutline = td.func('notchOutline'); + component.layout(); + td.verify(foundation.notchOutline(false)); +}); + test('#adapter.removeClass removes a class from the root element', () => { const {root, component} = setupTest(); root.classList.add('foo'); From 1c12f8f16c1d60d9418cf44e9a04b0bf3bf1fd3e Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 17:01:34 -0800 Subject: [PATCH 17/33] WIP: Style --- packages/mdc-textfield/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 6e6efe36373..2c0d85313ba 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -53,9 +53,12 @@ class MDCTextField extends MDCComponent implements Rippl root_!: HTMLElement; // assigned in MDCComponent constructor ripple!: MDCRipple | null; // assigned in initialize() + // The only required sub-element. + private input_!: HTMLInputElement; // assigned in initialize() + + // Optional sub-elements. private characterCounter_!: MDCTextFieldCharacterCounter | null; // assigned in initialize() private helperText_!: MDCTextFieldHelperText | null; // assigned in initialize() - private input_!: HTMLInputElement; // assigned in initialize() private label_!: MDCFloatingLabel | null; // assigned in initialize() private leadingIcon_!: MDCTextFieldIcon | null; // assigned in initialize() private lineRipple_!: MDCLineRipple | null; // assigned in initialize() From 9fa0cc4bd4a80737dcaf8c9af0624bb67d0561ca Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 17:09:23 -0800 Subject: [PATCH 18/33] WIP: Fix broken sub-component integration and screenshot test for character counter --- packages/mdc-textfield/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 2c0d85313ba..469bef0c75d 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -373,8 +373,7 @@ class MDCTextField extends MDCComponent implements Rippl ...this.getLabelAdapterMethods_(), ...this.getLineRippleAdapterMethods_(), ...this.getOutlineAdapterMethods_(), - ...this.getFoundationMap_(), - }); + }, this.getFoundationMap_()); } private getLabelAdapterMethods_() { From 5a07c7472b2d9b3fbcd31fa3b4e296e1eaffa69c Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 21:41:41 -0800 Subject: [PATCH 19/33] WIP: Temporarily debug failing unit test in IE 11 --- packages/mdc-textfield/foundation.ts | 6 +++--- packages/mdc-textfield/index.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 7873835a2ce..9df06f484b1 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -217,7 +217,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.styleFocused_(this.isFocused_); this.adapter_.activateLineRipple(); if (this.adapter_.hasLabel()) { - this.notchOutline(this.shouldFloat); + this.notchOutline(this.shouldFloat || (2 as unknown as boolean)); this.adapter_.floatLabel(this.shouldFloat); this.adapter_.shakeLabel(this.shouldShake); } @@ -266,7 +266,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.styleValidity_(isValid); this.styleFocused_(this.isFocused_); if (this.adapter_.hasLabel()) { - this.notchOutline(this.shouldFloat); + this.notchOutline(this.shouldFloat || (3 as unknown as boolean)); this.adapter_.floatLabel(this.shouldFloat); this.adapter_.shakeLabel(this.shouldShake); } @@ -290,7 +290,7 @@ class MDCTextFieldFoundation extends MDCFoundation { const isValid = this.isValid(); this.styleValidity_(isValid); if (this.adapter_.hasLabel()) { - this.notchOutline(this.shouldFloat); + this.notchOutline(this.shouldFloat || (4 as unknown as boolean)); this.adapter_.floatLabel(this.shouldFloat); this.adapter_.shakeLabel(this.shouldShake); } diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 469bef0c75d..bcc2468a3e5 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -342,7 +342,7 @@ class MDCTextField extends MDCComponent implements Rippl */ layout() { const openNotch = this.foundation_.shouldFloat; - this.foundation_.notchOutline(openNotch); + this.foundation_.notchOutline(openNotch || (1 as unknown as boolean)); } getDefaultFoundation(): MDCTextFieldFoundation { From c3a4977dcf98302768692c6b0523b7204c06118f Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 23:39:38 -0800 Subject: [PATCH 20/33] WIP: Fix failing unit test in IE 11? --- packages/mdc-textfield/foundation.ts | 9 +++++---- packages/mdc-textfield/index.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 9df06f484b1..e9accf23a62 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -217,7 +217,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.styleFocused_(this.isFocused_); this.adapter_.activateLineRipple(); if (this.adapter_.hasLabel()) { - this.notchOutline(this.shouldFloat || (2 as unknown as boolean)); + this.notchOutline(this.shouldFloat); this.adapter_.floatLabel(this.shouldFloat); this.adapter_.shakeLabel(this.shouldShake); } @@ -266,7 +266,7 @@ class MDCTextFieldFoundation extends MDCFoundation { this.styleValidity_(isValid); this.styleFocused_(this.isFocused_); if (this.adapter_.hasLabel()) { - this.notchOutline(this.shouldFloat || (3 as unknown as boolean)); + this.notchOutline(this.shouldFloat); this.adapter_.floatLabel(this.shouldFloat); this.adapter_.shakeLabel(this.shouldShake); } @@ -290,7 +290,7 @@ class MDCTextFieldFoundation extends MDCFoundation { const isValid = this.isValid(); this.styleValidity_(isValid); if (this.adapter_.hasLabel()) { - this.notchOutline(this.shouldFloat || (4 as unknown as boolean)); + this.notchOutline(this.shouldFloat); this.adapter_.floatLabel(this.shouldFloat); this.adapter_.shakeLabel(this.shouldShake); } @@ -400,7 +400,8 @@ class MDCTextFieldFoundation extends MDCFoundation { * @return True if the Text Field input fails in converting the user-supplied value. */ private isBadInput_(): boolean { - return this.getNativeInput_().validity.badInput; + // The trailing `|| false` is needed for unit tests to pass in IE 11. + return this.getNativeInput_().validity.badInput || false; } /** diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index bcc2468a3e5..e90544a0488 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -342,7 +342,7 @@ class MDCTextField extends MDCComponent implements Rippl */ layout() { const openNotch = this.foundation_.shouldFloat; - this.foundation_.notchOutline(openNotch || (1 as unknown as boolean)); + this.foundation_.notchOutline(openNotch || (99 as unknown as boolean)); } getDefaultFoundation(): MDCTextFieldFoundation { From 059e1a5042f4cc3b6d2b1d728523ece642545514 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 23:49:34 -0800 Subject: [PATCH 21/33] WIP: Fix IE 11? --- packages/mdc-textfield/foundation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index e9accf23a62..3ecadda091b 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -56,7 +56,8 @@ class MDCTextFieldFoundation extends MDCFoundation { } get shouldFloat(): boolean { - return this.shouldAlwaysFloat_ || this.isFocused_ || Boolean(this.getValue()) || this.isBadInput_(); + // The trailing `|| false` is needed for unit tests to pass in IE 11. + return this.shouldAlwaysFloat_ || this.isFocused_ || Boolean(this.getValue()) || this.isBadInput_() || false; } /** @@ -400,8 +401,7 @@ class MDCTextFieldFoundation extends MDCFoundation { * @return True if the Text Field input fails in converting the user-supplied value. */ private isBadInput_(): boolean { - // The trailing `|| false` is needed for unit tests to pass in IE 11. - return this.getNativeInput_().validity.badInput || false; + return this.getNativeInput_().validity.badInput; } /** From b6b68a65e8611c9fec2cb179e4c52121c8762006 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Fri, 8 Feb 2019 23:51:59 -0800 Subject: [PATCH 22/33] WIP: Fix IE 11? --- packages/mdc-textfield/foundation.ts | 3 +-- packages/mdc-textfield/index.ts | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 3ecadda091b..7873835a2ce 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -56,8 +56,7 @@ class MDCTextFieldFoundation extends MDCFoundation { } get shouldFloat(): boolean { - // The trailing `|| false` is needed for unit tests to pass in IE 11. - return this.shouldAlwaysFloat_ || this.isFocused_ || Boolean(this.getValue()) || this.isBadInput_() || false; + return this.shouldAlwaysFloat_ || this.isFocused_ || Boolean(this.getValue()) || this.isBadInput_(); } /** diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index e90544a0488..eca4ecb6bf5 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -341,8 +341,9 @@ class MDCTextField extends MDCComponent implements Rippl * Recomputes the outline SVG path for the outline element. */ layout() { - const openNotch = this.foundation_.shouldFloat; - this.foundation_.notchOutline(openNotch || (99 as unknown as boolean)); + // The trailing `|| false` is needed for a unit test to pass in IE 11. + const openNotch = this.foundation_.shouldFloat || false; + this.foundation_.notchOutline(openNotch); } getDefaultFoundation(): MDCTextFieldFoundation { From 8abd45a0b73a24fd9a913a68243e98cada523c9c Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Sat, 9 Feb 2019 00:03:07 -0800 Subject: [PATCH 23/33] WIP: Fix IE 11! --- packages/mdc-textfield/foundation.ts | 3 ++- packages/mdc-textfield/index.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 7873835a2ce..857f8ddfe6d 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -400,7 +400,8 @@ class MDCTextFieldFoundation extends MDCFoundation { * @return True if the Text Field input fails in converting the user-supplied value. */ private isBadInput_(): boolean { - return this.getNativeInput_().validity.badInput; + // The badInput property is not supported in IE 11 💩. + return this.getNativeInput_().validity.badInput || false; } /** diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index eca4ecb6bf5..469bef0c75d 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -341,8 +341,7 @@ class MDCTextField extends MDCComponent implements Rippl * Recomputes the outline SVG path for the outline element. */ layout() { - // The trailing `|| false` is needed for a unit test to pass in IE 11. - const openNotch = this.foundation_.shouldFloat || false; + const openNotch = this.foundation_.shouldFloat; this.foundation_.notchOutline(openNotch); } From fbf3ad3eb408a2416787386f4b22239747ac9ac3 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Sat, 9 Feb 2019 13:14:28 -0800 Subject: [PATCH 24/33] WIP: Make Istanbul happy --- packages/mdc-textfield/character-counter/foundation.ts | 2 +- packages/mdc-textfield/helper-text/foundation.ts | 2 +- packages/mdc-textfield/icon/foundation.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mdc-textfield/character-counter/foundation.ts b/packages/mdc-textfield/character-counter/foundation.ts index 0e47aef0f91..a9c7f8058ae 100644 --- a/packages/mdc-textfield/character-counter/foundation.ts +++ b/packages/mdc-textfield/character-counter/foundation.ts @@ -43,7 +43,7 @@ class MDCTextFieldCharacterCounterFoundation extends MDCFoundation = {}) { + constructor(adapter?: Partial) { super({...MDCTextFieldCharacterCounterFoundation.defaultAdapter, ...adapter}); } diff --git a/packages/mdc-textfield/helper-text/foundation.ts b/packages/mdc-textfield/helper-text/foundation.ts index aa2ce690b4a..291b173bbd5 100644 --- a/packages/mdc-textfield/helper-text/foundation.ts +++ b/packages/mdc-textfield/helper-text/foundation.ts @@ -50,7 +50,7 @@ class MDCTextFieldHelperTextFoundation extends MDCFoundation = {}) { + constructor(adapter?: Partial) { super({...MDCTextFieldHelperTextFoundation.defaultAdapter, ...adapter}); } diff --git a/packages/mdc-textfield/icon/foundation.ts b/packages/mdc-textfield/icon/foundation.ts index b8a9dbc6bb0..fe25a786703 100644 --- a/packages/mdc-textfield/icon/foundation.ts +++ b/packages/mdc-textfield/icon/foundation.ts @@ -53,7 +53,7 @@ class MDCTextFieldIconFoundation extends MDCFoundation private savedTabIndex_: string | null; private readonly interactionHandler_: EventListener; - constructor(adapter: Partial = {}) { + constructor(adapter?: Partial) { super({...MDCTextFieldIconFoundation.defaultAdapter, ...adapter}); this.savedTabIndex_ = null; From d3ee22d1ec50162665db31e6943024d57587b130 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Mon, 11 Feb 2019 17:19:56 -0800 Subject: [PATCH 25/33] WIP: Address review comments --- packages/mdc-textfield/README.md | 2 +- packages/mdc-textfield/adapter.ts | 4 +-- packages/mdc-textfield/foundation.ts | 47 +++++++++++++++------------- packages/mdc-textfield/types.ts | 4 +-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/packages/mdc-textfield/README.md b/packages/mdc-textfield/README.md index 45a84f0a351..67a5a74ddfa 100644 --- a/packages/mdc-textfield/README.md +++ b/packages/mdc-textfield/README.md @@ -376,7 +376,7 @@ Method Signature | Description `deregisterInputInteractionHandler(evtType: string, handler: EventListener) => void` | Deregisters an event listener on the native input element for a given event. `registerValidationAttributeChangeHandler(handler: (attributeNames: string[]) => void) => MutationObserver` | Registers a validation attribute change listener on the input element. Handler accepts list of attribute changes. `deregisterValidationAttributeChangeHandler(!MutationObserver) => void` | Disconnects a validation attribute observer on the input element. -`getNativeInput() => HTMLInputElement \| NativeInputType` | Returns an object representing the native text input element, with a similar API shape. +`getNativeInput() => NativeInputType \| null` | Returns an object representing the native text input element, with a similar API shape. See [types.ts](types.ts). `isFocused() => boolean` | Returns whether the input is focused. `hasOutline() => boolean` | Returns whether there is an outline element. `notchOutline(labelWidth: number) => void` | Updates the notched outline path to open the notch and update the notch width for the label element. diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index 4e69648807d..12ffc1bf853 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -22,7 +22,7 @@ */ import {EventType, SpecificEventListener} from '@material/base/index'; -import {NativeInputType} from './types'; +import {NativeInputElement} from './types'; /** * Defines the shape of the adapter expected by the foundation. @@ -85,7 +85,7 @@ interface MDCTextFieldAdapter { * 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. */ - getNativeInput(): HTMLInputElement | NativeInputType | null; + getNativeInput(): NativeInputElement | null; /** * @return true if the textfield is focused. We achieve this via `document.activeElement === this.root_`. diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index 857f8ddfe6d..baf58827f5c 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -22,16 +22,19 @@ */ import {MDCFoundation} from '@material/base/foundation'; -import {EventType} from '@material/base/index'; +import {SpecificEventListener} from '@material/base/types'; import {MDCTextFieldAdapter} from './adapter'; import {MDCTextFieldCharacterCounterFoundation} from './character-counter'; import {ALWAYS_FLOAT_TYPES, cssClasses, numbers, strings, VALIDATION_ATTR_WHITELIST} from './constants'; import {MDCTextFieldHelperTextFoundation} from './helper-text'; import {MDCTextFieldIconFoundation} from './icon'; -import {FoundationMapType, NativeInputType} from './types'; +import {FoundationMapType, NativeInputElement} from './types'; -const MOUSEDOWN_TOUCHSTART_EVENTS: EventType[] = ['mousedown', 'touchstart']; -const CLICK_KEYDOWN_EVENTS: EventType[] = ['click', 'keydown']; +type PointerDownEventType = 'mousedown' | 'touchstart'; +type InteractionEventType = 'click' | 'keydown'; + +const POINTERDOWN_EVENTS: PointerDownEventType[] = ['mousedown', 'touchstart']; +const INTERACTION_EVENTS: InteractionEventType[] = ['click', 'keydown']; class MDCTextFieldFoundation extends MDCFoundation { static get cssClasses() { @@ -96,17 +99,17 @@ class MDCTextFieldFoundation extends MDCFoundation { private useNativeValidation_ = true; private readonly inputFocusHandler_: () => void; - private readonly inputBlurHandler_: EventListener; - private readonly inputInputHandler_: EventListener; - private readonly setPointerXOffset_: EventListener; - private readonly textFieldInteractionHandler_: EventListener; + private readonly inputBlurHandler_: SpecificEventListener<'blur'>; + private readonly inputInputHandler_: SpecificEventListener<'input'>; + private readonly setPointerXOffset_: SpecificEventListener; + private readonly textFieldInteractionHandler_: SpecificEventListener; private readonly validationAttributeChangeHandler_: (attributesList: string[]) => void; private validationObserver_!: MutationObserver; // assigned in init() - private readonly helperText_: MDCTextFieldHelperTextFoundation | undefined; - private readonly characterCounter_: MDCTextFieldCharacterCounterFoundation | undefined; - private readonly leadingIcon_: MDCTextFieldIconFoundation | undefined; - private readonly trailingIcon_: MDCTextFieldIconFoundation | undefined; + private readonly helperText_?: MDCTextFieldHelperTextFoundation; + private readonly characterCounter_?: MDCTextFieldCharacterCounterFoundation; + private readonly leadingIcon_?: MDCTextFieldIconFoundation; + private readonly trailingIcon_?: MDCTextFieldIconFoundation; /** * @param adapter @@ -139,10 +142,10 @@ class MDCTextFieldFoundation extends MDCFoundation { this.adapter_.registerInputInteractionHandler('focus', this.inputFocusHandler_); this.adapter_.registerInputInteractionHandler('blur', this.inputBlurHandler_); this.adapter_.registerInputInteractionHandler('input', this.inputInputHandler_); - MOUSEDOWN_TOUCHSTART_EVENTS.forEach((evtType) => { + POINTERDOWN_EVENTS.forEach((evtType) => { this.adapter_.registerInputInteractionHandler(evtType, this.setPointerXOffset_); }); - CLICK_KEYDOWN_EVENTS.forEach((evtType) => { + INTERACTION_EVENTS.forEach((evtType) => { this.adapter_.registerTextFieldInteractionHandler(evtType, this.textFieldInteractionHandler_); }); this.validationObserver_ = @@ -154,10 +157,10 @@ class MDCTextFieldFoundation extends MDCFoundation { this.adapter_.deregisterInputInteractionHandler('focus', this.inputFocusHandler_); this.adapter_.deregisterInputInteractionHandler('blur', this.inputBlurHandler_); this.adapter_.deregisterInputInteractionHandler('input', this.inputInputHandler_); - MOUSEDOWN_TOUCHSTART_EVENTS.forEach((evtType) => { + POINTERDOWN_EVENTS.forEach((evtType) => { this.adapter_.deregisterInputInteractionHandler(evtType, this.setPointerXOffset_); }); - CLICK_KEYDOWN_EVENTS.forEach((evtType) => { + INTERACTION_EVENTS.forEach((evtType) => { this.adapter_.deregisterTextFieldInteractionHandler(evtType, this.textFieldInteractionHandler_); }); this.adapter_.deregisterValidationAttributeChangeHandler(this.validationObserver_); @@ -230,7 +233,7 @@ class MDCTextFieldFoundation extends MDCFoundation { * Sets the line ripple's transform origin, so that the line ripple activate * animation will animate out from the user's click location. */ - setTransformOrigin(evt: Event): void { + setTransformOrigin(evt: TouchEvent | MouseEvent): void { const touches = (evt as TouchEvent).touches; const targetEvent = touches ? touches[0] : evt; const targetClientRect = (targetEvent.target as Element).getBoundingClientRect(); @@ -460,12 +463,12 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @return The native text input from the host environment, or a dummy if none exists. + * @return The native text input from the host environment. */ - private getNativeInput_(): HTMLInputElement | NativeInputType { - // adapter_ can be undefined in foundation unit tests. This happens when testdouble is creating a mock object and - // invokes the shouldShake/shouldFloat getters (which in turn call getValue(), which calls this method) before - // init() has been called in the MDCTextField constructor. + private getNativeInput_(): NativeInputElement { + // this.adapter_ may be undefined in foundation unit tests. This happens when testdouble is creating a mock object + // and invokes the shouldShake/shouldFloat getters (which in turn call getValue(), which calls this method) before + // init() has been called from the MDCTextField constructor. To work around that issue, we return a dummy input. const nativeInput = this.adapter_ ? this.adapter_.getNativeInput() : null; return nativeInput || { disabled: false, diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts index b3f2e3316f7..39ccd6a3076 100644 --- a/packages/mdc-textfield/types.ts +++ b/packages/mdc-textfield/types.ts @@ -32,7 +32,7 @@ import {MDCTextFieldHelperText} from './helper-text/index'; import {MDCTextFieldIconFoundation} from './icon/foundation'; import {MDCTextFieldIcon} from './icon/index'; -export interface NativeInputType { +export type NativeInputElement = HTMLInputElement | { disabled: boolean; maxLength: number; type: string; @@ -41,7 +41,7 @@ export interface NativeInputType { valid: boolean; }; value: string; -} +}; export interface FoundationMapType { helperText: MDCTextFieldHelperTextFoundation; From 44f2987053d7b71055ef9229c71d178f3d183d43 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Mon, 11 Feb 2019 17:21:53 -0800 Subject: [PATCH 26/33] WIP: Address review comments --- packages/mdc-textfield/icon/foundation.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/mdc-textfield/icon/foundation.ts b/packages/mdc-textfield/icon/foundation.ts index fe25a786703..7a37ce195e1 100644 --- a/packages/mdc-textfield/icon/foundation.ts +++ b/packages/mdc-textfield/icon/foundation.ts @@ -22,11 +22,13 @@ */ import {MDCFoundation} from '@material/base/foundation'; -import {EventType} from '@material/base/index'; +import {SpecificEventListener} from '@material/base/types'; import {MDCTextFieldIconAdapter} from './adapter'; import {strings} from './constants'; -const CLICK_KEYDOWN_EVENTS: EventType[] = ['click', 'keydown']; +type InteractionEventType = 'click' | 'keydown'; + +const INTERACTION_EVENTS: InteractionEventType[] = ['click', 'keydown']; class MDCTextFieldIconFoundation extends MDCFoundation { static get strings() { @@ -51,7 +53,7 @@ class MDCTextFieldIconFoundation extends MDCFoundation } private savedTabIndex_: string | null; - private readonly interactionHandler_: EventListener; + private readonly interactionHandler_: SpecificEventListener; constructor(adapter?: Partial) { super({...MDCTextFieldIconFoundation.defaultAdapter, ...adapter}); @@ -63,13 +65,13 @@ class MDCTextFieldIconFoundation extends MDCFoundation init() { this.savedTabIndex_ = this.adapter_.getAttr('tabindex'); - CLICK_KEYDOWN_EVENTS.forEach((evtType) => { + INTERACTION_EVENTS.forEach((evtType) => { this.adapter_.registerInteractionHandler(evtType, this.interactionHandler_); }); } destroy() { - CLICK_KEYDOWN_EVENTS.forEach((evtType) => { + INTERACTION_EVENTS.forEach((evtType) => { this.adapter_.deregisterInteractionHandler(evtType, this.interactionHandler_); }); } @@ -96,7 +98,7 @@ class MDCTextFieldIconFoundation extends MDCFoundation this.adapter_.setContent(content); } - handleInteraction(evt: Event) { + handleInteraction(evt: MouseEvent | KeyboardEvent) { const isEnterKey = (evt as KeyboardEvent).key === 'Enter' || (evt as KeyboardEvent).keyCode === 13; if (evt.type === 'click' || isEnterKey) { this.adapter_.notifyIconAction(); From 2bc7221bc2b428164c0da7d3e16f67d712903d7a Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Mon, 11 Feb 2019 19:58:16 -0800 Subject: [PATCH 27/33] WIP: Import from specific `.ts` files instead of `/index` --- packages/mdc-textfield/adapter.ts | 2 +- packages/mdc-textfield/icon/adapter.ts | 2 +- packages/mdc-textfield/icon/index.ts | 2 +- packages/mdc-textfield/index.ts | 6 ++++-- packages/mdc-textfield/types.ts | 3 ++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index 12ffc1bf853..fd3293e900f 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {EventType, SpecificEventListener} from '@material/base/index'; +import {EventType, SpecificEventListener} from '@material/base/types'; import {NativeInputElement} from './types'; /** diff --git a/packages/mdc-textfield/icon/adapter.ts b/packages/mdc-textfield/icon/adapter.ts index e0059d34226..0516ed5a70f 100644 --- a/packages/mdc-textfield/icon/adapter.ts +++ b/packages/mdc-textfield/icon/adapter.ts @@ -21,7 +21,7 @@ * THE SOFTWARE. */ -import {EventType, SpecificEventListener} from '@material/base/index'; +import {EventType, SpecificEventListener} from '@material/base/types'; /** * Defines the shape of the adapter expected by the foundation. diff --git a/packages/mdc-textfield/icon/index.ts b/packages/mdc-textfield/icon/index.ts index cce3657f5b7..a27678bddd4 100644 --- a/packages/mdc-textfield/icon/index.ts +++ b/packages/mdc-textfield/icon/index.ts @@ -22,7 +22,7 @@ */ import {MDCComponent} from '@material/base/component'; -import {EventType, SpecificEventListener} from '@material/base/index'; +import {EventType, SpecificEventListener} from '@material/base/types'; import {MDCTextFieldIconFoundation} from './foundation'; class MDCTextFieldIcon extends MDCComponent { diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index 469bef0c75d..ebc1d33a7d5 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -22,12 +22,14 @@ */ import {MDCComponent} from '@material/base/component'; -import {EventType, SpecificEventListener} from '@material/base/index'; +import {EventType, SpecificEventListener} from '@material/base/types'; import * as ponyfill from '@material/dom/ponyfill'; import {MDCFloatingLabel} from '@material/floating-label/index'; import {MDCLineRipple} from '@material/line-ripple/index'; import {MDCNotchedOutline} from '@material/notched-outline/index'; -import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple/index'; +import {MDCRippleFoundation} from '@material/ripple/foundation'; +import {MDCRipple} from '@material/ripple/index'; +import {RippleCapableSurface} from '@material/ripple/types'; import {MDCTextFieldCharacterCounter, MDCTextFieldCharacterCounterFoundation} from './character-counter'; import {cssClasses, strings} from './constants'; import {MDCTextFieldFoundation} from './foundation'; diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts index 39ccd6a3076..b9ea82591f8 100644 --- a/packages/mdc-textfield/types.ts +++ b/packages/mdc-textfield/types.ts @@ -24,7 +24,8 @@ import {MDCFloatingLabel} from '@material/floating-label/index'; import {MDCLineRipple} from '@material/line-ripple/index'; import {MDCNotchedOutline} from '@material/notched-outline/index'; -import {MDCRipple, MDCRippleFoundation} from '@material/ripple/index'; +import {MDCRippleFoundation} from '@material/ripple/foundation'; +import {MDCRipple} from '@material/ripple/index'; import {MDCTextFieldCharacterCounterFoundation} from './character-counter/foundation'; import {MDCTextFieldCharacterCounter} from './character-counter/index'; import {MDCTextFieldHelperTextFoundation} from './helper-text/foundation'; From b67643396e6f26d9461f69c369616415fc32af02 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Mon, 11 Feb 2019 20:05:42 -0800 Subject: [PATCH 28/33] WIP: Use `Pick` instead of a hard-coded interface def --- packages/mdc-textfield/types.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/mdc-textfield/types.ts b/packages/mdc-textfield/types.ts index b9ea82591f8..7ac8a921d06 100644 --- a/packages/mdc-textfield/types.ts +++ b/packages/mdc-textfield/types.ts @@ -33,15 +33,8 @@ import {MDCTextFieldHelperText} from './helper-text/index'; import {MDCTextFieldIconFoundation} from './icon/foundation'; import {MDCTextFieldIcon} from './icon/index'; -export type NativeInputElement = HTMLInputElement | { - disabled: boolean; - maxLength: number; - type: string; - validity: ValidityState | { - badInput: boolean; - valid: boolean; - }; - value: string; +export type NativeInputElement = Pick & { + validity: Pick; }; export interface FoundationMapType { From 730164d80e3a4b34b911a8df84c0e5208e5177fc Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Mon, 11 Feb 2019 20:09:52 -0800 Subject: [PATCH 29/33] WIP: Update comments --- packages/mdc-textfield/adapter.ts | 7 ++----- packages/mdc-textfield/foundation.ts | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/mdc-textfield/adapter.ts b/packages/mdc-textfield/adapter.ts index fd3293e900f..c8163f1f1c3 100644 --- a/packages/mdc-textfield/adapter.ts +++ b/packages/mdc-textfield/adapter.ts @@ -79,11 +79,8 @@ interface MDCTextFieldAdapter { deregisterValidationAttributeChangeHandler(observer: MutationObserver): void; /** - * @return An object representing the native text input element, with a similar API shape. - * 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. + * @return The native `` element, or an object with the same shape. + * Note that this method can return null, which the foundation will handle gracefully. */ getNativeInput(): NativeInputElement | null; diff --git a/packages/mdc-textfield/foundation.ts b/packages/mdc-textfield/foundation.ts index baf58827f5c..acdbf0c840d 100644 --- a/packages/mdc-textfield/foundation.ts +++ b/packages/mdc-textfield/foundation.ts @@ -463,12 +463,12 @@ class MDCTextFieldFoundation extends MDCFoundation { } /** - * @return The native text input from the host environment. + * @return The native text input element from the host environment, or an object with the same shape for unit tests. */ private getNativeInput_(): NativeInputElement { // this.adapter_ may be undefined in foundation unit tests. This happens when testdouble is creating a mock object // and invokes the shouldShake/shouldFloat getters (which in turn call getValue(), which calls this method) before - // init() has been called from the MDCTextField constructor. To work around that issue, we return a dummy input. + // init() has been called from the MDCTextField constructor. To work around that issue, we return a dummy object. const nativeInput = this.adapter_ ? this.adapter_.getNativeInput() : null; return nativeInput || { disabled: false, From 530c4c63334d53d059d9ac5d1842722192f919ff Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Tue, 12 Feb 2019 12:47:18 -0800 Subject: [PATCH 30/33] WIP: Address review comments --- packages/mdc-textfield/icon/foundation.ts | 3 +-- packages/mdc-textfield/index.ts | 12 ++++-------- test/unit/mdc-textfield/foundation.test.js | 4 ++-- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/mdc-textfield/icon/foundation.ts b/packages/mdc-textfield/icon/foundation.ts index 7a37ce195e1..7b0dd71edc0 100644 --- a/packages/mdc-textfield/icon/foundation.ts +++ b/packages/mdc-textfield/icon/foundation.ts @@ -52,13 +52,12 @@ class MDCTextFieldIconFoundation extends MDCFoundation // tslint:enable:object-literal-sort-keys } - private savedTabIndex_: string | null; + private savedTabIndex_: string | null = null; private readonly interactionHandler_: SpecificEventListener; constructor(adapter?: Partial) { super({...MDCTextFieldIconFoundation.defaultAdapter, ...adapter}); - this.savedTabIndex_ = null; this.interactionHandler_ = (evt) => this.handleInteraction(evt); } diff --git a/packages/mdc-textfield/index.ts b/packages/mdc-textfield/index.ts index ebc1d33a7d5..78870c12eb1 100644 --- a/packages/mdc-textfield/index.ts +++ b/packages/mdc-textfield/index.ts @@ -127,12 +127,8 @@ class MDCTextField extends MDCComponent implements Rippl ...({ // tslint:disable:object-literal-sort-keys isSurfaceActive: () => ponyfill.matches(this.input_, ':active'), - registerInteractionHandler: (evtType, handler) => { - return this.input_.addEventListener(evtType, handler); - }, - deregisterInteractionHandler: (evtType, handler) => { - return this.input_.removeEventListener(evtType, handler); - }, + registerInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler), + deregisterInteractionHandler: (evtType, handler) => this.input_.removeEventListener(evtType, handler), // tslint:enable:object-literal-sort-keys }), })); @@ -419,10 +415,10 @@ class MDCTextField extends MDCComponent implements Rippl // tslint:disable:object-literal-sort-keys return { registerInputInteractionHandler: (evtType: E, handler: SpecificEventListener) => { - return this.input_.addEventListener(evtType, handler); + this.input_.addEventListener(evtType, handler); }, deregisterInputInteractionHandler: (evtType: E, handler: SpecificEventListener) => { - return this.input_.removeEventListener(evtType, handler); + this.input_.removeEventListener(evtType, handler); }, getNativeInput: () => this.input_, }; diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 6958b0adf1a..135674df9d9 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -443,7 +443,7 @@ test('#setLeadingIconContent sets the content of the leading icon element', () = td.verify(leadingIcon.setContent('foo')); }); -test('#setLeadingIconContent sets does nothing if element is not present', () => { +test('#setLeadingIconContent does nothing if element is not present', () => { const {foundation} = setupTest({useLeadingIcon: false}); assert.doesNotThrow(() => foundation.setLeadingIconContent('foo')); }); @@ -465,7 +465,7 @@ test('#setTrailingIconContent sets the content of the trailing icon element', () td.verify(trailingIcon.setContent('foo')); }); -test('#setTrailingIconContent sets does nothing if element is not present', () => { +test('#setTrailingIconContent does nothing if element is not present', () => { const {foundation} = setupTest({useTrailingIcon: false}); assert.doesNotThrow(() => foundation.setTrailingIconContent('foo')); }); From be64af28d164b6edf63dd7c0e3f4299ccf37ebc6 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Tue, 12 Feb 2019 12:55:38 -0800 Subject: [PATCH 31/33] WIP: Wrap `setContent()` expressions in `{}` to ensure `void` return type --- packages/mdc-textfield/character-counter/index.ts | 4 +--- packages/mdc-textfield/helper-text/index.ts | 2 +- packages/mdc-textfield/icon/index.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/mdc-textfield/character-counter/index.ts b/packages/mdc-textfield/character-counter/index.ts index 80b277869b5..d2a39a22c18 100644 --- a/packages/mdc-textfield/character-counter/index.ts +++ b/packages/mdc-textfield/character-counter/index.ts @@ -35,9 +35,7 @@ class MDCTextFieldCharacterCounter extends MDCComponent { - this.root_.textContent = content; - }, + setContent: (content) => { this.root_.textContent = content; }, }); } } diff --git a/packages/mdc-textfield/helper-text/index.ts b/packages/mdc-textfield/helper-text/index.ts index be43b63a917..8bd08413b02 100644 --- a/packages/mdc-textfield/helper-text/index.ts +++ b/packages/mdc-textfield/helper-text/index.ts @@ -41,7 +41,7 @@ class MDCTextFieldHelperText extends MDCComponent this.root_.classList.contains(className), setAttr: (attr, value) => this.root_.setAttribute(attr, value), removeAttr: (attr) => this.root_.removeAttribute(attr), - setContent: (content) => this.root_.textContent = content, + setContent: (content) => { this.root_.textContent = content; }, }); // tslint:enable:object-literal-sort-keys } diff --git a/packages/mdc-textfield/icon/index.ts b/packages/mdc-textfield/icon/index.ts index a27678bddd4..2d19a8d5e1f 100644 --- a/packages/mdc-textfield/icon/index.ts +++ b/packages/mdc-textfield/icon/index.ts @@ -40,7 +40,7 @@ class MDCTextFieldIcon extends MDCComponent { getAttr: (attr) => this.root_.getAttribute(attr), setAttr: (attr, value) => this.root_.setAttribute(attr, value), removeAttr: (attr) => this.root_.removeAttribute(attr), - setContent: (content) => this.root_.textContent = content, + setContent: (content) => { this.root_.textContent = content; }, registerInteractionHandler: (evtType: E, handler: SpecificEventListener) => { this.root_.addEventListener(evtType, handler); }, From c405d56d0b0daebe785ad2b31775e3e71f6d3532 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Tue, 12 Feb 2019 14:49:41 -0800 Subject: [PATCH 32/33] WIP: Address review comments --- test/unit/mdc-textfield/foundation.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/mdc-textfield/foundation.test.js b/test/unit/mdc-textfield/foundation.test.js index 135674df9d9..6f0bfab0c14 100644 --- a/test/unit/mdc-textfield/foundation.test.js +++ b/test/unit/mdc-textfield/foundation.test.js @@ -63,15 +63,15 @@ const setupTest = ({ useTrailingIcon = false, } = {}) => { const mockAdapter = td.object(MDCTextFieldFoundation.defaultAdapter); - const helperText = useHelperText === true ? td.object({ + const helperText = useHelperText ? td.object({ setContent: () => {}, showToScreenReader: () => {}, setValidity: () => {}, }) : undefined; - const characterCounter = useCharacterCounter === true ? td.object({ + const characterCounter = useCharacterCounter ? td.object({ setCounterValue: () => {}, }) : undefined; - const leadingIcon = useLeadingIcon === true ? td.object({ + const leadingIcon = useLeadingIcon ? td.object({ setDisabled: () => {}, setAriaLabel: () => {}, setContent: () => {}, @@ -79,7 +79,7 @@ const setupTest = ({ deregisterInteractionHandler: () => {}, handleInteraction: () => {}, }) : undefined; - const trailingIcon = useTrailingIcon === true ? td.object({ + const trailingIcon = useTrailingIcon ? td.object({ setDisabled: () => {}, setAriaLabel: () => {}, setContent: () => {}, From 03f002296d0f1d4b0d7ab4c790636572fa3e4a71 Mon Sep 17 00:00:00 2001 From: Andy Dvorak Date: Tue, 12 Feb 2019 14:58:30 -0800 Subject: [PATCH 33/33] WIP: Address review comments --- ...-text-field-character-counter-foundation.test.js | 10 ++++++---- .../mdc-text-field-helper-text-foundation.test.js | 10 ++++++---- .../mdc-text-field-icon-foundation.test.js | 13 +++++++------ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js index a91f687eb28..2561f42b99e 100644 --- a/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-character-counter-foundation.test.js @@ -46,6 +46,10 @@ test('defaultAdapter returns a complete adapter implementation', () => { const setupTest = () => setupFoundationTest(MDCTextFieldCharacterCounterFoundation); +test('istanbul code coverage', () => { + assert.doesNotThrow(() => new MDCTextFieldCharacterCounterFoundation()); +}); + test('#setContent sets the content of the character counter element', () => { const {foundation, mockAdapter} = setupTest(); foundation.setCounterValue(12, 20); @@ -53,9 +57,7 @@ test('#setContent sets the content of the character counter element', () => { }); test('#setContent current length does not exceed character count limit', () => { - const foundation = new MDCTextFieldCharacterCounterFoundation(); - const adapter = foundation.adapter_; - adapter.setContent = td.func('setContent'); + const {foundation, mockAdapter} = setupTest(); foundation.setCounterValue(24, 20); - td.verify(adapter.setContent('20 / 20')); + td.verify(mockAdapter.setContent('20 / 20')); }); diff --git a/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js index 1d4509b4b1b..1bb4d634f0b 100644 --- a/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-helper-text-foundation.test.js @@ -48,12 +48,14 @@ test('defaultAdapter returns a complete adapter implementation', () => { const setupTest = () => setupFoundationTest(MDCTextFieldHelperTextFoundation); +test('istanbul code coverage', () => { + assert.doesNotThrow(() => new MDCTextFieldHelperTextFoundation()); +}); + test('#setContent sets the content of the helper text element', () => { - const foundation = new MDCTextFieldHelperTextFoundation(); - const adapter = foundation.adapter_; - adapter.setContent = td.func('setContent'); + const {foundation, mockAdapter} = setupTest(); foundation.setContent('foo'); - td.verify(adapter.setContent('foo')); + td.verify(mockAdapter.setContent('foo')); }); test('#setPersistent toggles the persistent class', () => { diff --git a/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js b/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js index 409bd515dad..914860a2ce7 100644 --- a/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js +++ b/test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js @@ -44,15 +44,16 @@ test('defaultAdapter returns a complete adapter implementation', () => { const setupTest = () => setupFoundationTest(MDCTextFieldIconFoundation); -test('#init adds event listeners', () => { - const foundation = new MDCTextFieldIconFoundation(); - const adapter = foundation.adapter_; - adapter.registerInteractionHandler = td.func('registerInteractionHandler'); +test('istanbul code coverage', () => { + assert.doesNotThrow(() => new MDCTextFieldIconFoundation()); +}); +test('#init adds event listeners', () => { + const {foundation, mockAdapter} = setupTest(); foundation.init(); - td.verify(adapter.registerInteractionHandler('click', td.matchers.isA(Function))); - td.verify(adapter.registerInteractionHandler('keydown', td.matchers.isA(Function))); + td.verify(mockAdapter.registerInteractionHandler('click', td.matchers.isA(Function))); + td.verify(mockAdapter.registerInteractionHandler('keydown', td.matchers.isA(Function))); }); test('#destroy removes event listeners', () => {