From b8b19887c1825cdfc1962bdeae722ea619309246 Mon Sep 17 00:00:00 2001 From: "Andrew C. Dvorak" Date: Thu, 14 Feb 2019 11:07:49 -0800 Subject: [PATCH] feat(top-app-bar): Convert JS to TypeScript (#4397) Refs #4225 --- .../{adapter.js => adapter.ts} | 64 ++++----- .../{constants.js => constants.ts} | 7 +- .../fixed/{foundation.js => foundation.ts} | 32 ++--- packages/mdc-top-app-bar/foundation.js | 99 -------------- packages/mdc-top-app-bar/foundation.ts | 114 ++++++++++++++++ .../mdc-top-app-bar/{index.js => index.ts} | 101 +++++++------- .../short/{foundation.js => foundation.ts} | 33 ++--- .../standard/{foundation.js => foundation.ts} | 124 +++++++----------- scripts/webpack/js-bundle-factory.js | 2 +- test/unit/mdc-top-app-bar/foundation.test.js | 7 +- .../mdc-top-app-bar/mdc-top-app-bar.test.js | 14 +- 11 files changed, 271 insertions(+), 326 deletions(-) rename packages/mdc-top-app-bar/{adapter.js => adapter.ts} (55%) rename packages/mdc-top-app-bar/{constants.js => constants.ts} (94%) rename packages/mdc-top-app-bar/fixed/{foundation.js => foundation.ts} (71%) delete mode 100644 packages/mdc-top-app-bar/foundation.js create mode 100644 packages/mdc-top-app-bar/foundation.ts rename packages/mdc-top-app-bar/{index.js => index.ts} (62%) rename packages/mdc-top-app-bar/short/{foundation.js => foundation.ts} (74%) rename packages/mdc-top-app-bar/standard/{foundation.js => foundation.ts} (71%) diff --git a/packages/mdc-top-app-bar/adapter.js b/packages/mdc-top-app-bar/adapter.ts similarity index 55% rename from packages/mdc-top-app-bar/adapter.js rename to packages/mdc-top-app-bar/adapter.ts index e648cb58d5e..e1ab8eddd63 100644 --- a/packages/mdc-top-app-bar/adapter.js +++ b/packages/mdc-top-app-bar/adapter.ts @@ -21,87 +21,67 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ +import {EventType, SpecificEventListener} from '@material/base/types'; /** - * Adapter for MDC Top App Bar - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the Top App Bar 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 MDCTopAppBarAdapter { +interface MDCTopAppBarAdapter { /** * 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. - * @param {string} className - * @return {boolean} */ - hasClass(className) {} + hasClass(className: string): boolean; /** * Sets the specified inline style property on the root Element to the given value. - * @param {string} property - * @param {string} value */ - setStyle(property, value) {} + setStyle(property: string, value: string): void; /** * Gets the height of the top app bar. - * @return {number} */ - getTopAppBarHeight() {} + getTopAppBarHeight(): number; /** * Registers an event handler on the navigation icon element for a given event. - * @param {string} type - * @param {function(!Event): undefined} handler */ - registerNavigationIconInteractionHandler(type, handler) {} + registerNavigationIconInteractionHandler(type: K, handler: SpecificEventListener): void; /** * Deregisters an event handler on the navigation icon element for a given event. - * @param {string} type - * @param {function(!Event): undefined} handler */ - deregisterNavigationIconInteractionHandler(type, handler) {} + deregisterNavigationIconInteractionHandler(type: K, handler: SpecificEventListener): void; /** * Emits an event when the navigation icon is clicked. */ - notifyNavigationIconClicked() {} + notifyNavigationIconClicked(): void; - /** @param {function(!Event)} handler */ - registerScrollHandler(handler) {} + registerScrollHandler(handler: SpecificEventListener<'scroll'>): void; - /** @param {function(!Event)} handler */ - deregisterScrollHandler(handler) {} + deregisterScrollHandler(handler: SpecificEventListener<'scroll'>): void; - /** @param {function(!Event)} handler */ - registerResizeHandler(handler) {} + registerResizeHandler(handler: SpecificEventListener<'resize'>): void; - /** @param {function(!Event)} handler */ - deregisterResizeHandler(handler) {} + deregisterResizeHandler(handler: SpecificEventListener<'resize'>): void; - /** @return {number} */ - getViewportScrollY() {} + getViewportScrollY(): number; - /** @return {number} */ - getTotalActionItems() {} + getTotalActionItems(): number; } -export default MDCTopAppBarAdapter; +export {MDCTopAppBarAdapter as default, MDCTopAppBarAdapter}; diff --git a/packages/mdc-top-app-bar/constants.js b/packages/mdc-top-app-bar/constants.ts similarity index 94% rename from packages/mdc-top-app-bar/constants.js rename to packages/mdc-top-app-bar/constants.ts index b26e836bbd9..c3db858d8dd 100644 --- a/packages/mdc-top-app-bar/constants.js +++ b/packages/mdc-top-app-bar/constants.ts @@ -21,22 +21,19 @@ * THE SOFTWARE. */ -/** @enum {string} */ const cssClasses = { FIXED_CLASS: 'mdc-top-app-bar--fixed', FIXED_SCROLLED_CLASS: 'mdc-top-app-bar--fixed-scrolled', SHORT_CLASS: 'mdc-top-app-bar--short', - SHORT_HAS_ACTION_ITEM_CLASS: 'mdc-top-app-bar--short-has-action-item', SHORT_COLLAPSED_CLASS: 'mdc-top-app-bar--short-collapsed', + SHORT_HAS_ACTION_ITEM_CLASS: 'mdc-top-app-bar--short-has-action-item', }; -/** @enum {number} */ const numbers = { DEBOUNCE_THROTTLE_RESIZE_TIME_MS: 100, MAX_TOP_APP_BAR_HEIGHT: 128, }; -/** @enum {string} */ const strings = { ACTION_ITEM_SELECTOR: '.mdc-top-app-bar__action-item', NAVIGATION_EVENT: 'MDCTopAppBar:nav', @@ -45,4 +42,4 @@ const strings = { TITLE_SELECTOR: '.mdc-top-app-bar__title', }; -export {strings, cssClasses, numbers}; +export {cssClasses, numbers, strings}; diff --git a/packages/mdc-top-app-bar/fixed/foundation.js b/packages/mdc-top-app-bar/fixed/foundation.ts similarity index 71% rename from packages/mdc-top-app-bar/fixed/foundation.js rename to packages/mdc-top-app-bar/fixed/foundation.ts index c7d5b661dda..33a39bc0784 100644 --- a/packages/mdc-top-app-bar/fixed/foundation.js +++ b/packages/mdc-top-app-bar/fixed/foundation.ts @@ -21,39 +21,25 @@ * THE SOFTWARE. */ +import {MDCTopAppBarAdapter} from '../adapter'; import {cssClasses} from '../constants'; -import MDCTopAppBarAdapter from '../adapter'; -import MDCTopAppBarFoundation from '../foundation'; +import {MDCTopAppBarFoundation} from '../standard/foundation'; -/** - * @extends {MDCTopAppBarFoundation} - * @final - */ class MDCFixedTopAppBarFoundation extends MDCTopAppBarFoundation { /** - * @param {!MDCTopAppBarAdapter} adapter + * State variable for the previous scroll iteration top app bar state */ - constructor(adapter) { + private wasScrolled_ = false; + + /* istanbul ignore next: optional argument is not a branch statement */ + constructor(adapter?: Partial) { super(adapter); - /** State variable for the previous scroll iteration top app bar state */ - this.wasScrolled_ = false; this.scrollHandler_ = () => this.fixedScrollHandler_(); } - init() { - super.init(); - this.adapter_.registerScrollHandler(this.scrollHandler_); - } - - destroy() { - super.destroy(); - this.adapter_.deregisterScrollHandler(this.scrollHandler_); - } - /** - * Scroll handler for applying/removing the modifier class - * on the fixed top app bar. + * Scroll handler for applying/removing the modifier class on the fixed top app bar. */ fixedScrollHandler_() { const currentScroll = this.adapter_.getViewportScrollY(); @@ -72,4 +58,4 @@ class MDCFixedTopAppBarFoundation extends MDCTopAppBarFoundation { } } -export default MDCFixedTopAppBarFoundation; +export {MDCFixedTopAppBarFoundation as default, MDCFixedTopAppBarFoundation}; diff --git a/packages/mdc-top-app-bar/foundation.js b/packages/mdc-top-app-bar/foundation.js deleted file mode 100644 index 57a931d211f..00000000000 --- a/packages/mdc-top-app-bar/foundation.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright 2018 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 {strings, cssClasses, numbers} from './constants'; -import MDCTopAppBarAdapter from './adapter'; -import {MDCFoundation} from '@material/base/foundation'; - -/** - * @extends {MDCFoundation} - */ -class MDCTopAppBarBaseFoundation extends MDCFoundation { - /** @return enum {string} */ - static get strings() { - return strings; - } - - /** @return enum {string} */ - static get cssClasses() { - return cssClasses; - } - - /** @return enum {number} */ - static get numbers() { - return numbers; - } - - /** - * {@see MDCTopAppBarAdapter} for typing information on parameters and return - * types. - * @return {!MDCTopAppBarAdapter} - */ - static get defaultAdapter() { - return /** @type {!MDCTopAppBarAdapter} */ ({ - hasClass: (/* className: string */) => {}, - addClass: (/* className: string */) => {}, - removeClass: (/* className: string */) => {}, - setStyle: (/* property: string, value: string */) => {}, - getTopAppBarHeight: () => {}, - registerNavigationIconInteractionHandler: (/* type: string, handler: EventListener */) => {}, - deregisterNavigationIconInteractionHandler: (/* type: string, handler: EventListener */) => {}, - notifyNavigationIconClicked: () => {}, - registerScrollHandler: (/* handler: EventListener */) => {}, - deregisterScrollHandler: (/* handler: EventListener */) => {}, - registerResizeHandler: (/* handler: EventListener */) => {}, - deregisterResizeHandler: (/* handler: EventListener */) => {}, - getViewportScrollY: () => /* number */ 0, - getTotalActionItems: () => /* number */ 0, - }); - } - - /** - * @param {!MDCTopAppBarAdapter} adapter - */ - constructor(/** @type {!MDCTopAppBarAdapter} */ adapter) { - super(Object.assign(MDCTopAppBarBaseFoundation.defaultAdapter, adapter)); - - this.navClickHandler_ = () => this.adapter_.notifyNavigationIconClicked(); - - this.scrollHandler_ = () => {}; - } - - init() { - this.adapter_.registerNavigationIconInteractionHandler('click', this.navClickHandler_); - } - - destroy() { - this.adapter_.deregisterNavigationIconInteractionHandler('click', this.navClickHandler_); - } - - initScrollHandler() { - this.adapter_.registerScrollHandler(this.scrollHandler_); - } - - destroyScrollHandler() { - this.adapter_.deregisterScrollHandler(this.scrollHandler_); - } -} - -export default MDCTopAppBarBaseFoundation; diff --git a/packages/mdc-top-app-bar/foundation.ts b/packages/mdc-top-app-bar/foundation.ts new file mode 100644 index 00000000000..56c85c9d80b --- /dev/null +++ b/packages/mdc-top-app-bar/foundation.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2018 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 {MDCFoundation} from '@material/base/foundation'; +import {SpecificEventListener} from '@material/base/types'; +import {MDCTopAppBarAdapter} from './adapter'; +import {cssClasses, numbers, strings} from './constants'; + +class MDCTopAppBarBaseFoundation extends MDCFoundation { + static get strings() { + return strings; + } + + static get cssClasses() { + return cssClasses; + } + + static get numbers() { + return numbers; + } + + /** + * See {@link MDCTopAppBarAdapter} for typing information on parameters and return types. + */ + static get defaultAdapter(): MDCTopAppBarAdapter { + // tslint:disable:object-literal-sort-keys + return { + addClass: () => undefined, + removeClass: () => undefined, + hasClass: () => false, + setStyle: () => undefined, + getTopAppBarHeight: () => 0, + registerNavigationIconInteractionHandler: () => undefined, + deregisterNavigationIconInteractionHandler: () => undefined, + notifyNavigationIconClicked: () => undefined, + registerScrollHandler: () => undefined, + deregisterScrollHandler: () => undefined, + registerResizeHandler: () => undefined, + deregisterResizeHandler: () => undefined, + getViewportScrollY: () => 0, + getTotalActionItems: () => 0, + }; + // tslint:enable:object-literal-sort-keys + } + + protected scrollHandler_?: SpecificEventListener<'scroll'>; + protected resizeHandler_?: SpecificEventListener<'resize'>; + private readonly navClickHandler_: SpecificEventListener<'click'>; + + /* istanbul ignore next: optional argument is not a branch statement */ + constructor(adapter?: Partial) { + super({...MDCTopAppBarBaseFoundation.defaultAdapter, ...adapter}); + + this.navClickHandler_ = () => this.adapter_.notifyNavigationIconClicked(); + } + + init() { + this.initScrollHandler(); + this.initResizeHandler_(); + this.adapter_.registerNavigationIconInteractionHandler('click', this.navClickHandler_); + } + + destroy() { + this.destroyScrollHandler(); + this.destroyResizeHandler_(); + this.adapter_.deregisterNavigationIconInteractionHandler('click', this.navClickHandler_); + } + + initScrollHandler() { + if (this.scrollHandler_) { + this.adapter_.registerScrollHandler(this.scrollHandler_); + } + } + + destroyScrollHandler() { + if (this.scrollHandler_) { + this.adapter_.deregisterScrollHandler(this.scrollHandler_); + } + } + + private initResizeHandler_() { + if (this.resizeHandler_) { + this.adapter_.registerResizeHandler(this.resizeHandler_); + } + } + + private destroyResizeHandler_() { + if (this.resizeHandler_) { + this.adapter_.deregisterResizeHandler(this.resizeHandler_); + } + } +} + +export {MDCTopAppBarBaseFoundation as default, MDCTopAppBarBaseFoundation}; diff --git a/packages/mdc-top-app-bar/index.js b/packages/mdc-top-app-bar/index.ts similarity index 62% rename from packages/mdc-top-app-bar/index.js rename to packages/mdc-top-app-bar/index.ts index 7a7aa61ab2b..75907d67f76 100644 --- a/packages/mdc-top-app-bar/index.js +++ b/packages/mdc-top-app-bar/index.ts @@ -21,39 +21,31 @@ * THE SOFTWARE. */ -import MDCTopAppBarAdapter from './adapter'; import {MDCComponent} from '@material/base/component'; import {MDCRipple} from '@material/ripple/index'; +import {MDCTopAppBarAdapter} from './adapter'; import {cssClasses, strings} from './constants'; -import MDCTopAppBarBaseFoundation from './foundation'; -import MDCFixedTopAppBarFoundation from './fixed/foundation'; -import MDCShortTopAppBarFoundation from './short/foundation'; -import MDCTopAppBarFoundation from './standard/foundation'; +import {MDCFixedTopAppBarFoundation} from './fixed/foundation'; +import {MDCTopAppBarBaseFoundation} from './foundation'; +import {MDCShortTopAppBarFoundation} from './short/foundation'; +import {MDCTopAppBarFoundation} from './standard/foundation'; -/** - * @extends {MDCComponent} - * @final - */ -class MDCTopAppBar extends MDCComponent { - /** - * @param {...?} args - */ - constructor(...args) { - super(...args); - /** @private {?Element} */ - this.navIcon_; - /** @type {?Array} */ - this.iconRipples_; - /** @type {Object} */ - this.scrollTarget_; +export type MDCRippleFactory = (el: Element) => MDCRipple; + +class MDCTopAppBar extends MDCComponent { + static attachTo(root: Element): MDCTopAppBar { + return new MDCTopAppBar(root); } - initialize( - rippleFactory = (el) => MDCRipple.attachTo(el)) { + private navIcon_!: Element | null; + private iconRipples_!: MDCRipple[]; + private scrollTarget_!: EventTarget; + + initialize(rippleFactory: MDCRippleFactory = (el) => MDCRipple.attachTo(el)) { this.navIcon_ = this.root_.querySelector(strings.NAVIGATION_ICON_SELECTOR); // Get all icons in the toolbar and instantiate the ripples - const icons = [].slice.call(this.root_.querySelectorAll(strings.ACTION_ITEM_SELECTOR)); + const icons: Element[] = [].slice.call(this.root_.querySelectorAll(strings.ACTION_ITEM_SELECTOR)); if (this.navIcon_) { icons.push(this.navIcon_); } @@ -72,30 +64,23 @@ class MDCTopAppBar extends MDCComponent { super.destroy(); } - setScrollTarget(target) { + setScrollTarget(target: EventTarget) { + // Remove scroll handler from the previous scroll target this.foundation_.destroyScrollHandler(); + this.scrollTarget_ = target; - this.foundation_.initScrollHandler(); - } - /** - * @param {!Element} root - * @return {!MDCTopAppBar} - */ - static attachTo(root) { - return new MDCTopAppBar(root); + // Initialize scroll handler on the new scroll target + this.foundation_.initScrollHandler(); } - /** - * @return {!MDCTopAppBarBaseFoundation} - */ - getDefaultFoundation() { - /** @type {!MDCTopAppBarAdapter} */ - const adapter = /** @type {!MDCTopAppBarAdapter} */ (Object.assign({ + getDefaultFoundation(): MDCTopAppBarBaseFoundation { + // tslint:disable:object-literal-sort-keys + const adapter: MDCTopAppBarAdapter = { hasClass: (className) => this.root_.classList.contains(className), addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), - setStyle: (property, value) => this.root_.style.setProperty(property, value), + setStyle: (property, value) => (this.root_ as HTMLElement).style.setProperty(property, value), getTopAppBarHeight: () => this.root_.clientHeight, registerNavigationIconInteractionHandler: (evtType, handler) => { if (this.navIcon_) { @@ -107,22 +92,21 @@ class MDCTopAppBar extends MDCComponent { this.navIcon_.removeEventListener(evtType, handler); } }, - notifyNavigationIconClicked: () => { - this.emit(strings.NAVIGATION_EVENT, {}); - }, - registerScrollHandler: (handler) => this.scrollTarget_.addEventListener('scroll', handler), - deregisterScrollHandler: (handler) => this.scrollTarget_.removeEventListener('scroll', handler), + notifyNavigationIconClicked: () => this.emit(strings.NAVIGATION_EVENT, {}), + registerScrollHandler: (handler) => this.scrollTarget_.addEventListener('scroll', handler as EventListener), + deregisterScrollHandler: (handler) => this.scrollTarget_.removeEventListener('scroll', handler as EventListener), registerResizeHandler: (handler) => window.addEventListener('resize', handler), deregisterResizeHandler: (handler) => window.removeEventListener('resize', handler), - getViewportScrollY: () => - this.scrollTarget_[this.scrollTarget_ === window ? 'pageYOffset' : 'scrollTop'], - getTotalActionItems: () => - this.root_.querySelectorAll(strings.ACTION_ITEM_SELECTOR).length, - }) - ); - - /** @type {!MDCTopAppBarBaseFoundation} */ - let foundation; + getViewportScrollY: () => { + const win = this.scrollTarget_ as Window; + const el = this.scrollTarget_ as Element; + return win.pageYOffset !== undefined ? win.pageYOffset : el.scrollTop; + }, + getTotalActionItems: () => this.root_.querySelectorAll(strings.ACTION_ITEM_SELECTOR).length, + }; + // tslint:enable:object-literal-sort-keys + + let foundation: MDCTopAppBarBaseFoundation; if (this.root_.classList.contains(cssClasses.SHORT_CLASS)) { foundation = new MDCShortTopAppBarFoundation(adapter); } else if (this.root_.classList.contains(cssClasses.FIXED_CLASS)) { @@ -135,6 +119,9 @@ class MDCTopAppBar extends MDCComponent { } } -export {MDCTopAppBar, MDCTopAppBarBaseFoundation, - MDCTopAppBarFoundation, MDCFixedTopAppBarFoundation, - MDCShortTopAppBarFoundation}; +export {MDCTopAppBar as default, MDCTopAppBar}; +export * from './fixed/foundation'; +export * from './short/foundation'; +export * from './standard/foundation'; +export * from './adapter'; +export * from './foundation'; diff --git a/packages/mdc-top-app-bar/short/foundation.js b/packages/mdc-top-app-bar/short/foundation.ts similarity index 74% rename from packages/mdc-top-app-bar/short/foundation.js rename to packages/mdc-top-app-bar/short/foundation.ts index aabb3d88402..6f8833faf55 100644 --- a/packages/mdc-top-app-bar/short/foundation.js +++ b/packages/mdc-top-app-bar/short/foundation.ts @@ -21,35 +21,30 @@ * THE SOFTWARE. */ -import MDCTopAppBarAdapter from '../adapter'; -import MDCTopAppBarBaseFoundation from '../foundation'; +import {MDCTopAppBarAdapter} from '../adapter'; import {cssClasses} from '../constants'; +import {MDCTopAppBarBaseFoundation} from '../foundation'; -/** - * @extends {MDCTopAppBarBaseFoundation} - * @final - */ class MDCShortTopAppBarFoundation extends MDCTopAppBarBaseFoundation { /** - * @param {!MDCTopAppBarAdapter} adapter + * State variable for the current top app bar state */ - constructor(adapter) { - super(adapter); - // State variable for the current top app bar state - this.isCollapsed = false; + isCollapsed = false; - this.scrollHandler_ = () => this.shortAppBarScrollHandler_(); + /* istanbul ignore next: optional argument is not a branch statement */ + constructor(adapter?: Partial) { + super(adapter); } init() { super.init(); - const isAlwaysCollapsed = this.adapter_.hasClass(cssClasses.SHORT_COLLAPSED_CLASS); if (this.adapter_.getTotalActionItems() > 0) { this.adapter_.addClass(cssClasses.SHORT_HAS_ACTION_ITEM_CLASS); } - if (!isAlwaysCollapsed) { + if (!this.adapter_.hasClass(cssClasses.SHORT_COLLAPSED_CLASS)) { + this.scrollHandler_ = () => this.shortAppBarScrollHandler_(); this.adapter_.registerScrollHandler(this.scrollHandler_); this.shortAppBarScrollHandler_(); } @@ -57,16 +52,12 @@ class MDCShortTopAppBarFoundation extends MDCTopAppBarBaseFoundation { destroy() { super.destroy(); - this.adapter_.deregisterScrollHandler(this.scrollHandler_); } - /** - * Scroll handler for applying/removing the collapsed modifier class - * on the short top app bar. - * @private + * Scroll handler for applying/removing the collapsed modifier class on the short top app bar. */ - shortAppBarScrollHandler_() { + private shortAppBarScrollHandler_() { const currentScroll = this.adapter_.getViewportScrollY(); if (currentScroll <= 0) { @@ -83,4 +74,4 @@ class MDCShortTopAppBarFoundation extends MDCTopAppBarBaseFoundation { } } -export default MDCShortTopAppBarFoundation; +export {MDCShortTopAppBarFoundation as default, MDCShortTopAppBarFoundation}; diff --git a/packages/mdc-top-app-bar/standard/foundation.js b/packages/mdc-top-app-bar/standard/foundation.ts similarity index 71% rename from packages/mdc-top-app-bar/standard/foundation.js rename to packages/mdc-top-app-bar/standard/foundation.ts index 69024028a40..c05df38a54f 100644 --- a/packages/mdc-top-app-bar/standard/foundation.js +++ b/packages/mdc-top-app-bar/standard/foundation.ts @@ -21,93 +21,73 @@ * THE SOFTWARE. */ -import MDCTopAppBarAdapter from '../adapter'; -import MDCTopAppBarBaseFoundation from '../foundation'; +import {MDCTopAppBarAdapter} from '../adapter'; import {numbers} from '../constants'; +import {MDCTopAppBarBaseFoundation} from '../foundation'; const INITIAL_VALUE = 0; -/** - * @extends {MDCTopAppBarBaseFoundation} - * @final - */ + class MDCTopAppBarFoundation extends MDCTopAppBarBaseFoundation { /** - * @param {!MDCTopAppBarAdapter} adapter + * Indicates if the top app bar was docked in the previous scroll handler iteration. + */ + private wasDocked_ = true; + + /** + * Indicates if the top app bar is docked in the fully shown position. + */ + private isDockedShowing_ = true; + + /** + * Variable for current scroll position of the top app bar + */ + private currentAppBarOffsetTop_ = 0; + + /** + * Used to prevent the top app bar from being scrolled out of view during resize events + */ + private isCurrentlyBeingResized_ = false; + + /** + * The timeout that's used to throttle the resize events */ - constructor(adapter) { + private resizeThrottleId_ = INITIAL_VALUE; + + /** + * Used for diffs of current scroll position vs previous scroll position + */ + private lastScrollPosition_: number; + + /** + * Used to verify when the top app bar is completely showing or completely hidden + */ + private topAppBarHeight_: number; + + /** + * The timeout that's used to debounce toggling the isCurrentlyBeingResized_ variable after a resize + */ + private resizeDebounceId_ = INITIAL_VALUE; + + /* istanbul ignore next: optional argument is not a branch statement */ + constructor(adapter?: Partial) { super(adapter); - /** - * Used for diffs of current scroll position vs previous scroll position - * @private {number} - */ - this.lastScrollPosition_ = this.adapter_.getViewportScrollY(); - /** - * Used to verify when the top app bar is completely showing or completely hidden - * @private {number} - */ + this.lastScrollPosition_ = this.adapter_.getViewportScrollY(); this.topAppBarHeight_ = this.adapter_.getTopAppBarHeight(); - /** - * wasDocked_ is used to indicate if the top app bar was docked in the previous - * scroll handler iteration. - * @private {boolean} - */ - this.wasDocked_ = true; - - /** - * isDockedShowing_ is used to indicate if the top app bar is docked in the fully - * shown position. - * @private {boolean} - */ - this.isDockedShowing_ = true; - - /** - * Variable for current scroll position of the top app bar - * @private {number} - */ - this.currentAppBarOffsetTop_ = 0; - - /** - * Used to prevent the top app bar from being scrolled out of view during resize events - * @private {boolean} */ - this.isCurrentlyBeingResized_ = false; - - /** - * The timeout that's used to throttle the resize events - * @private {number} - */ - this.resizeThrottleId_ = INITIAL_VALUE; - - /** - * The timeout that's used to debounce toggling the isCurrentlyBeingResized_ variable after a resize - * @private {number} - */ - this.resizeDebounceId_ = INITIAL_VALUE; - this.scrollHandler_ = () => this.topAppBarScrollHandler_(); this.resizeHandler_ = () => this.topAppBarResizeHandler_(); } - init() { - super.init(); - this.adapter_.registerScrollHandler(this.scrollHandler_); - this.adapter_.registerResizeHandler(this.resizeHandler_); - } - destroy() { super.destroy(); - this.adapter_.deregisterScrollHandler(this.scrollHandler_); - this.adapter_.deregisterResizeHandler(this.resizeHandler_); this.adapter_.setStyle('top', ''); } /** * Function to determine if the DOM needs to update. - * @return {boolean} - * @private */ - checkForUpdate_() { + private checkForUpdate_(): boolean { const offscreenBoundaryTop = -this.topAppBarHeight_; const hasAnyPixelsOffscreen = this.currentAppBarOffsetTop_ < 0; const hasAnyPixelsOnscreen = this.currentAppBarOffsetTop_ > offscreenBoundaryTop; @@ -132,9 +112,8 @@ class MDCTopAppBarFoundation extends MDCTopAppBarBaseFoundation { /** * Function to move the top app bar if needed. - * @private */ - moveTopAppBar_() { + private moveTopAppBar_() { if (this.checkForUpdate_()) { // Once the top app bar is fully hidden we use the max potential top app bar height as our offset // so the top app bar doesn't show if the window resizes and the new height > the old height. @@ -149,9 +128,8 @@ class MDCTopAppBarFoundation extends MDCTopAppBarBaseFoundation { /** * Scroll handler for the default scroll behavior of the top app bar. - * @private */ - topAppBarScrollHandler_() { + private topAppBarScrollHandler_() { const currentScrollPosition = Math.max(this.adapter_.getViewportScrollY(), 0); const diff = currentScrollPosition - this.lastScrollPosition_; this.lastScrollPosition_ = currentScrollPosition; @@ -173,9 +151,8 @@ class MDCTopAppBarFoundation extends MDCTopAppBarBaseFoundation { /** * Top app bar resize handler that throttle/debounce functions that execute updates. - * @private */ - topAppBarResizeHandler_() { + private topAppBarResizeHandler_() { // Throttle resize events 10 p/s if (!this.resizeThrottleId_) { this.resizeThrottleId_ = setTimeout(() => { @@ -200,9 +177,8 @@ class MDCTopAppBarFoundation extends MDCTopAppBarBaseFoundation { /** * Throttled function that updates the top app bar scrolled values if the * top app bar height changes. - * @private */ - throttledResizeHandler_() { + private throttledResizeHandler_() { const currentHeight = this.adapter_.getTopAppBarHeight(); if (this.topAppBarHeight_ !== currentHeight) { this.wasDocked_ = false; @@ -217,4 +193,4 @@ class MDCTopAppBarFoundation extends MDCTopAppBarBaseFoundation { } } -export default MDCTopAppBarFoundation; +export {MDCTopAppBarFoundation as default, MDCTopAppBarFoundation}; diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index f5992945d81..b49114d40ff 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -185,7 +185,7 @@ class JsBundleFactory { tabs: getAbsolutePath('/packages/mdc-tabs/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'), + topAppBar: getAbsolutePath('/packages/mdc-top-app-bar/index.ts'), }, output: { fsDirAbsolutePath, diff --git a/test/unit/mdc-top-app-bar/foundation.test.js b/test/unit/mdc-top-app-bar/foundation.test.js index fdeee603786..5b8094429fb 100644 --- a/test/unit/mdc-top-app-bar/foundation.test.js +++ b/test/unit/mdc-top-app-bar/foundation.test.js @@ -27,7 +27,7 @@ import {captureHandlers} from '../helpers/foundation'; import {verifyDefaultAdapter} from '../helpers/foundation'; import MDCTopAppBarBaseFoundation from '../../../packages/mdc-top-app-bar/foundation'; -import {strings, cssClasses} from '../../../packages/mdc-top-app-bar/constants'; +import {cssClasses, numbers, strings} from '../../../packages/mdc-top-app-bar/constants'; suite('MDCTopAppBarBaseFoundation'); @@ -41,6 +41,11 @@ test('exports cssClasses', () => { assert.deepEqual(MDCTopAppBarBaseFoundation.cssClasses, cssClasses); }); +test('exports numbers', () => { + assert.isTrue('numbers' in MDCTopAppBarBaseFoundation); + assert.deepEqual(MDCTopAppBarBaseFoundation.numbers, numbers); +}); + test('defaultAdapter returns a complete adapter implementation', () => { verifyDefaultAdapter(MDCTopAppBarBaseFoundation, [ 'hasClass', 'addClass', 'removeClass', 'setStyle', 'getTopAppBarHeight', 'registerNavigationIconInteractionHandler', diff --git a/test/unit/mdc-top-app-bar/mdc-top-app-bar.test.js b/test/unit/mdc-top-app-bar/mdc-top-app-bar.test.js index 4fa9627bc45..90b231fa679 100644 --- a/test/unit/mdc-top-app-bar/mdc-top-app-bar.test.js +++ b/test/unit/mdc-top-app-bar/mdc-top-app-bar.test.js @@ -123,13 +123,21 @@ test('destroy destroys icon ripples', () => { test('#setScrollTarget deregisters and registers scroll handler on provided target', () => { const {component} = setupTest(); - const fakeTarget = {}; + const fakeTarget1 = document.createElement('div'); + const fakeTarget2 = document.createElement('div'); + + component.setScrollTarget(fakeTarget1); + assert.equal(component.scrollTarget_, fakeTarget1); + component.foundation_.destroyScrollHandler = td.func(); component.foundation_.initScrollHandler = td.func(); - component.setScrollTarget(fakeTarget); + + component.setScrollTarget(fakeTarget2); + td.verify(component.foundation_.destroyScrollHandler(), {times: 1}); td.verify(component.foundation_.initScrollHandler(), {times: 1}); - assert.equal(component.scrollTarget_, fakeTarget); + + assert.equal(component.scrollTarget_, fakeTarget2); }); test('getDefaultFoundation returns the appropriate foundation for default', () => {