From 667aeee6cf84514af3b167f4abce2de3e1eb4379 Mon Sep 17 00:00:00 2001 From: "Andrew C. Dvorak" Date: Mon, 11 Feb 2019 19:07:31 -0800 Subject: [PATCH 1/4] feat(tab-scroller): Convert JS to TypeScript (#4392) Refs #4225 --- packages/mdc-tab-scroller/adapter.js | 151 ------------- packages/mdc-tab-scroller/adapter.ts | 114 ++++++++++ .../{constants.js => constants.ts} | 4 +- .../{foundation.js => foundation.ts} | 200 +++++++----------- .../mdc-tab-scroller/{index.js => index.ts} | 78 +++---- packages/mdc-tab-scroller/package.json | 1 + ...lt-scroller.js => rtl-default-scroller.ts} | 61 ++---- ...e-scroller.js => rtl-negative-scroller.ts} | 63 ++---- ...se-scroller.js => rtl-reverse-scroller.ts} | 62 ++---- .../{rtl-scroller.js => rtl-scroller.ts} | 47 ++-- packages/mdc-tab-scroller/types.ts | 49 +++++ .../mdc-tab-scroller/{util.js => util.ts} | 34 +-- scripts/webpack/js-bundle-factory.js | 2 +- .../mdc-tab-scroller/rtl-scroller.test.js | 56 ----- 14 files changed, 350 insertions(+), 572 deletions(-) delete mode 100644 packages/mdc-tab-scroller/adapter.js create mode 100644 packages/mdc-tab-scroller/adapter.ts rename packages/mdc-tab-scroller/{constants.js => constants.ts} (97%) rename packages/mdc-tab-scroller/{foundation.js => foundation.ts} (70%) rename packages/mdc-tab-scroller/{index.js => index.ts} (70%) rename packages/mdc-tab-scroller/{rtl-default-scroller.js => rtl-default-scroller.ts} (70%) rename packages/mdc-tab-scroller/{rtl-negative-scroller.js => rtl-negative-scroller.ts} (66%) rename packages/mdc-tab-scroller/{rtl-reverse-scroller.js => rtl-reverse-scroller.ts} (68%) rename packages/mdc-tab-scroller/{rtl-scroller.js => rtl-scroller.ts} (54%) create mode 100644 packages/mdc-tab-scroller/types.ts rename packages/mdc-tab-scroller/{util.js => util.ts} (69%) delete mode 100644 test/unit/mdc-tab-scroller/rtl-scroller.test.js diff --git a/packages/mdc-tab-scroller/adapter.js b/packages/mdc-tab-scroller/adapter.js deleted file mode 100644 index ab1fce28048..00000000000 --- a/packages/mdc-tab-scroller/adapter.js +++ /dev/null @@ -1,151 +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. - */ - -/* eslint no-unused-vars: [2, {"args": "none"}] */ - -/** - * MDCTabScrollerAnimation contains the values required for animating from the - * current scroll position to the new scroll position. The "finalScrollPosition" - * value represents the new scroll position while the "scrollDelta" value is the - * corresponding transformation that is applied to the scroll content. Together, - * they create the animation by first updating the scroll value then applying - * the transformation and animating the transition. Both pieces are necessary - * for the scroll animation to work. The values are used as-is by the tab - * scroller animation method, ensuring that all logic for determining scroll - * position or transformation is abstracted away from the animation method. - * @typedef {{finalScrollPosition: number, scrollDelta: number}} - */ -let MDCTabScrollerAnimation; - -/** - * MDCTabScrollerHorizontalEdges represents the left and right edges of the - * scroll content. These values vary depending on how scrolling in RTL is - * implemented by the browser. One value is always 0 and one value is always - * the max scrollable value as either a positive or negative integer. - * @typedef {{left: number, right: number}} - */ -let MDCTabScrollerHorizontalEdges; - -/** - * Adapter for MDC Tab Scroller. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the Tab into your framework. See - * https://github.com/material-components/material-components-web/blob/master/docs/authoring-components.md - * for more information. - * - * @record - */ -class MDCTabScrollerAdapter { - /** - * Adds the given className to the root element. - * @param {string} className The className to add - */ - addClass(className) {} - - /** - * Removes the given className from the root element. - * @param {string} className The className to remove - */ - removeClass(className) {} - - /** - * Adds the given className to the scroll area element. - * @param {string} className The className to add - */ - addScrollAreaClass(className) {} - - /** - * Returns whether the event target matches given className. - * @param {EventTarget} evtTarget The event target - * @param {string} selector The selector to check - * @return {boolean} - */ - eventTargetMatchesSelector(evtTarget, selector) {} - - /** - * Sets a style property of the area element to the passed value. - * @param {string} propName The style property name to set - * @param {string} value The style property value - */ - setScrollAreaStyleProperty(propName, value) {} - - /** - * Sets a style property of the content element to the passed value. - * @param {string} propName The style property name to set - * @param {string} value The style property value - */ - setScrollContentStyleProperty(propName, value) {} - - /** - * Returns the scroll content element's computed style value of the given css property `propertyName`. - * We achieve this via `getComputedStyle(...).getPropertyValue(propertyName)`. - * @param {string} propertyName - * @return {string} - */ - getScrollContentStyleValue(propertyName) {} - - /** - * Sets the scrollLeft value of the scroll area element to the passed value. - * @param {number} scrollLeft The new scrollLeft value - */ - setScrollAreaScrollLeft(scrollLeft) {} - - /** - * Returns the scrollLeft value of the scroll area element. - * @return {number} - */ - getScrollAreaScrollLeft() {} - - /** - * Returns the offsetWidth of the scroll content element. - * @return {number} - */ - getScrollContentOffsetWidth() {} - - /** - * Returns the offsetWitdth of the scroll area element. - * @return {number} - */ - getScrollAreaOffsetWidth() {} - - /** - * Returns the bounding client rect of the scroll area element. - * @return {!ClientRect} - */ - computeScrollAreaClientRect() {} - - /** - * Returns the bounding client rect of the scroll content element. - * @return {!ClientRect} - */ - computeScrollContentClientRect() {} - - /** - * Returns the height of the browser's horizontal scrollbars (in px). - * @return {number} - */ - computeHorizontalScrollbarHeight() {} -} - -export {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges, MDCTabScrollerAdapter}; diff --git a/packages/mdc-tab-scroller/adapter.ts b/packages/mdc-tab-scroller/adapter.ts new file mode 100644 index 00000000000..430ef725345 --- /dev/null +++ b/packages/mdc-tab-scroller/adapter.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. + */ + +/** + * 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 + */ +interface MDCTabScrollerAdapter { + /** + * Adds the given className to the root element. + * @param className The className to add + */ + addClass(className: string): void; + + /** + * Removes the given className from the root element. + * @param className The className to remove + */ + removeClass(className: string): void; + + /** + * Adds the given className to the scroll area element. + * @param className The className to add + */ + addScrollAreaClass(className: string): void; + + /** + * Returns whether the event target matches given className. + * @param evtTarget The event target + * @param selector The selector to check + */ + eventTargetMatchesSelector(evtTarget: EventTarget, selector: string): boolean; + + /** + * Sets a style property of the area element to the passed value. + * @param propName The style property name to set + * @param value The style property value + */ + setScrollAreaStyleProperty(propName: string, value: string): void; + + /** + * Sets a style property of the content element to the passed value. + * @param propName The style property name to set + * @param value The style property value + */ + setScrollContentStyleProperty(propName: string, value: string): void; + + /** + * Returns the scroll content element's computed style value of the given css property `propertyName`. + * We achieve this via `getComputedStyle(...).getPropertyValue(propertyName)`. + */ + getScrollContentStyleValue(propertyName: string): string; + + /** + * Sets the scrollLeft value of the scroll area element to the passed value. + * @param scrollLeft The new scrollLeft value + */ + setScrollAreaScrollLeft(scrollLeft: number): void; + + /** + * Returns the scrollLeft value of the scroll area element. + */ + getScrollAreaScrollLeft(): number; + + /** + * Returns the offsetWidth of the scroll content element. + */ + getScrollContentOffsetWidth(): number; + + /** + * Returns the offsetWitdth of the scroll area element. + */ + getScrollAreaOffsetWidth(): number; + + /** + * Returns the bounding client rect of the scroll area element. + */ + computeScrollAreaClientRect(): ClientRect; + + /** + * Returns the bounding client rect of the scroll content element. + */ + computeScrollContentClientRect(): ClientRect; + + /** + * Returns the height of the browser's horizontal scrollbars (in px). + */ + computeHorizontalScrollbarHeight(): number; +} + +export {MDCTabScrollerAdapter as default, MDCTabScrollerAdapter}; diff --git a/packages/mdc-tab-scroller/constants.js b/packages/mdc-tab-scroller/constants.ts similarity index 97% rename from packages/mdc-tab-scroller/constants.js rename to packages/mdc-tab-scroller/constants.ts index f7f4da6bd83..ff158626ea2 100644 --- a/packages/mdc-tab-scroller/constants.js +++ b/packages/mdc-tab-scroller/constants.ts @@ -21,14 +21,12 @@ * THE SOFTWARE. */ -/** @enum {string} */ const cssClasses = { ANIMATING: 'mdc-tab-scroller--animating', - SCROLL_TEST: 'mdc-tab-scroller__test', SCROLL_AREA_SCROLL: 'mdc-tab-scroller__scroll-area--scroll', + SCROLL_TEST: 'mdc-tab-scroller__test', }; -/** @enum {string} */ const strings = { AREA_SELECTOR: '.mdc-tab-scroller__scroll-area', CONTENT_SELECTOR: '.mdc-tab-scroller__scroll-content', diff --git a/packages/mdc-tab-scroller/foundation.js b/packages/mdc-tab-scroller/foundation.ts similarity index 70% rename from packages/mdc-tab-scroller/foundation.js rename to packages/mdc-tab-scroller/foundation.ts index 7e39a6145f2..1a52fd9720b 100644 --- a/packages/mdc-tab-scroller/foundation.js +++ b/packages/mdc-tab-scroller/foundation.ts @@ -22,69 +22,57 @@ */ import {MDCFoundation} from '@material/base/foundation'; +import {MDCTabScrollerAdapter} from './adapter'; import {cssClasses, strings} from './constants'; -/* eslint-disable no-unused-vars */ -import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges, MDCTabScrollerAdapter} from './adapter'; -import MDCTabScrollerRTL from './rtl-scroller'; -/* eslint-enable no-unused-vars */ -import MDCTabScrollerRTLDefault from './rtl-default-scroller'; -import MDCTabScrollerRTLNegative from './rtl-negative-scroller'; -import MDCTabScrollerRTLReverse from './rtl-reverse-scroller'; +import {MDCTabScrollerRTLDefault} from './rtl-default-scroller'; +import {MDCTabScrollerRTLNegative} from './rtl-negative-scroller'; +import {MDCTabScrollerRTLReverse} from './rtl-reverse-scroller'; +import {MDCTabScrollerRTL} from './rtl-scroller'; +import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './types'; -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTabScrollerFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTabScrollerFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } - /** @return enum {string} */ static get strings() { return strings; } - /** - * @see MDCTabScrollerAdapter for typing information - * @return {!MDCTabScrollerAdapter} - */ - static get defaultAdapter() { - return /** @type {!MDCTabScrollerAdapter} */ ({ - eventTargetMatchesSelector: () => {}, - addClass: () => {}, - removeClass: () => {}, - addScrollAreaClass: () => {}, - setScrollAreaStyleProperty: () => {}, - setScrollContentStyleProperty: () => {}, - getScrollContentStyleValue: () => {}, - setScrollAreaScrollLeft: () => {}, - getScrollAreaScrollLeft: () => {}, - getScrollContentOffsetWidth: () => {}, - getScrollAreaOffsetWidth: () => {}, - computeScrollAreaClientRect: () => {}, - computeScrollContentClientRect: () => {}, - computeHorizontalScrollbarHeight: () => {}, - }); + static get defaultAdapter(): MDCTabScrollerAdapter { + // tslint:disable:object-literal-sort-keys + return { + eventTargetMatchesSelector: () => false, + addClass: () => undefined, + removeClass: () => undefined, + addScrollAreaClass: () => undefined, + setScrollAreaStyleProperty: () => undefined, + setScrollContentStyleProperty: () => undefined, + getScrollContentStyleValue: () => '', + setScrollAreaScrollLeft: () => undefined, + getScrollAreaScrollLeft: () => 0, + getScrollContentOffsetWidth: () => 0, + getScrollAreaOffsetWidth: () => 0, + computeScrollAreaClientRect: () => ({top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0}), + computeScrollContentClientRect: () => ({top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0}), + computeHorizontalScrollbarHeight: () => 0, + }; + // tslint:enable:object-literal-sort-keys } - /** @param {!MDCTabScrollerAdapter} adapter */ - constructor(adapter) { - super(Object.assign(MDCTabScrollerFoundation.defaultAdapter, adapter)); + /** + * Controls whether we should handle the transitionend and interaction events during the animation. + */ + private isAnimating_ = false; - /** - * This boolean controls whether we should handle the transitionend and interaction events during the animation. - * @private {boolean} - */ - this.isAnimating_ = false; + /** + * The MDCTabScrollerRTL instance varies per browser and allows us to encapsulate the peculiar browser behavior + * of RTL scrolling in it's own class. + */ + private rtlScrollerInstance_?: MDCTabScrollerRTL; - /** - * The MDCTabScrollerRTL instance varies per browser and allows us to encapsulate the peculiar browser behavior - * of RTL scrolling in it's own class. - * @private {?MDCTabScrollerRTL} - */ - this.rtlScrollerInstance_; + constructor(adapter?: Partial) { + super({...MDCTabScrollerFoundation.defaultAdapter, ...adapter}); } init() { @@ -97,9 +85,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Computes the current visual scroll position - * @return {number} */ - getScrollPosition() { + getScrollPosition(): number { if (this.isRTL_()) { return this.computeCurrentScrollPositionRTL_(); } @@ -124,12 +111,12 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Handles the transitionend event - * @param {!Event} evt */ - handleTransitionEnd(evt) { + handleTransitionEnd(evt: Event) { // Early exit if we aren't animating or the event was triggered by a different element. - if (!this.isAnimating_ - || !this.adapter_.eventTargetMatchesSelector(evt.target, MDCTabScrollerFoundation.strings.CONTENT_SELECTOR)) { + const evtTarget = evt.target as Element; + if (!this.isAnimating_ || + !this.adapter_.eventTargetMatchesSelector(evtTarget, MDCTabScrollerFoundation.strings.CONTENT_SELECTOR)) { return; } @@ -139,9 +126,9 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Increment the scroll value by the scrollXIncrement - * @param {number} scrollXIncrement The value by which to increment the scroll position + * @param scrollXIncrement The value by which to increment the scroll position */ - incrementScroll(scrollXIncrement) { + incrementScroll(scrollXIncrement: number) { // Early exit for non-operational increment values if (scrollXIncrement === 0) { return; @@ -156,9 +143,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Scrolls to the given scrollX value - * @param {number} scrollX */ - scrollTo(scrollX) { + scrollTo(scrollX: number) { if (this.isRTL_()) { return this.scrollToRTL_(scrollX); } @@ -168,9 +154,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Returns the appropriate version of the MDCTabScrollerRTL - * @return {!MDCTabScrollerRTL} */ - getRTLScroller() { + getRTLScroller(): MDCTabScrollerRTL { if (!this.rtlScrollerInstance_) { this.rtlScrollerInstance_ = this.rtlScrollerFactory_(); } @@ -180,10 +165,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Returns the translateX value from a CSS matrix transform function string - * @return {number} - * @private */ - calculateCurrentTranslateX_() { + private calculateCurrentTranslateX_(): number { const transformValue = this.adapter_.getScrollContentStyleValue('transform'); // Early exit if no transform is present if (transformValue === 'none') { @@ -194,101 +177,89 @@ class MDCTabScrollerFoundation extends MDCFoundation { // of `matrix(a, b, c, d, tx, ty)`. We only care about tx (translateX) so // we're going to grab all the parenthesized values, strip out tx, and // parse it. - const results = /\((.+)\)/.exec(transformValue)[1]; - const parts = results.split(','); - return parseFloat(parts[4]); + const [, matrixParams] = /\((.+?)\)/.exec(transformValue); + + // @ts-ignore + const [a, b, c, d, tx, ty] = matrixParams.split(','); + + return parseFloat(tx); // tslint:disable-line:ban } /** * Calculates a safe scroll value that is > 0 and < the max scroll value - * @param {number} scrollX The distance to scroll - * @return {number} - * @private + * @param scrollX The distance to scroll */ - clampScrollValue_(scrollX) { + private clampScrollValue_(scrollX: number): number { const edges = this.calculateScrollEdges_(); return Math.min(Math.max(edges.left, scrollX), edges.right); } - /** - * @return {number} - * @private - */ - computeCurrentScrollPositionRTL_() { + private computeCurrentScrollPositionRTL_(): number { const translateX = this.calculateCurrentTranslateX_(); return this.getRTLScroller().getScrollPositionRTL(translateX); } - /** - * @return {!MDCTabScrollerHorizontalEdges} - * @private - */ - calculateScrollEdges_() { + private calculateScrollEdges_(): MDCTabScrollerHorizontalEdges { const contentWidth = this.adapter_.getScrollContentOffsetWidth(); const rootWidth = this.adapter_.getScrollAreaOffsetWidth(); - return /** @type {!MDCTabScrollerHorizontalEdges} */ ({ + return { left: 0, right: contentWidth - rootWidth, - }); + }; } /** * Internal scroll method - * @param {number} scrollX The new scroll position - * @private + * @param scrollX The new scroll position */ - scrollTo_(scrollX) { + private scrollTo_(scrollX: number) { const currentScrollX = this.getScrollPosition(); const safeScrollX = this.clampScrollValue_(scrollX); const scrollDelta = safeScrollX - currentScrollX; - this.animate_(/** @type {!MDCTabScrollerAnimation} */ ({ + this.animate_({ finalScrollPosition: safeScrollX, - scrollDelta: scrollDelta, - })); + scrollDelta, + }); } /** * Internal RTL scroll method - * @param {number} scrollX The new scroll position - * @private + * @param scrollX The new scroll position */ - scrollToRTL_(scrollX) { + private scrollToRTL_(scrollX: number) { const animation = this.getRTLScroller().scrollToRTL(scrollX); this.animate_(animation); } /** * Internal increment scroll method - * @param {number} scrollX The new scroll position increment - * @private + * @param scrollX The new scroll position increment */ - incrementScroll_(scrollX) { + private incrementScroll_(scrollX: number) { const currentScrollX = this.getScrollPosition(); const targetScrollX = scrollX + currentScrollX; const safeScrollX = this.clampScrollValue_(targetScrollX); const scrollDelta = safeScrollX - currentScrollX; - this.animate_(/** @type {!MDCTabScrollerAnimation} */ ({ + this.animate_({ finalScrollPosition: safeScrollX, - scrollDelta: scrollDelta, - })); + scrollDelta, + }); } /** - * Internal incremenet scroll RTL method - * @param {number} scrollX The new scroll position RTL increment - * @private + * Internal increment scroll RTL method + * @param scrollX The new scroll position RTL increment */ - incrementScrollRTL_(scrollX) { + private incrementScrollRTL_(scrollX: number) { const animation = this.getRTLScroller().incrementScrollRTL(scrollX); this.animate_(animation); } /** * Animates the tab scrolling - * @param {!MDCTabScrollerAnimation} animation The animation to apply - * @private + * @param animation The animation to apply */ - animate_(animation) { + private animate_(animation: MDCTabScrollerAnimation) { // Early exit if translateX is 0, which means there's no animation to perform if (animation.scrollDelta === 0) { return; @@ -312,9 +283,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Stops scroll animation - * @private */ - stopScrollAnimation_() { + private stopScrollAnimation_() { this.isAnimating_ = false; const currentScrollPosition = this.getAnimatingScrollPosition_(); this.adapter_.removeClass(MDCTabScrollerFoundation.cssClasses.ANIMATING); @@ -324,10 +294,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Gets the current scroll position during animation - * @return {number} - * @private */ - getAnimatingScrollPosition_() { + private getAnimatingScrollPosition_(): number { const currentTranslateX = this.calculateCurrentTranslateX_(); const scrollLeft = this.adapter_.getScrollAreaScrollLeft(); if (this.isRTL_()) { @@ -339,10 +307,8 @@ class MDCTabScrollerFoundation extends MDCFoundation { /** * Determines the RTL Scroller to use - * @return {!MDCTabScrollerRTL} - * @private */ - rtlScrollerFactory_() { + private rtlScrollerFactory_(): MDCTabScrollerRTL { // Browsers have three different implementations of scrollLeft in RTL mode, // dependent on the browser. The behavior is based off the max LTR // scrollleft value and 0. @@ -390,13 +356,9 @@ class MDCTabScrollerFoundation extends MDCFoundation { return new MDCTabScrollerRTLDefault(this.adapter_); } - /** - * @return {boolean} - * @private - */ - isRTL_() { + private isRTL_(): boolean { return this.adapter_.getScrollContentStyleValue('direction') === 'rtl'; } } -export default MDCTabScrollerFoundation; +export {MDCTabScrollerFoundation as default, MDCTabScrollerFoundation}; diff --git a/packages/mdc-tab-scroller/index.js b/packages/mdc-tab-scroller/index.ts similarity index 70% rename from packages/mdc-tab-scroller/index.js rename to packages/mdc-tab-scroller/index.ts index 53371e31d6f..4fc80edf16c 100644 --- a/packages/mdc-tab-scroller/index.js +++ b/packages/mdc-tab-scroller/index.ts @@ -22,43 +22,26 @@ */ import {MDCComponent} from '@material/base/component'; - -import {MDCTabScrollerAdapter} from './adapter'; -import MDCTabScrollerFoundation from './foundation'; +import {SpecificEventListener} from '@material/base/types'; +import {ponyfill} from '@material/dom/index'; +import {MDCTabScrollerFoundation} from './foundation'; import * as util from './util'; -/** - * @extends {MDCComponent} - * @final - */ -class MDCTabScroller extends MDCComponent { - /** - * @param {!Element} root - * @return {!MDCTabScroller} - */ - static attachTo(root) { +type InteractionEventType = 'wheel' | 'touchstart' | 'pointerdown' | 'mousedown' | 'keydown'; + +class MDCTabScroller extends MDCComponent { + static attachTo(root: Element): MDCTabScroller { return new MDCTabScroller(root); } - constructor(...args) { - super(...args); - - /** @private {?Element} */ - this.content_; - - /** @private {?Element} */ - this.area_; - - /** @private {?function(?Event): undefined} */ - this.handleInteraction_; - - /** @private {?function(!Event): undefined} */ - this.handleTransitionEnd_; - } + private content_!: HTMLElement; // assigned in initialize() + private area_!: HTMLElement; // assigned in initialize() + private handleInteraction_!: SpecificEventListener; // assigned in initialSyncWithDOM() + private handleTransitionEnd_!: SpecificEventListener<'transitionend'>; // assigned in initialSyncWithDOM() initialize() { - this.area_ = this.root_.querySelector(MDCTabScrollerFoundation.strings.AREA_SELECTOR); - this.content_ = this.root_.querySelector(MDCTabScrollerFoundation.strings.CONTENT_SELECTOR); + this.area_ = this.root_.querySelector(MDCTabScrollerFoundation.strings.AREA_SELECTOR)!; + this.content_ = this.root_.querySelector(MDCTabScrollerFoundation.strings.CONTENT_SELECTOR)!; } initialSyncWithDOM() { @@ -84,15 +67,10 @@ class MDCTabScroller extends MDCComponent { this.content_.removeEventListener('transitionend', this.handleTransitionEnd_); } - /** - * @return {!MDCTabScrollerFoundation} - */ - getDefaultFoundation() { - const adapter = /** @type {!MDCTabScrollerAdapter} */ ({ - eventTargetMatchesSelector: (evtTarget, selector) => { - const MATCHES = util.getMatchesProperty(HTMLElement.prototype); - return evtTarget[MATCHES](selector); - }, + getDefaultFoundation(): MDCTabScrollerFoundation { + // tslint:disable:object-literal-sort-keys + return new MDCTabScrollerFoundation({ + eventTargetMatchesSelector: (evtTarget, selector) => ponyfill.matches(evtTarget as Element, selector), addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), addScrollAreaClass: (className) => this.area_.classList.add(className), @@ -107,41 +85,41 @@ class MDCTabScroller extends MDCComponent { computeScrollContentClientRect: () => this.content_.getBoundingClientRect(), computeHorizontalScrollbarHeight: () => util.computeHorizontalScrollbarHeight(document), }); - - return new MDCTabScrollerFoundation(adapter); + // tslint:enable:object-literal-sort-keys } /** * Returns the current visual scroll position - * @return {number} */ - getScrollPosition() { + getScrollPosition(): number { return this.foundation_.getScrollPosition(); } /** * Returns the width of the scroll content - * @return {number} */ - getScrollContentWidth() { + getScrollContentWidth(): number { return this.content_.offsetWidth; } /** * Increments the scroll value by the given amount - * @param {number} scrollXIncrement The pixel value by which to increment the scroll value + * @param scrollXIncrement The pixel value by which to increment the scroll value */ - incrementScroll(scrollXIncrement) { + incrementScroll(scrollXIncrement: number) { this.foundation_.incrementScroll(scrollXIncrement); } /** * Scrolls to the given pixel position - * @param {number} scrollX The pixel value to scroll to + * @param scrollX The pixel value to scroll to */ - scrollTo(scrollX) { + scrollTo(scrollX: number) { this.foundation_.scrollTo(scrollX); } } -export {MDCTabScroller, MDCTabScrollerFoundation, util}; +export {MDCTabScroller as default, MDCTabScroller, util}; +export * from './adapter'; +export * from './foundation'; +export * from './types'; diff --git a/packages/mdc-tab-scroller/package.json b/packages/mdc-tab-scroller/package.json index bc4043b9056..0e02117c97d 100644 --- a/packages/mdc-tab-scroller/package.json +++ b/packages/mdc-tab-scroller/package.json @@ -17,6 +17,7 @@ "dependencies": { "@material/animation": "^0.41.0", "@material/base": "^0.41.0", + "@material/dom": "^0.41.0", "@material/tab": "^0.44.0" }, "publishConfig": { diff --git a/packages/mdc-tab-scroller/rtl-default-scroller.js b/packages/mdc-tab-scroller/rtl-default-scroller.ts similarity index 70% rename from packages/mdc-tab-scroller/rtl-default-scroller.js rename to packages/mdc-tab-scroller/rtl-default-scroller.ts index 9baf2db0df9..083a6b5676a 100644 --- a/packages/mdc-tab-scroller/rtl-default-scroller.js +++ b/packages/mdc-tab-scroller/rtl-default-scroller.ts @@ -21,84 +21,53 @@ * THE SOFTWARE. */ -import MDCTabScrollerRTL from './rtl-scroller'; +import {MDCTabScrollerRTL} from './rtl-scroller'; +import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './types'; -/* eslint-disable no-unused-vars */ -import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './adapter'; -/* eslint-enable no-unused-vars */ - -/** - * @extends {MDCTabScrollerRTL} - * @final - */ class MDCTabScrollerRTLDefault extends MDCTabScrollerRTL { - /** - * @return {number} - */ - getScrollPositionRTL() { + getScrollPositionRTL(): number { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const {right} = this.calculateScrollEdges_(); // Scroll values on most browsers are ints instead of floats so we round return Math.round(right - currentScrollLeft); } - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - */ - scrollToRTL(scrollX) { + scrollToRTL(scrollX: number): MDCTabScrollerAnimation { const edges = this.calculateScrollEdges_(); const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const clampedScrollLeft = this.clampScrollValue_(edges.right - scrollX); - return /** @type {!MDCTabScrollerAnimation} */ ({ + return { finalScrollPosition: clampedScrollLeft, scrollDelta: clampedScrollLeft - currentScrollLeft, - }); + }; } - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - */ - incrementScrollRTL(scrollX) { + incrementScrollRTL(scrollX: number): MDCTabScrollerAnimation { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const clampedScrollLeft = this.clampScrollValue_(currentScrollLeft - scrollX); - return /** @type {!MDCTabScrollerAnimation} */ ({ + return { finalScrollPosition: clampedScrollLeft, scrollDelta: clampedScrollLeft - currentScrollLeft, - }); + }; } - /** - * @param {number} scrollX - * @return {number} - */ - getAnimatingScrollPosition(scrollX) { + getAnimatingScrollPosition(scrollX: number): number { return scrollX; } - /** - * @return {!MDCTabScrollerHorizontalEdges} - * @private - */ - calculateScrollEdges_() { + private calculateScrollEdges_(): MDCTabScrollerHorizontalEdges { const contentWidth = this.adapter_.getScrollContentOffsetWidth(); const rootWidth = this.adapter_.getScrollAreaOffsetWidth(); - return /** @type {!MDCTabScrollerHorizontalEdges} */ ({ + return { left: 0, right: contentWidth - rootWidth, - }); + }; } - /** - * @param {number} scrollX - * @return {number} - * @private - */ - clampScrollValue_(scrollX) { + private clampScrollValue_(scrollX: number): number { const edges = this.calculateScrollEdges_(); return Math.min(Math.max(edges.left, scrollX), edges.right); } } -export default MDCTabScrollerRTLDefault; +export {MDCTabScrollerRTLDefault as default, MDCTabScrollerRTLDefault}; diff --git a/packages/mdc-tab-scroller/rtl-negative-scroller.js b/packages/mdc-tab-scroller/rtl-negative-scroller.ts similarity index 66% rename from packages/mdc-tab-scroller/rtl-negative-scroller.js rename to packages/mdc-tab-scroller/rtl-negative-scroller.ts index 6a0d1636629..afb7f5e38ff 100644 --- a/packages/mdc-tab-scroller/rtl-negative-scroller.js +++ b/packages/mdc-tab-scroller/rtl-negative-scroller.ts @@ -21,83 +21,50 @@ * THE SOFTWARE. */ -import MDCTabScrollerRTL from './rtl-scroller'; +import {MDCTabScrollerRTL} from './rtl-scroller'; +import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './types'; -/* eslint-disable no-unused-vars */ -import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './adapter'; -/* eslint-enable no-unused-vars */ - -/** - * @extends {MDCTabScrollerRTL} - * @final - */ class MDCTabScrollerRTLNegative extends MDCTabScrollerRTL { - /** - * @param {number} translateX The current translateX position - * @return {number} - */ - getScrollPositionRTL(translateX) { + getScrollPositionRTL(translateX: number): number { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); return Math.round(translateX - currentScrollLeft); } - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - */ - scrollToRTL(scrollX) { + scrollToRTL(scrollX: number): MDCTabScrollerAnimation { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const clampedScrollLeft = this.clampScrollValue_(-scrollX); - return /** @type {!MDCTabScrollerAnimation} */ ({ + return { finalScrollPosition: clampedScrollLeft, scrollDelta: clampedScrollLeft - currentScrollLeft, - }); + }; } - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - */ - incrementScrollRTL(scrollX) { + incrementScrollRTL(scrollX: number): MDCTabScrollerAnimation { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const clampedScrollLeft = this.clampScrollValue_(currentScrollLeft - scrollX); - return /** @type {!MDCTabScrollerAnimation} */ ({ + return { finalScrollPosition: clampedScrollLeft, scrollDelta: clampedScrollLeft - currentScrollLeft, - }); + }; } - /** - * @param {number} scrollX - * @param {number} translateX - * @return {number} - */ - getAnimatingScrollPosition(scrollX, translateX) { + getAnimatingScrollPosition(scrollX: number, translateX: number): number { return scrollX - translateX; } - /** - * @return {!MDCTabScrollerHorizontalEdges} - * @private - */ - calculateScrollEdges_() { + private calculateScrollEdges_(): MDCTabScrollerHorizontalEdges { const contentWidth = this.adapter_.getScrollContentOffsetWidth(); const rootWidth = this.adapter_.getScrollAreaOffsetWidth(); - return /** @type {!MDCTabScrollerHorizontalEdges} */ ({ + return { left: rootWidth - contentWidth, right: 0, - }); + }; } - /** - * @param {number} scrollX - * @return {number} - * @private - */ - clampScrollValue_(scrollX) { + private clampScrollValue_(scrollX: number): number { const edges = this.calculateScrollEdges_(); return Math.max(Math.min(edges.right, scrollX), edges.left); } } -export default MDCTabScrollerRTLNegative; +export {MDCTabScrollerRTLNegative as default, MDCTabScrollerRTLNegative}; diff --git a/packages/mdc-tab-scroller/rtl-reverse-scroller.js b/packages/mdc-tab-scroller/rtl-reverse-scroller.ts similarity index 68% rename from packages/mdc-tab-scroller/rtl-reverse-scroller.js rename to packages/mdc-tab-scroller/rtl-reverse-scroller.ts index 49dc8707d76..83748615cf6 100644 --- a/packages/mdc-tab-scroller/rtl-reverse-scroller.js +++ b/packages/mdc-tab-scroller/rtl-reverse-scroller.ts @@ -21,83 +21,51 @@ * THE SOFTWARE. */ -import MDCTabScrollerRTL from './rtl-scroller'; +import {MDCTabScrollerRTL} from './rtl-scroller'; +import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './types'; -/* eslint-disable no-unused-vars */ -import {MDCTabScrollerAnimation, MDCTabScrollerHorizontalEdges} from './adapter'; -/* eslint-enable no-unused-vars */ - -/** - * @extends {MDCTabScrollerRTL} - * @final - */ class MDCTabScrollerRTLReverse extends MDCTabScrollerRTL { - /** - * @param {number} translateX - * @return {number} - */ - getScrollPositionRTL(translateX) { + getScrollPositionRTL(translateX: number): number { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); // Scroll values on most browsers are ints instead of floats so we round return Math.round(currentScrollLeft - translateX); } - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - */ - scrollToRTL(scrollX) { + scrollToRTL(scrollX: number): MDCTabScrollerAnimation { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const clampedScrollLeft = this.clampScrollValue_(scrollX); - return /** @type {!MDCTabScrollerAnimation} */ ({ + return { finalScrollPosition: clampedScrollLeft, scrollDelta: currentScrollLeft - clampedScrollLeft, - }); + }; } - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - */ - incrementScrollRTL(scrollX) { + incrementScrollRTL(scrollX: number): MDCTabScrollerAnimation { const currentScrollLeft = this.adapter_.getScrollAreaScrollLeft(); const clampedScrollLeft = this.clampScrollValue_(currentScrollLeft + scrollX); - return /** @type {!MDCTabScrollerAnimation} */ ({ + return { finalScrollPosition: clampedScrollLeft, scrollDelta: currentScrollLeft - clampedScrollLeft, - }); + }; } - /** - * @param {number} scrollX - * @return {number} - */ - getAnimatingScrollPosition(scrollX, translateX) { + getAnimatingScrollPosition(scrollX: number, translateX: number): number { return scrollX + translateX; } - /** - * @return {!MDCTabScrollerHorizontalEdges} - * @private - */ - calculateScrollEdges_() { + private calculateScrollEdges_(): MDCTabScrollerHorizontalEdges { const contentWidth = this.adapter_.getScrollContentOffsetWidth(); const rootWidth = this.adapter_.getScrollAreaOffsetWidth(); - return /** @type {!MDCTabScrollerHorizontalEdges} */ ({ + return { left: contentWidth - rootWidth, right: 0, - }); + }; } - /** - * @param {number} scrollX - * @return {number} - * @private - */ - clampScrollValue_(scrollX) { + private clampScrollValue_(scrollX: number): number { const edges = this.calculateScrollEdges_(); return Math.min(Math.max(edges.right, scrollX), edges.left); } } -export default MDCTabScrollerRTLReverse; +export {MDCTabScrollerRTLReverse as default, MDCTabScrollerRTLReverse}; diff --git a/packages/mdc-tab-scroller/rtl-scroller.js b/packages/mdc-tab-scroller/rtl-scroller.ts similarity index 54% rename from packages/mdc-tab-scroller/rtl-scroller.js rename to packages/mdc-tab-scroller/rtl-scroller.ts index 692db0f3aff..6453715de8e 100644 --- a/packages/mdc-tab-scroller/rtl-scroller.js +++ b/packages/mdc-tab-scroller/rtl-scroller.ts @@ -21,50 +21,27 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ +import {MDCTabScrollerAdapter} from './adapter'; +import {MDCTabScrollerAnimation} from './types'; -/* eslint-disable no-unused-vars */ -import {MDCTabScrollerAdapter, MDCTabScrollerAnimation} from './adapter'; -/* eslint-enable no-unused-vars */ +abstract class MDCTabScrollerRTL { + protected readonly adapter_: MDCTabScrollerAdapter; -/** - * @abstract - */ -class MDCTabScrollerRTL { - /** @param {!MDCTabScrollerAdapter} adapter */ - constructor(adapter) { - /** @private */ + constructor(adapter: MDCTabScrollerAdapter) { this.adapter_ = adapter; } - /** - * @param {number} translateX The current translateX position - * @return {number} - * @abstract - */ - getScrollPositionRTL(translateX) {} + abstract getScrollPositionRTL(translateX: number): number; - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - * @abstract - */ - scrollToRTL(scrollX) {} + abstract scrollToRTL(scrollX: number): MDCTabScrollerAnimation; - /** - * @param {number} scrollX - * @return {!MDCTabScrollerAnimation} - * @abstract - */ - incrementScrollRTL(scrollX) {} + abstract incrementScrollRTL(scrollX: number): MDCTabScrollerAnimation; /** - * @param {number} scrollX The current scrollX position - * @param {number} translateX The current translateX position - * @return {number} - * @abstract + * @param scrollX The current scrollX position + * @param translateX The current translateX position */ - getAnimatingScrollPosition(scrollX, translateX) {} + abstract getAnimatingScrollPosition(scrollX: number, translateX: number): number; } -export default MDCTabScrollerRTL; +export {MDCTabScrollerRTL as default, MDCTabScrollerRTL}; diff --git a/packages/mdc-tab-scroller/types.ts b/packages/mdc-tab-scroller/types.ts new file mode 100644 index 00000000000..c83e801e0fe --- /dev/null +++ b/packages/mdc-tab-scroller/types.ts @@ -0,0 +1,49 @@ +/** + * @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. + */ + +/** + * MDCTabScrollerAnimation contains the values required for animating from the + * current scroll position to the new scroll position. The "finalScrollPosition" + * value represents the new scroll position while the "scrollDelta" value is the + * corresponding transformation that is applied to the scroll content. Together, + * they create the animation by first updating the scroll value then applying + * the transformation and animating the transition. Both pieces are necessary + * for the scroll animation to work. The values are used as-is by the tab + * scroller animation method, ensuring that all logic for determining scroll + * position or transformation is abstracted away from the animation method. + */ +export interface MDCTabScrollerAnimation { + finalScrollPosition: number; + scrollDelta: number; +} + +/** + * MDCTabScrollerHorizontalEdges represents the left and right edges of the + * scroll content. These values vary depending on how scrolling in RTL is + * implemented by the browser. One value is always 0 and one value is always + * the max scrollable value as either a positive or negative integer. + */ +export interface MDCTabScrollerHorizontalEdges { + left: number; + right: number; +} diff --git a/packages/mdc-tab-scroller/util.js b/packages/mdc-tab-scroller/util.ts similarity index 69% rename from packages/mdc-tab-scroller/util.js rename to packages/mdc-tab-scroller/util.ts index ea43f4ac101..640b74d9a87 100644 --- a/packages/mdc-tab-scroller/util.js +++ b/packages/mdc-tab-scroller/util.ts @@ -25,18 +25,14 @@ import {cssClasses} from './constants'; /** * Stores result from computeHorizontalScrollbarHeight to avoid redundant processing. - * @private {number|undefined} */ -let horizontalScrollbarHeight_; +let horizontalScrollbarHeight_: number | undefined; /** * Computes the height of browser-rendered horizontal scrollbars using a self-created test element. * May return 0 (e.g. on OS X browsers under default configuration). - * @param {!Document} documentObj - * @param {boolean=} shouldCacheResult - * @return {number} */ -function computeHorizontalScrollbarHeight(documentObj, shouldCacheResult = true) { +export function computeHorizontalScrollbarHeight(documentObj: Document, shouldCacheResult = true): number { if (shouldCacheResult && typeof horizontalScrollbarHeight_ !== 'undefined') { return horizontalScrollbarHeight_; } @@ -54,14 +50,20 @@ function computeHorizontalScrollbarHeight(documentObj, shouldCacheResult = true) return horizontalScrollbarHeight; } -/** - * @param {!Object} HTMLElementPrototype - * @return {string} - */ -function getMatchesProperty(HTMLElementPrototype) { - return [ - 'msMatchesSelector', 'matches', - ].filter((p) => p in HTMLElementPrototype).pop(); -} +export type VendorMatchesFunctionName = 'webkitMatchesSelector' | 'msMatchesSelector'; +export type MatchesFunctionName = VendorMatchesFunctionName | 'matches'; -export {computeHorizontalScrollbarHeight, getMatchesProperty}; +export function getMatchesProperty(htmlElementPrototype: {}): MatchesFunctionName { + // Order is important because we return the first existing method we find. + // Do not change the order of the items in the below array. + const matchesMethods: MatchesFunctionName[] = ['matches', 'webkitMatchesSelector', 'msMatchesSelector']; + let method: MatchesFunctionName = 'matches'; + for (const matchesMethod of matchesMethods) { + if (matchesMethod in htmlElementPrototype) { + method = matchesMethod; + break; + } + } + + return method; +} diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index f91236916ed..cf7bb077fb1 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -181,7 +181,7 @@ class JsBundleFactory { tab: getAbsolutePath('/packages/mdc-tab/index.js'), tabBar: getAbsolutePath('/packages/mdc-tab-bar/index.js'), tabIndicator: getAbsolutePath('/packages/mdc-tab-indicator/index.js'), - tabScroller: getAbsolutePath('/packages/mdc-tab-scroller/index.js'), + tabScroller: getAbsolutePath('/packages/mdc-tab-scroller/index.ts'), tabs: getAbsolutePath('/packages/mdc-tabs/index.js'), textfield: getAbsolutePath('/packages/mdc-textfield/index.js'), toolbar: getAbsolutePath('/packages/mdc-toolbar/index.js'), diff --git a/test/unit/mdc-tab-scroller/rtl-scroller.test.js b/test/unit/mdc-tab-scroller/rtl-scroller.test.js deleted file mode 100644 index 77e30109c30..00000000000 --- a/test/unit/mdc-tab-scroller/rtl-scroller.test.js +++ /dev/null @@ -1,56 +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 {assert} from 'chai'; - -import {setupFoundationTest} from '../helpers/setup'; -import MDCTabScrollerFoundation from '../../../packages/mdc-tab-scroller/foundation'; -import MDCTabScrollerRTL from '../../../packages/mdc-tab-scroller/rtl-scroller'; - -suite('MDCTabScrollerRTL'); - -const setup = () => { - const {mockAdapter} = setupFoundationTest(MDCTabScrollerFoundation); - const scroller = new MDCTabScrollerRTL(mockAdapter); - return {scroller}; -}; - -test('#getScrollPositionRTL() is abstract and does nothing', () => { - const {scroller} = setup(); - assert.isUndefined(scroller.getScrollPositionRTL()); -}); - -test('#scrollToRTL() is abstract and does nothing', () => { - const {scroller} = setup(); - assert.isUndefined(scroller.scrollToRTL()); -}); - -test('#incrementScrollRTL() is abstract and does nothing', () => { - const {scroller} = setup(); - assert.isUndefined(scroller.incrementScrollRTL()); -}); - -test('#getAnimatingScrollPosition() is abstract and does nothing', () => { - const {scroller} = setup(); - assert.isUndefined(scroller.getAnimatingScrollPosition()); -}); From 5f50d60e6ff5183c4fc3839b03cbf74fdad78889 Mon Sep 17 00:00:00 2001 From: "Andrew C. Dvorak" Date: Mon, 11 Feb 2019 20:30:03 -0800 Subject: [PATCH 2/4] feat(tab-indicator): Convert JS to TypeScript (#4391) Refs #4225 --- packages/mdc-tab-indicator/README.md | 4 +- .../{adapter.js => adapter.ts} | 36 +++++------ .../{constants.js => constants.ts} | 2 - ...ing-foundation.js => fading-foundation.ts} | 9 +-- .../{foundation.js => foundation.ts} | 56 ++++++------------ .../mdc-tab-indicator/{index.js => index.ts} | 59 +++++++------------ ...ng-foundation.js => sliding-foundation.ts} | 12 ++-- scripts/webpack/js-bundle-factory.js | 2 +- .../fading-foundation.test.js | 8 ++- .../unit/mdc-tab-indicator/foundation.test.js | 22 +++---- .../sliding-foundation.test.js | 8 ++- 11 files changed, 81 insertions(+), 137 deletions(-) rename packages/mdc-tab-indicator/{adapter.js => adapter.ts} (65%) rename packages/mdc-tab-indicator/{constants.js => constants.ts} (96%) rename packages/mdc-tab-indicator/{fading-foundation.js => fading-foundation.ts} (87%) rename packages/mdc-tab-indicator/{foundation.js => foundation.ts} (54%) rename packages/mdc-tab-indicator/{index.js => index.ts} (63%) rename packages/mdc-tab-indicator/{sliding-foundation.js => sliding-foundation.ts} (90%) diff --git a/packages/mdc-tab-indicator/README.md b/packages/mdc-tab-indicator/README.md index f14d8507b73..df71c50c3d1 100644 --- a/packages/mdc-tab-indicator/README.md +++ b/packages/mdc-tab-indicator/README.md @@ -145,7 +145,7 @@ Mixin | Description Method Signature | Description --- | --- -`activate(previousIndicatorClientRect: ClientRect) => void` | Activates the tab indicator. +`activate(previousIndicatorClientRect?: ClientRect) => void` | Activates the tab indicator. `deactivate() => void` | Deactivates the tab indicator. `computeContentClientRect() => ClientRect` | Returns the content element bounding client rect. @@ -167,6 +167,6 @@ Method Signature | Description Method Signature | Description --- | --- `handleTransitionEnd(evt: Event) => void` | Handles the logic for the `"transitionend"` event on the root element. -`activate(previousIndicatorClientRect: ClientRect) => void` | Activates the tab indicator. +`activate(previousIndicatorClientRect?: ClientRect) => void` | Activates the tab indicator. `deactivate() => void` | Deactivates the tab indicator. `computeContentClientRect() => ClientRect` | Returns the content element's bounding client rect. diff --git a/packages/mdc-tab-indicator/adapter.js b/packages/mdc-tab-indicator/adapter.ts similarity index 65% rename from packages/mdc-tab-indicator/adapter.js rename to packages/mdc-tab-indicator/adapter.ts index ba548ac92ac..236a993b67f 100644 --- a/packages/mdc-tab-indicator/adapter.js +++ b/packages/mdc-tab-indicator/adapter.ts @@ -21,43 +21,37 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - /** - * Adapter for MDC Tab Indicator. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the Tab Indicator 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 MDCTabIndicatorAdapter { +interface MDCTabIndicatorAdapter { /** * Adds the given className to the root element. - * @param {string} className The className to add + * @param className The className to add */ - addClass(className) {} + addClass(className: string): void; /** * Removes the given className from the root element. - * @param {string} className The className to remove + * @param className The className to remove */ - removeClass(className) {} + removeClass(className: string): void; /** * Returns the client rect of the content element. - * @return {!ClientRect} */ - computeContentClientRect() {} + computeContentClientRect(): ClientRect; /** * Sets a style property of the content element to the passed value - * @param {string} propName The style property name to set - * @param {string} value The style property value + * @param propName The style property name to set + * @param value The style property value */ - setContentStyleProperty(propName, value) {} + setContentStyleProperty(propName: string, value: string): void; } -export default MDCTabIndicatorAdapter; +export {MDCTabIndicatorAdapter as default, MDCTabIndicatorAdapter}; diff --git a/packages/mdc-tab-indicator/constants.js b/packages/mdc-tab-indicator/constants.ts similarity index 96% rename from packages/mdc-tab-indicator/constants.js rename to packages/mdc-tab-indicator/constants.ts index 89a9ee080aa..7437c9887a9 100644 --- a/packages/mdc-tab-indicator/constants.js +++ b/packages/mdc-tab-indicator/constants.ts @@ -21,14 +21,12 @@ * THE SOFTWARE. */ -/** @enum {string} */ const cssClasses = { ACTIVE: 'mdc-tab-indicator--active', FADE: 'mdc-tab-indicator--fade', NO_TRANSITION: 'mdc-tab-indicator--no-transition', }; -/** @enum {string} */ const strings = { CONTENT_SELECTOR: '.mdc-tab-indicator__content', }; diff --git a/packages/mdc-tab-indicator/fading-foundation.js b/packages/mdc-tab-indicator/fading-foundation.ts similarity index 87% rename from packages/mdc-tab-indicator/fading-foundation.js rename to packages/mdc-tab-indicator/fading-foundation.ts index b39124f42ce..2be01e9790b 100644 --- a/packages/mdc-tab-indicator/fading-foundation.js +++ b/packages/mdc-tab-indicator/fading-foundation.ts @@ -21,12 +21,9 @@ * THE SOFTWARE. */ -import MDCTabIndicatorFoundation from './foundation'; +import {MDCTabIndicatorFoundation} from './foundation'; -/** - * @extends {MDCTabIndicatorFoundation} - * @final - */ +/* istanbul ignore next: subclass is not a branch statement */ class MDCFadingTabIndicatorFoundation extends MDCTabIndicatorFoundation { activate() { this.adapter_.addClass(MDCTabIndicatorFoundation.cssClasses.ACTIVE); @@ -37,4 +34,4 @@ class MDCFadingTabIndicatorFoundation extends MDCTabIndicatorFoundation { } } -export default MDCFadingTabIndicatorFoundation; +export {MDCFadingTabIndicatorFoundation as default, MDCFadingTabIndicatorFoundation}; diff --git a/packages/mdc-tab-indicator/foundation.js b/packages/mdc-tab-indicator/foundation.ts similarity index 54% rename from packages/mdc-tab-indicator/foundation.js rename to packages/mdc-tab-indicator/foundation.ts index 7332df8622b..bab499e44bc 100644 --- a/packages/mdc-tab-indicator/foundation.js +++ b/packages/mdc-tab-indicator/foundation.ts @@ -22,59 +22,39 @@ */ import {MDCFoundation} from '@material/base/foundation'; -import MDCTabIndicatorAdapter from './adapter'; -import { - cssClasses, - strings, -} from './constants'; +import {MDCTabIndicatorAdapter} from './adapter'; +import {cssClasses, strings} from './constants'; -/** - * @extends {MDCFoundation} - * @abstract - */ -class MDCTabIndicatorFoundation extends MDCFoundation { - /** @return enum {string} */ +abstract class MDCTabIndicatorFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } - /** @return enum {string} */ static get strings() { return strings; } - /** - * @see MDCTabIndicatorAdapter for typing information - * @return {!MDCTabIndicatorAdapter} - */ - static get defaultAdapter() { - return /** @type {!MDCTabIndicatorAdapter} */ ({ - addClass: () => {}, - removeClass: () => {}, - computeContentClientRect: () => {}, - setContentStyleProperty: () => {}, - }); + static get defaultAdapter(): MDCTabIndicatorAdapter { + // tslint:disable:object-literal-sort-keys + return { + addClass: () => undefined, + removeClass: () => undefined, + computeContentClientRect: () => ({top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0}), + setContentStyleProperty: () => undefined, + }; + // tslint:enable:object-literal-sort-keys } - /** @param {!MDCTabIndicatorAdapter} adapter */ - constructor(adapter) { - super(Object.assign(MDCTabIndicatorFoundation.defaultAdapter, adapter)); + constructor(adapter?: Partial) { + super({...MDCTabIndicatorFoundation.defaultAdapter, ...adapter}); } - /** @return {!ClientRect} */ - computeContentClientRect() { + computeContentClientRect(): ClientRect { return this.adapter_.computeContentClientRect(); } - /** - * Activates the indicator - * @param {!ClientRect=} previousIndicatorClientRect - * @abstract - */ - activate(previousIndicatorClientRect) {} // eslint-disable-line no-unused-vars - - /** @abstract */ - deactivate() {} + abstract activate(previousIndicatorClientRect?: ClientRect): void; + abstract deactivate(): void; } -export default MDCTabIndicatorFoundation; +export {MDCTabIndicatorFoundation as default, MDCTabIndicatorFoundation}; diff --git a/packages/mdc-tab-indicator/index.js b/packages/mdc-tab-indicator/index.ts similarity index 63% rename from packages/mdc-tab-indicator/index.js rename to packages/mdc-tab-indicator/index.ts index 4eeb5831932..cebd2a33586 100644 --- a/packages/mdc-tab-indicator/index.js +++ b/packages/mdc-tab-indicator/index.ts @@ -23,55 +23,35 @@ import {MDCComponent} from '@material/base/component'; -import MDCTabIndicatorAdapter from './adapter'; -import MDCTabIndicatorFoundation from './foundation'; +import {MDCTabIndicatorAdapter} from './adapter'; +import {MDCFadingTabIndicatorFoundation} from './fading-foundation'; +import {MDCTabIndicatorFoundation} from './foundation'; +import {MDCSlidingTabIndicatorFoundation} from './sliding-foundation'; -import MDCSlidingTabIndicatorFoundation from './sliding-foundation'; -import MDCFadingTabIndicatorFoundation from './fading-foundation'; - -/** - * @extends {MDCComponent} - * @final - */ -class MDCTabIndicator extends MDCComponent { - /** - * @param {!Element} root - * @return {!MDCTabIndicator} - */ - static attachTo(root) { +class MDCTabIndicator extends MDCComponent { + static attachTo(root: Element): MDCTabIndicator { return new MDCTabIndicator(root); } - /** - * @param {...?} args - */ - constructor(...args) { - super(...args); - /** @type {?Element} */ - this.content_; - } + private content_!: HTMLElement; // assigned in initialize() initialize() { - this.content_ = this.root_.querySelector(MDCTabIndicatorFoundation.strings.CONTENT_SELECTOR); + this.content_ = this.root_.querySelector(MDCTabIndicatorFoundation.strings.CONTENT_SELECTOR)!; } - /** - * @return {!ClientRect} - */ - computeContentClientRect() { + computeContentClientRect(): ClientRect { return this.foundation_.computeContentClientRect(); } - /** - * @return {!MDCTabIndicatorFoundation} - */ - getDefaultFoundation() { - const adapter = /** @type {!MDCTabIndicatorAdapter} */ (Object.assign({ + getDefaultFoundation(): MDCTabIndicatorFoundation { + // tslint:disable:object-literal-sort-keys + const adapter: MDCTabIndicatorAdapter = { addClass: (className) => this.root_.classList.add(className), removeClass: (className) => this.root_.classList.remove(className), computeContentClientRect: () => this.content_.getBoundingClientRect(), setContentStyleProperty: (prop, value) => this.content_.style.setProperty(prop, value), - })); + }; + // tslint:enable:object-literal-sort-keys if (this.root_.classList.contains(MDCTabIndicatorFoundation.cssClasses.FADE)) { return new MDCFadingTabIndicatorFoundation(adapter); @@ -81,10 +61,7 @@ class MDCTabIndicator extends MDCComponent { return new MDCSlidingTabIndicatorFoundation(adapter); } - /** - * @param {!ClientRect=} previousIndicatorClientRect - */ - activate(previousIndicatorClientRect) { + activate(previousIndicatorClientRect?: ClientRect) { this.foundation_.activate(previousIndicatorClientRect); } @@ -93,4 +70,8 @@ class MDCTabIndicator extends MDCComponent { } } -export {MDCTabIndicator, MDCTabIndicatorFoundation, MDCSlidingTabIndicatorFoundation, MDCFadingTabIndicatorFoundation}; +export {MDCTabIndicator as default, MDCTabIndicator}; +export * from './adapter'; +export * from './foundation'; +export * from './fading-foundation'; +export * from './sliding-foundation'; diff --git a/packages/mdc-tab-indicator/sliding-foundation.js b/packages/mdc-tab-indicator/sliding-foundation.ts similarity index 90% rename from packages/mdc-tab-indicator/sliding-foundation.js rename to packages/mdc-tab-indicator/sliding-foundation.ts index 927397b8874..943c57bf7ca 100644 --- a/packages/mdc-tab-indicator/sliding-foundation.js +++ b/packages/mdc-tab-indicator/sliding-foundation.ts @@ -21,15 +21,11 @@ * THE SOFTWARE. */ -import MDCTabIndicatorFoundation from './foundation'; +import {MDCTabIndicatorFoundation} from './foundation'; -/** - * @extends {MDCTabIndicatorFoundation} - * @final - */ +/* istanbul ignore next: subclass is not a branch statement */ class MDCSlidingTabIndicatorFoundation extends MDCTabIndicatorFoundation { - /** @param {!ClientRect=} previousIndicatorClientRect */ - activate(previousIndicatorClientRect) { + activate(previousIndicatorClientRect?: ClientRect) { // Early exit if no indicator is present to handle cases where an indicator // may be activated without a prior indicator state if (!previousIndicatorClientRect) { @@ -60,4 +56,4 @@ class MDCSlidingTabIndicatorFoundation extends MDCTabIndicatorFoundation { } } -export default MDCSlidingTabIndicatorFoundation; +export {MDCSlidingTabIndicatorFoundation as default, MDCSlidingTabIndicatorFoundation}; diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index cf7bb077fb1..45180c98b5c 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -180,7 +180,7 @@ class JsBundleFactory { switch: getAbsolutePath('/packages/mdc-switch/index.ts'), tab: getAbsolutePath('/packages/mdc-tab/index.js'), tabBar: getAbsolutePath('/packages/mdc-tab-bar/index.js'), - tabIndicator: getAbsolutePath('/packages/mdc-tab-indicator/index.js'), + tabIndicator: getAbsolutePath('/packages/mdc-tab-indicator/index.ts'), tabScroller: getAbsolutePath('/packages/mdc-tab-scroller/index.ts'), tabs: getAbsolutePath('/packages/mdc-tabs/index.js'), textfield: getAbsolutePath('/packages/mdc-textfield/index.js'), diff --git a/test/unit/mdc-tab-indicator/fading-foundation.test.js b/test/unit/mdc-tab-indicator/fading-foundation.test.js index e915173ceeb..1aca6c8e96a 100644 --- a/test/unit/mdc-tab-indicator/fading-foundation.test.js +++ b/test/unit/mdc-tab-indicator/fading-foundation.test.js @@ -24,7 +24,7 @@ import td from 'testdouble'; import {setupFoundationTest} from '../helpers/setup'; -import MDCFadingTabIndicatorFoundation from '../../../packages/mdc-tab-indicator/fading-foundation'; +import {MDCFadingTabIndicatorFoundation} from '../../../packages/mdc-tab-indicator/fading-foundation'; suite('MDCFadingTabIndicatorFoundation'); @@ -37,7 +37,9 @@ test(`#activate adds the ${MDCFadingTabIndicatorFoundation.cssClasses.ACTIVE} cl }); test(`#deactivate removes the ${MDCFadingTabIndicatorFoundation.cssClasses.ACTIVE} class`, () => { - const {foundation, mockAdapter} = setupTest(); + const foundation = new MDCFadingTabIndicatorFoundation(); + const adapter = foundation.adapter_; + adapter.removeClass = td.func('removeClass'); foundation.deactivate(); - td.verify(mockAdapter.removeClass(MDCFadingTabIndicatorFoundation.cssClasses.ACTIVE)); + td.verify(adapter.removeClass(MDCFadingTabIndicatorFoundation.cssClasses.ACTIVE)); }); diff --git a/test/unit/mdc-tab-indicator/foundation.test.js b/test/unit/mdc-tab-indicator/foundation.test.js index 01598e46f3a..f670b659cef 100644 --- a/test/unit/mdc-tab-indicator/foundation.test.js +++ b/test/unit/mdc-tab-indicator/foundation.test.js @@ -26,7 +26,7 @@ import td from 'testdouble'; import {verifyDefaultAdapter} from '../helpers/foundation'; import {setupFoundationTest} from '../helpers/setup'; -import MDCTabIndicatorFoundation from '../../../packages/mdc-tab-indicator/foundation'; +import {MDCTabIndicatorFoundation} from '../../../packages/mdc-tab-indicator/foundation'; suite('MDCTabIndicatorFoundation'); @@ -48,22 +48,16 @@ test('defaultAdapter returns a complete adapter implementation', () => { const setupTest = () => setupFoundationTest(MDCTabIndicatorFoundation); -test('#computeContentClientRect returns the client rect', () => { +test('#computeContentClientRect returns adapter value', () => { const {foundation, mockAdapter} = setupTest(); foundation.computeContentClientRect(); td.verify(mockAdapter.computeContentClientRect(), {times: 1}); }); -test('#activate is abstract and does nothing', () => { - const {foundation, mockAdapter} = setupTest(); - foundation.activate(); - td.verify(mockAdapter.addClass(td.matchers.isA(String)), {times: 0}); - td.verify(mockAdapter.removeClass(td.matchers.isA(String)), {times: 0}); -}); - -test('#deactivate is abstract and does nothing', () => { - const {foundation, mockAdapter} = setupTest(); - foundation.deactivate(); - td.verify(mockAdapter.addClass(td.matchers.isA(String)), {times: 0}); - td.verify(mockAdapter.removeClass(td.matchers.isA(String)), {times: 0}); +test('#computeContentClientRect returns default client rect', () => { + const foundation = new MDCTabIndicatorFoundation(); + assert.deepEqual( + foundation.computeContentClientRect(), + {top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0} + ); }); diff --git a/test/unit/mdc-tab-indicator/sliding-foundation.test.js b/test/unit/mdc-tab-indicator/sliding-foundation.test.js index ad5089cad57..c9113a8ac74 100644 --- a/test/unit/mdc-tab-indicator/sliding-foundation.test.js +++ b/test/unit/mdc-tab-indicator/sliding-foundation.test.js @@ -24,7 +24,7 @@ import td from 'testdouble'; import {setupFoundationTest} from '../helpers/setup'; -import MDCSlidingTabIndicatorFoundation from '../../../packages/mdc-tab-indicator/sliding-foundation'; +import {MDCSlidingTabIndicatorFoundation} from '../../../packages/mdc-tab-indicator/sliding-foundation'; suite('MDCSlidingTabIndicatorFoundation'); @@ -56,7 +56,9 @@ test('#activate does not modify transform and does not transition if no client r }); test(`#deactivate removes the ${MDCSlidingTabIndicatorFoundation.cssClasses.ACTIVE} class`, () => { - const {foundation, mockAdapter} = setupTest(); + const foundation = new MDCSlidingTabIndicatorFoundation(); + const adapter = foundation.adapter_; + adapter.removeClass = td.func('removeClass'); foundation.deactivate(); - td.verify(mockAdapter.removeClass(MDCSlidingTabIndicatorFoundation.cssClasses.ACTIVE)); + td.verify(adapter.removeClass(MDCSlidingTabIndicatorFoundation.cssClasses.ACTIVE)); }); From ab9879d8a26d4b0d62e7f742b0558778e79c6027 Mon Sep 17 00:00:00 2001 From: "Andrew C. Dvorak" Date: Mon, 11 Feb 2019 21:00:52 -0800 Subject: [PATCH 3/4] feat(tab): Convert JS to TypeScript (#4393) Refs #4225 --- .gitignore | 3 +- packages/mdc-tab/README.md | 8 +- packages/mdc-tab/{adapter.js => adapter.ts} | 78 +++----- .../mdc-tab/{constants.js => constants.ts} | 8 +- .../mdc-tab/{foundation.js => foundation.ts} | 88 ++++----- packages/mdc-tab/index.js | 168 ------------------ packages/mdc-tab/index.ts | 144 +++++++++++++++ packages/mdc-tab/types.ts | 47 +++++ scripts/webpack/js-bundle-factory.js | 2 +- test/unit/mdc-tab/mdc-tab.test.js | 4 +- 10 files changed, 259 insertions(+), 291 deletions(-) rename packages/mdc-tab/{adapter.js => adapter.ts} (53%) rename packages/mdc-tab/{constants.js => constants.ts} (97%) rename packages/mdc-tab/{foundation.js => foundation.ts} (65%) delete mode 100644 packages/mdc-tab/index.js create mode 100644 packages/mdc-tab/index.ts create mode 100644 packages/mdc-tab/types.ts diff --git a/.gitignore b/.gitignore index 89776a7dc01..f0a6e5eb0f1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ out .idea *.sw* .closure-tmp -.vscode +.site-generator-tmp .typescript-tmp +.vscode diff --git a/packages/mdc-tab/README.md b/packages/mdc-tab/README.md index f9e655c2ff9..592e1ffa1f4 100644 --- a/packages/mdc-tab/README.md +++ b/packages/mdc-tab/README.md @@ -146,7 +146,7 @@ Property | Value Type | Description Method Signature | Description --- | --- -`activate(previousIndicatorClientRect: ClientRect=) => void` | Activates the indicator. `previousIndicatorClientRect` is an optional argument. +`activate(previousIndicatorClientRect?: ClientRect) => void` | Activates the indicator. `previousIndicatorClientRect` is an optional argument. `deactivate() => void` | Deactivates the indicator. `focus() => void` | Focuses the tab. `computeIndicatorClientRect() => ClientRect` | Returns the bounding client rect of the indicator. @@ -168,7 +168,7 @@ Method Signature | Description `removeClass(className: string) => void` | Removes a class from the root element. `hasClass(className: string) => boolean` | Returns true if the root element contains the given class. `setAttr(attr: string, value: string) => void` | Sets the given attribute on the root element to the given value. -`activateIndicator(previousIndicatorClientRect: ClientRect=) => void` | Activates the tab indicator subcomponent. `previousIndicatorClientRect` is an optional argument. +`activateIndicator(previousIndicatorClientRect?: ClientRect) => void` | Activates the tab indicator subcomponent. `previousIndicatorClientRect` is an optional argument. `deactivateIndicator() => void` | Deactivates the tab indicator subcomponent. `getOffsetLeft() => number` | Returns the `offsetLeft` value of the root element. `getOffsetWidth() => number` | Returns the `offsetWidth` value of the root element. @@ -184,13 +184,13 @@ Method Signature | Description `handleClick() => void` | Handles the logic for the `"click"` event. `isActive() => boolean` | Returns whether the tab is active. `setFocusOnActivate(focusOnActivate: boolean) => void` | Sets whether the tab should focus itself when activated. -`activate(previousIndicatorClientRect: ClientRect=) => void` | Activates the tab. `previousIndicatorClientRect` is an optional argument. +`activate(previousIndicatorClientRect?: ClientRect) => void` | Activates the tab. `previousIndicatorClientRect` is an optional argument. `deactivate() => void` | Deactivates the tab. `computeDimensions() => MDCTabDimensions` | Returns the dimensions of the tab. ### `MDCTabFoundation` Event Handlers -When wrapping the Tab component, it is necessary to register the following event handler. For an example of this, see the [MDCTab](index.js) component's `initialSyncWithDOM` method. +When wrapping the Tab component, it is necessary to register the following event handler. For an example of this, see the [MDCTab](index.ts) component's `initialSyncWithDOM` method. Event | Element | Foundation Handler --- | --- | --- diff --git a/packages/mdc-tab/adapter.js b/packages/mdc-tab/adapter.ts similarity index 53% rename from packages/mdc-tab/adapter.js rename to packages/mdc-tab/adapter.ts index 868c53a5daa..036686136e6 100644 --- a/packages/mdc-tab/adapter.js +++ b/packages/mdc-tab/adapter.ts @@ -21,105 +21,77 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - /** - * MDCTabDimensions provides details about the left and right edges of the Tab - * root element and the Tab content element. These values are used to determine - * the visual position of the Tab with respect it's parent container. - * @typedef {{rootLeft: number, rootRight: number, contentLeft: number, contentRight: number}} - */ -let MDCTabDimensions; - -/** - * @typedef {{ - * detail: { - * tabId: string, - * }, - * bubbles: boolean, - * }} - */ -let MDCTabInteractionEventType; - -/** - * Adapter for MDC Tab. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the Tab 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 MDCTabAdapter { +interface MDCTabAdapter { /** * Adds the given className to the root element. - * @param {string} className The className to add + * @param className The className to add */ - addClass(className) {} + addClass(className: string): void; /** * Removes the given className from the root element. - * @param {string} className The className to remove + * @param className The className to remove */ - removeClass(className) {} + removeClass(className: string): void; /** * Returns whether the root element has the given className. - * @param {string} className The className to remove - * @return {boolean} + * @param className The className to remove */ - hasClass(className) {} + hasClass(className: string): boolean; /** * Sets the given attrName of the root element to the given value. - * @param {string} attr The attribute name to set - * @param {string} value The value so give the attribute + * @param attr The attribute name to set + * @param value The value so give the attribute */ - setAttr(attr, value) {} + setAttr(attr: string, value: string): void; /** * Activates the indicator element. - * @param {!ClientRect=} previousIndicatorClientRect The client rect of the previously activated indicator + * @param previousIndicatorClientRect The client rect of the previously activated indicator */ - activateIndicator(previousIndicatorClientRect) {} + activateIndicator(previousIndicatorClientRect?: ClientRect): void; /** Deactivates the indicator. */ - deactivateIndicator() {} + deactivateIndicator(): void; /** * Emits the MDCTab:interacted event for use by parent components */ - notifyInteracted() {} + notifyInteracted(): void; /** * Returns the offsetLeft value of the root element. - * @return {number} */ - getOffsetLeft() {} + getOffsetLeft(): number; /** * Returns the offsetWidth value of the root element. - * @return {number} */ - getOffsetWidth() {} + getOffsetWidth(): number; /** * Returns the offsetLeft of the content element. - * @return {number} */ - getContentOffsetLeft() {} + getContentOffsetLeft(): number; /** * Returns the offsetWidth of the content element. - * @return {number} */ - getContentOffsetWidth() {} + getContentOffsetWidth(): number; /** * Applies focus to the root element */ - focus() {} + focus(): void; } -export {MDCTabDimensions, MDCTabInteractionEventType, MDCTabAdapter}; +export {MDCTabAdapter as default, MDCTabAdapter}; diff --git a/packages/mdc-tab/constants.js b/packages/mdc-tab/constants.ts similarity index 97% rename from packages/mdc-tab/constants.js rename to packages/mdc-tab/constants.ts index b628751f2f8..73a3ef224f7 100644 --- a/packages/mdc-tab/constants.js +++ b/packages/mdc-tab/constants.ts @@ -21,19 +21,17 @@ * THE SOFTWARE. */ -/** @enum {string} */ const cssClasses = { ACTIVE: 'mdc-tab--active', }; -/** @enum {string} */ const strings = { ARIA_SELECTED: 'aria-selected', - RIPPLE_SELECTOR: '.mdc-tab__ripple', CONTENT_SELECTOR: '.mdc-tab__content', - TAB_INDICATOR_SELECTOR: '.mdc-tab-indicator', - TABINDEX: 'tabIndex', INTERACTED_EVENT: 'MDCTab:interacted', + RIPPLE_SELECTOR: '.mdc-tab__ripple', + TABINDEX: 'tabIndex', + TAB_INDICATOR_SELECTOR: '.mdc-tab-indicator', }; export { diff --git a/packages/mdc-tab/foundation.js b/packages/mdc-tab/foundation.ts similarity index 65% rename from packages/mdc-tab/foundation.js rename to packages/mdc-tab/foundation.ts index 718da06be97..cb3f076d3b6 100644 --- a/packages/mdc-tab/foundation.js +++ b/packages/mdc-tab/foundation.ts @@ -22,90 +22,65 @@ */ import {MDCFoundation} from '@material/base/foundation'; +import {MDCTabAdapter} from './adapter'; +import {cssClasses, strings} from './constants'; +import {MDCTabDimensions} from './types'; -/* eslint-disable no-unused-vars */ -import {MDCTabAdapter, MDCTabDimensions} from './adapter'; -/* eslint-enable no-unused-vars */ - -import { - cssClasses, - strings, -} from './constants'; - -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTabFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTabFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } - /** @return enum {string} */ static get strings() { return strings; } - /** - * @see MDCTabAdapter for typing information - * @return {!MDCTabAdapter} - */ - static get defaultAdapter() { - return /** @type {!MDCTabAdapter} */ ({ - addClass: () => {}, - removeClass: () => {}, - hasClass: () => {}, - setAttr: () => {}, - activateIndicator: () => {}, - deactivateIndicator: () => {}, - notifyInteracted: () => {}, - getOffsetLeft: () => {}, - getOffsetWidth: () => {}, - getContentOffsetLeft: () => {}, - getContentOffsetWidth: () => {}, - focus: () => {}, - }); + static get defaultAdapter(): MDCTabAdapter { + // tslint:disable:object-literal-sort-keys + return { + addClass: () => undefined, + removeClass: () => undefined, + hasClass: () => false, + setAttr: () => undefined, + activateIndicator: () => undefined, + deactivateIndicator: () => undefined, + notifyInteracted: () => undefined, + getOffsetLeft: () => 0, + getOffsetWidth: () => 0, + getContentOffsetLeft: () => 0, + getContentOffsetWidth: () => 0, + focus: () => undefined, + }; + // tslint:enable:object-literal-sort-keys } - /** @param {!MDCTabAdapter} adapter */ - constructor(adapter) { - super(Object.assign(MDCTabFoundation.defaultAdapter, adapter)); + private focusOnActivate_ = true; - /** @private {boolean} */ - this.focusOnActivate_ = true; + constructor(adapter?: Partial) { + super({...MDCTabFoundation.defaultAdapter, ...adapter}); } - /** - * Handles the "click" event - */ handleClick() { // It's up to the parent component to keep track of the active Tab and // ensure we don't activate a Tab that's already active. this.adapter_.notifyInteracted(); } - /** - * Returns the Tab's active state - * @return {boolean} - */ - isActive() { + isActive(): boolean { return this.adapter_.hasClass(cssClasses.ACTIVE); } /** * Sets whether the tab should focus itself when activated - * @param {boolean} focusOnActivate */ - setFocusOnActivate(focusOnActivate) { + setFocusOnActivate(focusOnActivate: boolean) { this.focusOnActivate_ = focusOnActivate; } /** * Activates the Tab - * @param {!ClientRect=} previousIndicatorClientRect */ - activate(previousIndicatorClientRect) { + activate(previousIndicatorClientRect?: ClientRect) { this.adapter_.addClass(cssClasses.ACTIVE); this.adapter_.setAttr(strings.ARIA_SELECTED, 'true'); this.adapter_.setAttr(strings.TABINDEX, '0'); @@ -132,21 +107,20 @@ class MDCTabFoundation extends MDCFoundation { /** * Returns the dimensions of the Tab - * @return {!MDCTabDimensions} */ - computeDimensions() { + computeDimensions(): MDCTabDimensions { const rootWidth = this.adapter_.getOffsetWidth(); const rootLeft = this.adapter_.getOffsetLeft(); const contentWidth = this.adapter_.getContentOffsetWidth(); const contentLeft = this.adapter_.getContentOffsetLeft(); return { - rootLeft, - rootRight: rootLeft + rootWidth, contentLeft: rootLeft + contentLeft, contentRight: rootLeft + contentLeft + contentWidth, + rootLeft, + rootRight: rootLeft + rootWidth, }; } } -export default MDCTabFoundation; +export {MDCTabFoundation as default, MDCTabFoundation}; diff --git a/packages/mdc-tab/index.js b/packages/mdc-tab/index.js deleted file mode 100644 index 021dcbb5641..00000000000 --- a/packages/mdc-tab/index.js +++ /dev/null @@ -1,168 +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 {MDCComponent} from '@material/base/component'; - -/* eslint-disable no-unused-vars */ -import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple/index'; -import {MDCTabIndicator, MDCTabIndicatorFoundation} from '@material/tab-indicator/index'; -import {MDCTabAdapter, MDCTabDimensions, MDCTabInteractionEventType} from './adapter'; -/* eslint-enable no-unused-vars */ - -import MDCTabFoundation from './foundation'; - -/** - * @extends {MDCComponent} - * @final - */ -class MDCTab extends MDCComponent { - /** - * @param {...?} args - */ - constructor(...args) { - super(...args); - - /** @type {string} */ - this.id; - /** @private {?MDCRipple} */ - this.ripple_; - /** @private {?MDCTabIndicator} */ - this.tabIndicator_; - /** @private {?Element} */ - this.content_; - - /** @private {?Function} */ - this.handleClick_; - } - - /** - * @param {!Element} root - * @return {!MDCTab} - */ - static attachTo(root) { - return new MDCTab(root); - } - - initialize( - rippleFactory = (el, foundation) => new MDCRipple(el, foundation), - tabIndicatorFactory = (el) => new MDCTabIndicator(el)) { - this.id = this.root_.id; - const rippleSurface = this.root_.querySelector(MDCTabFoundation.strings.RIPPLE_SELECTOR); - const rippleAdapter = Object.assign(MDCRipple.createAdapter(/** @type {!RippleCapableSurface} */ (this)), { - addClass: (className) => rippleSurface.classList.add(className), - removeClass: (className) => rippleSurface.classList.remove(className), - updateCssVariable: (varName, value) => rippleSurface.style.setProperty(varName, value), - }); - const rippleFoundation = new MDCRippleFoundation(rippleAdapter); - this.ripple_ = rippleFactory(this.root_, rippleFoundation); - - const tabIndicatorElement = this.root_.querySelector(MDCTabFoundation.strings.TAB_INDICATOR_SELECTOR); - this.tabIndicator_ = tabIndicatorFactory(tabIndicatorElement); - - this.content_ = this.root_.querySelector(MDCTabFoundation.strings.CONTENT_SELECTOR); - } - - initialSyncWithDOM() { - this.handleClick_ = this.foundation_.handleClick.bind(this.foundation_); - this.listen('click', this.handleClick_); - } - - destroy() { - this.unlisten('click', /** @type {!Function} */ (this.handleClick_)); - this.ripple_.destroy(); - super.destroy(); - } - - /** - * @return {!MDCTabFoundation} - */ - getDefaultFoundation() { - return new MDCTabFoundation( - /** @type {!MDCTabAdapter} */ ({ - setAttr: (attr, value) => this.root_.setAttribute(attr, value), - addClass: (className) => this.root_.classList.add(className), - removeClass: (className) => this.root_.classList.remove(className), - hasClass: (className) => this.root_.classList.contains(className), - activateIndicator: (previousIndicatorClientRect) => this.tabIndicator_.activate(previousIndicatorClientRect), - deactivateIndicator: () => this.tabIndicator_.deactivate(), - notifyInteracted: () => this.emit( - MDCTabFoundation.strings.INTERACTED_EVENT, {tabId: this.id}, true /* bubble */), - getOffsetLeft: () => this.root_.offsetLeft, - getOffsetWidth: () => this.root_.offsetWidth, - getContentOffsetLeft: () => this.content_.offsetLeft, - getContentOffsetWidth: () => this.content_.offsetWidth, - focus: () => this.root_.focus(), - })); - } - - /** - * Getter for the active state of the tab - * @return {boolean} - */ - get active() { - return this.foundation_.isActive(); - } - - set focusOnActivate(focusOnActivate) { - this.foundation_.setFocusOnActivate(focusOnActivate); - } - - /** - * Activates the tab - * @param {!ClientRect=} computeIndicatorClientRect - */ - activate(computeIndicatorClientRect) { - this.foundation_.activate(computeIndicatorClientRect); - } - - /** - * Deactivates the tab - */ - deactivate() { - this.foundation_.deactivate(); - } - - /** - * Returns the indicator's client rect - * @return {!ClientRect} - */ - computeIndicatorClientRect() { - return this.tabIndicator_.computeContentClientRect(); - } - - /** - * @return {!MDCTabDimensions} - */ - computeDimensions() { - return this.foundation_.computeDimensions(); - } - - /** - * Focuses the tab - */ - focus() { - this.root_.focus(); - } -} - -export {MDCTab, MDCTabFoundation}; diff --git a/packages/mdc-tab/index.ts b/packages/mdc-tab/index.ts new file mode 100644 index 00000000000..c33ba92f1b5 --- /dev/null +++ b/packages/mdc-tab/index.ts @@ -0,0 +1,144 @@ +/** + * @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 {MDCComponent} from '@material/base/component'; +import {SpecificEventListener} from '@material/base/types'; +import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple/index'; +import {MDCTabIndicator} from '@material/tab-indicator/index'; +import {MDCTabFoundation} from './foundation'; +import {MDCTabDimensions, RippleFactory, TabIndicatorFactory, TabInteractionEventDetail} from './types'; + +class MDCTab extends MDCComponent implements RippleCapableSurface { + static attachTo(root: Element): MDCTab { + return new MDCTab(root); + } + + id!: string; // assigned in initialize(); + + // Public visibility for this property is required by RippleCapableSurface. + root_!: HTMLElement; // assigned in MDCComponent constructor + + private ripple_!: MDCRipple; // assigned in initialize(); + private tabIndicator_!: MDCTabIndicator; // assigned in initialize(); + private content_!: HTMLElement; // assigned in initialize(); + private handleClick_!: SpecificEventListener<'click'>; // assigned in initialize(); + + initialize( + rippleFactory: RippleFactory = (el, foundation) => new MDCRipple(el, foundation), + tabIndicatorFactory: TabIndicatorFactory = (el) => new MDCTabIndicator(el), + ) { + this.id = this.root_.id; + const rippleSurface = this.root_.querySelector(MDCTabFoundation.strings.RIPPLE_SELECTOR)!; + const rippleAdapter = { + ...MDCRipple.createAdapter(this), + addClass: (className: string) => rippleSurface.classList.add(className), + removeClass: (className: string) => rippleSurface.classList.remove(className), + updateCssVariable: (varName: string, value: string) => rippleSurface.style.setProperty(varName, value), + }; + const rippleFoundation = new MDCRippleFoundation(rippleAdapter); + this.ripple_ = rippleFactory(this.root_, rippleFoundation); + + const tabIndicatorElement = this.root_.querySelector(MDCTabFoundation.strings.TAB_INDICATOR_SELECTOR)!; + this.tabIndicator_ = tabIndicatorFactory(tabIndicatorElement); + this.content_ = this.root_.querySelector(MDCTabFoundation.strings.CONTENT_SELECTOR)!; + } + + initialSyncWithDOM() { + this.handleClick_ = () => this.foundation_.handleClick(); + this.listen('click', this.handleClick_); + } + + destroy() { + this.unlisten('click', this.handleClick_); + this.ripple_.destroy(); + super.destroy(); + } + + getDefaultFoundation(): MDCTabFoundation { + // tslint:disable:object-literal-sort-keys + return new MDCTabFoundation({ + setAttr: (attr, value) => this.root_.setAttribute(attr, value), + addClass: (className) => this.root_.classList.add(className), + removeClass: (className) => this.root_.classList.remove(className), + hasClass: (className) => this.root_.classList.contains(className), + activateIndicator: (previousIndicatorClientRect) => this.tabIndicator_.activate(previousIndicatorClientRect), + deactivateIndicator: () => this.tabIndicator_.deactivate(), + notifyInteracted: () => this.emit( + MDCTabFoundation.strings.INTERACTED_EVENT, {tabId: this.id}, true /* bubble */), + getOffsetLeft: () => this.root_.offsetLeft, + getOffsetWidth: () => this.root_.offsetWidth, + getContentOffsetLeft: () => this.content_.offsetLeft, + getContentOffsetWidth: () => this.content_.offsetWidth, + focus: () => this.root_.focus(), + }); + // tslint:enable:object-literal-sort-keys + } + + /** + * Getter for the active state of the tab + */ + get active(): boolean { + return this.foundation_.isActive(); + } + + set focusOnActivate(focusOnActivate: boolean) { + this.foundation_.setFocusOnActivate(focusOnActivate); + } + + /** + * Activates the tab + */ + activate(computeIndicatorClientRect?: ClientRect) { + this.foundation_.activate(computeIndicatorClientRect); + } + + /** + * Deactivates the tab + */ + deactivate() { + this.foundation_.deactivate(); + } + + /** + * Returns the indicator's client rect + */ + computeIndicatorClientRect(): ClientRect { + return this.tabIndicator_.computeContentClientRect(); + } + + computeDimensions(): MDCTabDimensions { + return this.foundation_.computeDimensions(); + } + + /** + * Focuses the tab + */ + focus() { + this.root_.focus(); + } +} + +export {MDCTab as default, MDCTab}; +export * from './adapter'; +export * from './foundation'; +export * from './types'; diff --git a/packages/mdc-tab/types.ts b/packages/mdc-tab/types.ts new file mode 100644 index 00000000000..7058fa4c595 --- /dev/null +++ b/packages/mdc-tab/types.ts @@ -0,0 +1,47 @@ +/** + * @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 {MDCRippleFoundation} from '@material/ripple/foundation'; +import {MDCRipple} from '@material/ripple/index'; +import {MDCTabIndicator} from '@material/tab-indicator/index'; + +/** + * MDCTabDimensions provides details about the left and right edges of the Tab + * root element and the Tab content element. These values are used to determine + * the visual position of the Tab with respect it's parent container. + */ +export interface MDCTabDimensions { + rootLeft: number; + rootRight: number; + contentLeft: number; + contentRight: number; +} + +export type TabInteractionEvent = CustomEvent; + +export interface TabInteractionEventDetail { + tabId: string; +} + +export type RippleFactory = (el: Element, foundation: MDCRippleFoundation) => MDCRipple; +export type TabIndicatorFactory = (el: Element) => MDCTabIndicator; diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index 45180c98b5c..efd6b478409 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -178,7 +178,7 @@ class JsBundleFactory { slider: getAbsolutePath('/packages/mdc-slider/index.ts'), snackbar: getAbsolutePath('/packages/mdc-snackbar/index.ts'), switch: getAbsolutePath('/packages/mdc-switch/index.ts'), - tab: getAbsolutePath('/packages/mdc-tab/index.js'), + tab: getAbsolutePath('/packages/mdc-tab/index.ts'), tabBar: getAbsolutePath('/packages/mdc-tab-bar/index.js'), tabIndicator: getAbsolutePath('/packages/mdc-tab-indicator/index.ts'), tabScroller: getAbsolutePath('/packages/mdc-tab-scroller/index.ts'), diff --git a/test/unit/mdc-tab/mdc-tab.test.js b/test/unit/mdc-tab/mdc-tab.test.js index 9f81fe8d50a..9005a565774 100644 --- a/test/unit/mdc-tab/mdc-tab.test.js +++ b/test/unit/mdc-tab/mdc-tab.test.js @@ -60,7 +60,7 @@ test('click handler is added during initialSyncWithDOM', () => { const {component, root, mockFoundation} = setupTest({createMockFoundation: true}); domEvents.emit(root, 'click'); - td.verify(mockFoundation.handleClick(td.matchers.anything()), {times: 1}); + td.verify(mockFoundation.handleClick(), {times: 1}); component.destroy(); }); @@ -70,7 +70,7 @@ test('click handler is removed during destroy', () => { component.destroy(); domEvents.emit(root, 'click'); - td.verify(mockFoundation.handleClick(td.matchers.anything()), {times: 0}); + td.verify(mockFoundation.handleClick(), {times: 0}); }); test('#destroy removes the ripple', () => { From b2fa9a526877eb48879adde70e41bd978a92b1e4 Mon Sep 17 00:00:00 2001 From: "Andrew C. Dvorak" Date: Mon, 11 Feb 2019 21:12:19 -0800 Subject: [PATCH 4/4] feat(tab-bar): Convert JS to TypeScript (#4394) Refs #4225 --- .../mdc-tab-bar/{adapter.js => adapter.ts} | 89 +++---- .../{constants.js => constants.ts} | 19 +- .../{foundation.js => foundation.ts} | 222 ++++++++---------- packages/mdc-tab-bar/index.js | 206 ---------------- packages/mdc-tab-bar/index.ts | 175 ++++++++++++++ packages/mdc-tab-bar/types.ts | 28 +++ scripts/webpack/js-bundle-factory.js | 2 +- 7 files changed, 344 insertions(+), 397 deletions(-) rename packages/mdc-tab-bar/{adapter.js => adapter.ts} (54%) rename packages/mdc-tab-bar/{constants.js => constants.ts} (95%) rename packages/mdc-tab-bar/{foundation.js => foundation.ts} (71%) delete mode 100644 packages/mdc-tab-bar/index.js create mode 100644 packages/mdc-tab-bar/index.ts create mode 100644 packages/mdc-tab-bar/types.ts diff --git a/packages/mdc-tab-bar/adapter.js b/packages/mdc-tab-bar/adapter.ts similarity index 54% rename from packages/mdc-tab-bar/adapter.js rename to packages/mdc-tab-bar/adapter.ts index a24e8b10a6c..eb648f3d3f3 100644 --- a/packages/mdc-tab-bar/adapter.js +++ b/packages/mdc-tab-bar/adapter.ts @@ -21,128 +21,111 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - -/* eslint-disable no-unused-vars */ -import {MDCTabDimensions} from '@material/tab/adapter'; -/* eslint-enable no-unused-vars */ +import {MDCTabDimensions} from '@material/tab/types'; /** - * Adapter for MDC Tab Bar. - * - * Defines the shape of the adapter expected by the foundation. Implement this - * adapter to integrate the Tab 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 MDCTabBarAdapter { +interface MDCTabBarAdapter { /** * Scrolls to the given position - * @param {number} scrollX The position to scroll to + * @param scrollX The position to scroll to */ - scrollTo(scrollX) {} + scrollTo(scrollX: number): void; /** * Increments the current scroll position by the given amount - * @param {number} scrollXIncrement The amount to increment scroll + * @param scrollXIncrement The amount to increment scroll */ - incrementScroll(scrollXIncrement) {} + incrementScroll(scrollXIncrement: number): void; /** * Returns the current scroll position - * @return {number} */ - getScrollPosition() {} + getScrollPosition(): number; /** * Returns the width of the scroll content - * @return {number} */ - getScrollContentWidth() {} + getScrollContentWidth(): number; /** * Returns the root element's offsetWidth - * @return {number} */ - getOffsetWidth() {} + getOffsetWidth(): number; /** * Returns if the Tab Bar language direction is RTL - * @return {boolean} */ - isRTL() {} + isRTL(): boolean; /** * Sets the tab at the given index to be activated - * @param {number} index The index of the tab to activate + * @param index The index of the tab to activate */ - setActiveTab(index) {} + setActiveTab(index: number): void; /** * Activates the tab at the given index with the given client rect - * @param {number} index The index of the tab to activate - * @param {!ClientRect} clientRect The client rect of the previously active Tab Indicator + * @param index The index of the tab to activate + * @param clientRect The client rect of the previously active Tab Indicator */ - activateTabAtIndex(index, clientRect) {} + activateTabAtIndex(index: number, clientRect: ClientRect): void; /** * Deactivates the tab at the given index - * @param {number} index The index of the tab to deactivate + * @param index The index of the tab to deactivate */ - deactivateTabAtIndex(index) {} + deactivateTabAtIndex(index: number): void; /** * Focuses the tab at the given index - * @param {number} index The index of the tab to focus + * @param index The index of the tab to focus */ - focusTabAtIndex(index) {} + focusTabAtIndex(index: number): void; /** * Returns the client rect of the tab's indicator - * @param {number} index The index of the tab - * @return {!ClientRect} + * @param index The index of the tab */ - getTabIndicatorClientRectAtIndex(index) {} + getTabIndicatorClientRectAtIndex(index: number): ClientRect; /** * Returns the tab dimensions of the tab at the given index - * @param {number} index The index of the tab - * @return {!MDCTabDimensions} + * @param index The index of the tab */ - getTabDimensionsAtIndex(index) {} + getTabDimensionsAtIndex(index: number): MDCTabDimensions; /** * Returns the length of the tab list - * @return {number} */ - getTabListLength() {} + getTabListLength(): number; /** * Returns the index of the previously active tab - * @return {number} */ - getPreviousActiveTabIndex() {} + getPreviousActiveTabIndex(): number; /** * Returns the index of the focused tab - * @return {number} */ - getFocusedTabIndex() {} + getFocusedTabIndex(): number; /** * Returns the index of the given tab - * @param {string} id The ID of the tab whose index to determine - * @return {number} + * @param id The ID of the tab whose index to determine */ - getIndexOfTabById(id) {} + getIndexOfTabById(id: string): number; /** * Emits the MDCTabBar:activated event - * @param {number} index The index of the activated tab + * @param index The index of the activated tab */ - notifyTabActivated(index) {} + notifyTabActivated(index: number): void; } -export default MDCTabBarAdapter; +export {MDCTabBarAdapter as default, MDCTabBarAdapter}; diff --git a/packages/mdc-tab-bar/constants.js b/packages/mdc-tab-bar/constants.ts similarity index 95% rename from packages/mdc-tab-bar/constants.js rename to packages/mdc-tab-bar/constants.ts index c0ec5d780c4..7d63aca67a1 100644 --- a/packages/mdc-tab-bar/constants.js +++ b/packages/mdc-tab-bar/constants.ts @@ -21,31 +21,26 @@ * THE SOFTWARE. */ -/** @enum {string} */ const strings = { - TAB_ACTIVATED_EVENT: 'MDCTabBar:activated', - TAB_SCROLLER_SELECTOR: '.mdc-tab-scroller', - TAB_SELECTOR: '.mdc-tab', ARROW_LEFT_KEY: 'ArrowLeft', ARROW_RIGHT_KEY: 'ArrowRight', END_KEY: 'End', - HOME_KEY: 'Home', ENTER_KEY: 'Enter', + HOME_KEY: 'Home', SPACE_KEY: 'Space', + TAB_ACTIVATED_EVENT: 'MDCTabBar:activated', + TAB_SCROLLER_SELECTOR: '.mdc-tab-scroller', + TAB_SELECTOR: '.mdc-tab', }; -/** @enum {number} */ const numbers = { - EXTRA_SCROLL_AMOUNT: 20, ARROW_LEFT_KEYCODE: 37, ARROW_RIGHT_KEYCODE: 39, END_KEYCODE: 35, - HOME_KEYCODE: 36, ENTER_KEYCODE: 13, + EXTRA_SCROLL_AMOUNT: 20, + HOME_KEYCODE: 36, SPACE_KEYCODE: 32, }; -export { - numbers, - strings, -}; +export {numbers, strings}; diff --git a/packages/mdc-tab-bar/foundation.js b/packages/mdc-tab-bar/foundation.ts similarity index 71% rename from packages/mdc-tab-bar/foundation.js rename to packages/mdc-tab-bar/foundation.ts index 950bf4f0e39..624b87c53b6 100644 --- a/packages/mdc-tab-bar/foundation.js +++ b/packages/mdc-tab-bar/foundation.ts @@ -22,18 +22,11 @@ */ import {MDCFoundation} from '@material/base/foundation'; +import {MDCTabDimensions, TabInteractionEventDetail} from '@material/tab/types'; +import {MDCTabBarAdapter} from './adapter'; +import {numbers, strings} from './constants'; -import {strings, numbers} from './constants'; -import MDCTabBarAdapter from './adapter'; - -/* eslint-disable no-unused-vars */ -import {MDCTabDimensions} from '@material/tab/adapter'; -/* eslint-enable no-unused-vars */ - -/** - * @type {Set} - */ -const ACCEPTABLE_KEYS = new Set(); +const ACCEPTABLE_KEYS: Set = new Set(); // IE11 has no support for new Set with iterable so we need to initialize this by hand ACCEPTABLE_KEYS.add(strings.ARROW_LEFT_KEY); ACCEPTABLE_KEYS.add(strings.ARROW_RIGHT_KEY); @@ -42,10 +35,7 @@ ACCEPTABLE_KEYS.add(strings.HOME_KEY); ACCEPTABLE_KEYS.add(strings.ENTER_KEY); ACCEPTABLE_KEYS.add(strings.SPACE_KEY); -/** - * @type {Map} - */ -const KEYCODE_MAP = new Map(); +const KEYCODE_MAP: Map = new Map(); // IE11 has no support for new Map with iterable so we need to initialize this by hand KEYCODE_MAP.set(numbers.ARROW_LEFT_KEYCODE, strings.ARROW_LEFT_KEY); KEYCODE_MAP.set(numbers.ARROW_RIGHT_KEYCODE, strings.ARROW_RIGHT_KEY); @@ -54,71 +44,54 @@ KEYCODE_MAP.set(numbers.HOME_KEYCODE, strings.HOME_KEY); KEYCODE_MAP.set(numbers.ENTER_KEYCODE, strings.ENTER_KEY); KEYCODE_MAP.set(numbers.SPACE_KEYCODE, strings.SPACE_KEY); -/** - * @extends {MDCFoundation} - * @final - */ -class MDCTabBarFoundation extends MDCFoundation { - /** @return enum {string} */ +class MDCTabBarFoundation extends MDCFoundation { static get strings() { return strings; } - /** @return enum {number} */ static get numbers() { return numbers; } - /** - * @see MDCTabBarAdapter for typing information - * @return {!MDCTabBarAdapter} - */ - static get defaultAdapter() { - return /** @type {!MDCTabBarAdapter} */ ({ - scrollTo: () => {}, - incrementScroll: () => {}, - getScrollPosition: () => {}, - getScrollContentWidth: () => {}, - getOffsetWidth: () => {}, - isRTL: () => {}, - setActiveTab: () => {}, - activateTabAtIndex: () => {}, - deactivateTabAtIndex: () => {}, - focusTabAtIndex: () => {}, - getTabIndicatorClientRectAtIndex: () => {}, - getTabDimensionsAtIndex: () => {}, - getPreviousActiveTabIndex: () => {}, - getFocusedTabIndex: () => {}, - getIndexOfTabById: () => {}, - getTabListLength: () => {}, - notifyTabActivated: () => {}, - }); + static get defaultAdapter(): MDCTabBarAdapter { + // tslint:disable:object-literal-sort-keys + return { + scrollTo: () => undefined, + incrementScroll: () => undefined, + getScrollPosition: () => 0, + getScrollContentWidth: () => 0, + getOffsetWidth: () => 0, + isRTL: () => false, + setActiveTab: () => undefined, + activateTabAtIndex: () => undefined, + deactivateTabAtIndex: () => undefined, + focusTabAtIndex: () => undefined, + getTabIndicatorClientRectAtIndex: () => ({top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0}), + getTabDimensionsAtIndex: () => ({rootLeft: 0, rootRight: 0, contentLeft: 0, contentRight: 0}), + getPreviousActiveTabIndex: () => -1, + getFocusedTabIndex: () => -1, + getIndexOfTabById: () => -1, + getTabListLength: () => 0, + notifyTabActivated: () => undefined, + }; + // tslint:enable:object-literal-sort-keys } - /** - * @param {!MDCTabBarAdapter} adapter - * */ - constructor(adapter) { - super(Object.assign(MDCTabBarFoundation.defaultAdapter, adapter)); + private useAutomaticActivation_ = false; - /** @private {boolean} */ - this.useAutomaticActivation_ = false; + constructor(adapter?: Partial) { + super({...MDCTabBarFoundation.defaultAdapter, ...adapter}); } /** * Switches between automatic and manual activation modes. * See https://www.w3.org/TR/wai-aria-practices/#tabpanel for examples. - * @param {boolean} useAutomaticActivation */ - setUseAutomaticActivation(useAutomaticActivation) { + setUseAutomaticActivation(useAutomaticActivation: boolean) { this.useAutomaticActivation_ = useAutomaticActivation; } - /** - * Activates the tab at the given index - * @param {number} index - */ - activateTab(index) { + activateTab(index: number) { const previousActiveIndex = this.adapter_.getPreviousActiveTabIndex(); if (!this.indexIsInRange_(index) || index === previousActiveIndex) { return; @@ -131,11 +104,7 @@ class MDCTabBarFoundation extends MDCFoundation { this.adapter_.notifyTabActivated(index); } - /** - * Handles the keydown event - * @param {!Event} evt - */ - handleKeyDown(evt) { + handleKeyDown(evt: KeyboardEvent) { // Get the key from the event const key = this.getKeyFromEvent_(evt); @@ -171,17 +140,16 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Handles the MDCTab:interacted event - * @param {!CustomEvent} evt */ - handleTabInteraction(evt) { + handleTabInteraction(evt: CustomEvent) { this.adapter_.setActiveTab(this.adapter_.getIndexOfTabById(evt.detail.tabId)); } /** * Scrolls the tab at the given index into view - * @param {number} index The tab index to make visible + * @param index The tab index to make visible */ - scrollIntoView(index) { + scrollIntoView(index: number) { // Early exit if the index is out of range if (!this.indexIsInRange_(index)) { return; @@ -207,12 +175,10 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Private method for determining the index of the destination tab based on what key was pressed - * @param {number} origin The original index from which to determine the destination - * @param {string} key The name of the key - * @return {number} - * @private + * @param origin The original index from which to determine the destination + * @param key The name of the key */ - determineTargetFromKey_(origin, key) { + private determineTargetFromKey_(origin: number, key: string): number { const isRTL = this.isRTL_(); const maxIndex = this.adapter_.getTabListLength() - 1; const shouldGoToEnd = key === strings.END_KEY; @@ -241,14 +207,17 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Calculates the scroll increment that will make the tab at the given index visible - * @param {number} index The index of the tab - * @param {number} nextIndex The index of the next tab - * @param {number} scrollPosition The current scroll position - * @param {number} barWidth The width of the Tab Bar - * @return {number} - * @private + * @param index The index of the tab + * @param nextIndex The index of the next tab + * @param scrollPosition The current scroll position + * @param barWidth The width of the Tab Bar */ - calculateScrollIncrement_(index, nextIndex, scrollPosition, barWidth) { + private calculateScrollIncrement_( + index: number, + nextIndex: number, + scrollPosition: number, + barWidth: number, + ): number { const nextTabDimensions = this.adapter_.getTabDimensionsAtIndex(nextIndex); const relativeContentLeft = nextTabDimensions.contentLeft - scrollPosition - barWidth; const relativeContentRight = nextTabDimensions.contentRight - scrollPosition; @@ -264,15 +233,19 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Calculates the scroll increment that will make the tab at the given index visible in RTL - * @param {number} index The index of the tab - * @param {number} nextIndex The index of the next tab - * @param {number} scrollPosition The current scroll position - * @param {number} barWidth The width of the Tab Bar - * @param {number} scrollContentWidth The width of the scroll content - * @return {number} - * @private + * @param index The index of the tab + * @param nextIndex The index of the next tab + * @param scrollPosition The current scroll position + * @param barWidth The width of the Tab Bar + * @param scrollContentWidth The width of the scroll content */ - calculateScrollIncrementRTL_(index, nextIndex, scrollPosition, barWidth, scrollContentWidth) { + private calculateScrollIncrementRTL_( + index: number, + nextIndex: number, + scrollPosition: number, + barWidth: number, + scrollContentWidth: number, + ): number { const nextTabDimensions = this.adapter_.getTabDimensionsAtIndex(nextIndex); const relativeContentLeft = scrollContentWidth - nextTabDimensions.contentLeft - scrollPosition; const relativeContentRight = scrollContentWidth - nextTabDimensions.contentRight - scrollPosition - barWidth; @@ -288,14 +261,17 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Determines the index of the adjacent tab closest to either edge of the Tab Bar - * @param {number} index The index of the tab - * @param {!MDCTabDimensions} tabDimensions The dimensions of the tab - * @param {number} scrollPosition The current scroll position - * @param {number} barWidth The width of the tab bar - * @return {number} - * @private + * @param index The index of the tab + * @param tabDimensions The dimensions of the tab + * @param scrollPosition The current scroll position + * @param barWidth The width of the tab bar */ - findAdjacentTabIndexClosestToEdge_(index, tabDimensions, scrollPosition, barWidth) { + private findAdjacentTabIndexClosestToEdge_( + index: number, + tabDimensions: MDCTabDimensions, + scrollPosition: number, + barWidth: number, + ): number { /** * Tabs are laid out in the Tab Scroller like this: * @@ -339,15 +315,19 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Determines the index of the adjacent tab closest to either edge of the Tab Bar in RTL - * @param {number} index The index of the tab - * @param {!MDCTabDimensions} tabDimensions The dimensions of the tab - * @param {number} scrollPosition The current scroll position - * @param {number} barWidth The width of the tab bar - * @param {number} scrollContentWidth The width of the scroller content - * @return {number} - * @private + * @param index The index of the tab + * @param tabDimensions The dimensions of the tab + * @param scrollPosition The current scroll position + * @param barWidth The width of the tab bar + * @param scrollContentWidth The width of the scroller content */ - findAdjacentTabIndexClosestToEdgeRTL_(index, tabDimensions, scrollPosition, barWidth, scrollContentWidth) { + private findAdjacentTabIndexClosestToEdgeRTL_( + index: number, + tabDimensions: MDCTabDimensions, + scrollPosition: number, + barWidth: number, + scrollContentWidth: number, + ): number { const rootLeft = scrollContentWidth - tabDimensions.rootLeft - barWidth - scrollPosition; const rootRight = scrollContentWidth - tabDimensions.rootRight - scrollPosition; const rootDelta = rootLeft + rootRight; @@ -367,46 +347,39 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Returns the key associated with a keydown event - * @param {!Event} evt The keydown event - * @return {string} - * @private + * @param evt The keydown event */ - getKeyFromEvent_(evt) { + private getKeyFromEvent_(evt: KeyboardEvent): string { if (ACCEPTABLE_KEYS.has(evt.key)) { return evt.key; } - - return KEYCODE_MAP.get(evt.keyCode); + return KEYCODE_MAP.get(evt.keyCode)!; } - isActivationKey_(key) { + private isActivationKey_(key: string) { return key === strings.SPACE_KEY || key === strings.ENTER_KEY; } /** * Returns whether a given index is inclusively between the ends - * @param {number} index The index to test - * @private + * @param index The index to test */ - indexIsInRange_(index) { + private indexIsInRange_(index: number) { return index >= 0 && index < this.adapter_.getTabListLength(); } /** * Returns the view's RTL property - * @return {boolean} - * @private */ - isRTL_() { + private isRTL_(): boolean { return this.adapter_.isRTL(); } /** - * Scrolls the tab at the given index into view for left-to-right useragents - * @param {number} index The index of the tab to scroll into view - * @private + * Scrolls the tab at the given index into view for left-to-right user agents. + * @param index The index of the tab to scroll into view */ - scrollIntoView_(index) { + private scrollIntoView_(index: number) { const scrollPosition = this.adapter_.getScrollPosition(); const barWidth = this.adapter_.getOffsetWidth(); const tabDimensions = this.adapter_.getTabDimensionsAtIndex(index); @@ -422,10 +395,9 @@ class MDCTabBarFoundation extends MDCFoundation { /** * Scrolls the tab at the given index into view in RTL - * @param {number} index The tab index to make visible - * @private + * @param index The tab index to make visible */ - scrollIntoViewRTL_(index) { + private scrollIntoViewRTL_(index: number) { const scrollPosition = this.adapter_.getScrollPosition(); const barWidth = this.adapter_.getOffsetWidth(); const tabDimensions = this.adapter_.getTabDimensionsAtIndex(index); @@ -442,4 +414,4 @@ class MDCTabBarFoundation extends MDCFoundation { } } -export default MDCTabBarFoundation; +export {MDCTabBarFoundation as default, MDCTabBarFoundation}; diff --git a/packages/mdc-tab-bar/index.js b/packages/mdc-tab-bar/index.js deleted file mode 100644 index 93785225532..00000000000 --- a/packages/mdc-tab-bar/index.js +++ /dev/null @@ -1,206 +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 {MDCComponent} from '@material/base/component'; - -import {MDCTab, MDCTabFoundation} from '@material/tab/index'; -import {MDCTabScroller} from '@material/tab-scroller/index'; - -import MDCTabBarAdapter from './adapter'; -import MDCTabBarFoundation from './foundation'; - -let tabIdCounter = 0; - -/** - * @extends {MDCComponent} - * @final - */ -class MDCTabBar extends MDCComponent { - /** - * @param {...?} args - */ - constructor(...args) { - super(...args); - - /** @private {!Array} */ - this.tabList_; - - /** @private {?MDCTabScroller} */ - this.tabScroller_; - - /** @private {?function(?Event): undefined} */ - this.handleTabInteraction_; - - /** @private {?function(?Event): undefined} */ - this.handleKeyDown_; - } - - /** - * @param {!Element} root - * @return {!MDCTabBar} - */ - static attachTo(root) { - return new MDCTabBar(root); - } - - set focusOnActivate(focusOnActivate) { - this.tabList_.forEach((tab) => tab.focusOnActivate = focusOnActivate); - } - - set useAutomaticActivation(useAutomaticActivation) { - this.foundation_.setUseAutomaticActivation(useAutomaticActivation); - } - - /** - * @param {(function(!Element): !MDCTab)=} tabFactory A function which creates a new MDCTab - * @param {(function(!Element): !MDCTabScroller)=} tabScrollerFactory A function which creates a new MDCTabScroller - */ - initialize( - tabFactory = (el) => new MDCTab(el), - tabScrollerFactory = (el) => new MDCTabScroller(el)) { - this.tabList_ = this.instantiateTabs_(tabFactory); - this.tabScroller_ = this.instantiateTabScroller_(tabScrollerFactory); - } - - initialSyncWithDOM() { - this.handleTabInteraction_ = (evt) => this.foundation_.handleTabInteraction(evt); - this.handleKeyDown_ = (evt) => this.foundation_.handleKeyDown(evt); - - this.root_.addEventListener(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_); - this.root_.addEventListener('keydown', this.handleKeyDown_); - - for (let i = 0; i < this.tabList_.length; i++) { - if (this.tabList_[i].active) { - this.scrollIntoView(i); - break; - } - } - } - - destroy() { - super.destroy(); - this.root_.removeEventListener(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_); - this.root_.removeEventListener('keydown', this.handleKeyDown_); - this.tabList_.forEach((tab) => tab.destroy()); - this.tabScroller_.destroy(); - } - - /** - * @return {!MDCTabBarFoundation} - */ - getDefaultFoundation() { - return new MDCTabBarFoundation( - /** @type {!MDCTabBarAdapter} */ ({ - scrollTo: (scrollX) => this.tabScroller_.scrollTo(scrollX), - incrementScroll: (scrollXIncrement) => this.tabScroller_.incrementScroll(scrollXIncrement), - getScrollPosition: () => this.tabScroller_.getScrollPosition(), - getScrollContentWidth: () => this.tabScroller_.getScrollContentWidth(), - getOffsetWidth: () => this.root_.offsetWidth, - isRTL: () => window.getComputedStyle(this.root_).getPropertyValue('direction') === 'rtl', - setActiveTab: (index) => this.foundation_.activateTab(index), - activateTabAtIndex: (index, clientRect) => this.tabList_[index].activate(clientRect), - deactivateTabAtIndex: (index) => this.tabList_[index].deactivate(), - focusTabAtIndex: (index) => this.tabList_[index].focus(), - getTabIndicatorClientRectAtIndex: (index) => this.tabList_[index].computeIndicatorClientRect(), - getTabDimensionsAtIndex: (index) => this.tabList_[index].computeDimensions(), - getPreviousActiveTabIndex: () => { - for (let i = 0; i < this.tabList_.length; i++) { - if (this.tabList_[i].active) { - return i; - } - } - return -1; - }, - getFocusedTabIndex: () => { - const tabElements = this.getTabElements_(); - const activeElement = document.activeElement; - return tabElements.indexOf(activeElement); - }, - getIndexOfTabById: (id) => { - for (let i = 0; i < this.tabList_.length; i++) { - if (this.tabList_[i].id === id) { - return i; - } - } - return -1; - }, - getTabListLength: () => this.tabList_.length, - notifyTabActivated: (index) => this.emit(MDCTabBarFoundation.strings.TAB_ACTIVATED_EVENT, {index}, true), - }) - ); - } - - /** - * Activates the tab at the given index - * @param {number} index The index of the tab - */ - activateTab(index) { - this.foundation_.activateTab(index); - } - - /** - * Scrolls the tab at the given index into view - * @param {number} index THe index of the tab - */ - scrollIntoView(index) { - this.foundation_.scrollIntoView(index); - } - - /** - * Returns all the tab elements in a nice clean array - * @return {!Array} - * @private - */ - getTabElements_() { - return [].slice.call(this.root_.querySelectorAll(MDCTabBarFoundation.strings.TAB_SELECTOR)); - } - - /** - * Instantiates tab components on all child tab elements - * @param {(function(!Element): !MDCTab)} tabFactory - * @return {!Array} - * @private - */ - instantiateTabs_(tabFactory) { - return this.getTabElements_().map((el) => { - el.id = el.id || `mdc-tab-${++tabIdCounter}`; - return tabFactory(el); - }); - } - - /** - * Instantiates tab scroller component on the child tab scroller element - * @param {(function(!Element): !MDCTabScroller)} tabScrollerFactory - * @return {?MDCTabScroller} - * @private - */ - instantiateTabScroller_(tabScrollerFactory) { - const tabScrollerElement = this.root_.querySelector(MDCTabBarFoundation.strings.TAB_SCROLLER_SELECTOR); - if (tabScrollerElement) { - return tabScrollerFactory(tabScrollerElement); - } - return null; - } -} - -export {MDCTabBar, MDCTabBarFoundation}; diff --git a/packages/mdc-tab-bar/index.ts b/packages/mdc-tab-bar/index.ts new file mode 100644 index 00000000000..c3c45b211e5 --- /dev/null +++ b/packages/mdc-tab-bar/index.ts @@ -0,0 +1,175 @@ +/** + * @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 {MDCComponent} from '@material/base/component'; +import {CustomEventListener, SpecificEventListener} from '@material/base/types'; +import {MDCTabScroller} from '@material/tab-scroller/index'; +import {MDCTab, MDCTabFoundation, TabInteractionEvent} from '@material/tab/index'; +import {MDCTabBarFoundation} from './foundation'; +import {TabFactory, TabScrollerFactory} from './types'; + +let tabIdCounter = 0; + +class MDCTabBar extends MDCComponent { + static attachTo(root: Element): MDCTabBar { + return new MDCTabBar(root); + } + + private tabList_!: MDCTab[]; // assigned in initialize() + private tabScroller_!: MDCTabScroller | null; // assigned in initialize() + private handleTabInteraction_!: CustomEventListener; // assigned in initialSyncWithDOM() + private handleKeyDown_!: SpecificEventListener<'keydown'>; // assigned in initialSyncWithDOM() + + set focusOnActivate(focusOnActivate: boolean) { + this.tabList_.forEach((tab) => tab.focusOnActivate = focusOnActivate); + } + + set useAutomaticActivation(useAutomaticActivation: boolean) { + this.foundation_.setUseAutomaticActivation(useAutomaticActivation); + } + + initialize( + tabFactory: TabFactory = (el) => new MDCTab(el), + tabScrollerFactory: TabScrollerFactory = (el) => new MDCTabScroller(el), + ) { + this.tabList_ = this.instantiateTabs_(tabFactory); + this.tabScroller_ = this.instantiateTabScroller_(tabScrollerFactory); + } + + initialSyncWithDOM() { + this.handleTabInteraction_ = (evt) => this.foundation_.handleTabInteraction(evt); + this.handleKeyDown_ = (evt) => this.foundation_.handleKeyDown(evt); + + this.listen(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_); + this.listen('keydown', this.handleKeyDown_); + + for (let i = 0; i < this.tabList_.length; i++) { + if (this.tabList_[i].active) { + this.scrollIntoView(i); + break; + } + } + } + + destroy() { + super.destroy(); + this.unlisten(MDCTabFoundation.strings.INTERACTED_EVENT, this.handleTabInteraction_); + this.unlisten('keydown', this.handleKeyDown_); + this.tabList_.forEach((tab) => tab.destroy()); + + if (this.tabScroller_) { + this.tabScroller_.destroy(); + } + } + + getDefaultFoundation(): MDCTabBarFoundation { + // tslint:disable:object-literal-sort-keys + return new MDCTabBarFoundation({ + scrollTo: (scrollX) => this.tabScroller_!.scrollTo(scrollX), + incrementScroll: (scrollXIncrement) => this.tabScroller_!.incrementScroll(scrollXIncrement), + getScrollPosition: () => this.tabScroller_!.getScrollPosition(), + getScrollContentWidth: () => this.tabScroller_!.getScrollContentWidth(), + getOffsetWidth: () => (this.root_ as HTMLElement).offsetWidth, + isRTL: () => window.getComputedStyle(this.root_).getPropertyValue('direction') === 'rtl', + setActiveTab: (index) => this.foundation_.activateTab(index), + activateTabAtIndex: (index, clientRect) => this.tabList_[index].activate(clientRect), + deactivateTabAtIndex: (index) => this.tabList_[index].deactivate(), + focusTabAtIndex: (index) => this.tabList_[index].focus(), + getTabIndicatorClientRectAtIndex: (index) => this.tabList_[index].computeIndicatorClientRect(), + getTabDimensionsAtIndex: (index) => this.tabList_[index].computeDimensions(), + getPreviousActiveTabIndex: () => { + for (let i = 0; i < this.tabList_.length; i++) { + if (this.tabList_[i].active) { + return i; + } + } + return -1; + }, + getFocusedTabIndex: () => { + const tabElements = this.getTabElements_(); + const activeElement = document.activeElement!; + return tabElements.indexOf(activeElement); + }, + getIndexOfTabById: (id) => { + for (let i = 0; i < this.tabList_.length; i++) { + if (this.tabList_[i].id === id) { + return i; + } + } + return -1; + }, + getTabListLength: () => this.tabList_.length, + notifyTabActivated: (index) => this.emit(MDCTabBarFoundation.strings.TAB_ACTIVATED_EVENT, {index}, true), + }); + // tslint:enable:object-literal-sort-keys + } + + /** + * Activates the tab at the given index + * @param index The index of the tab + */ + activateTab(index: number) { + this.foundation_.activateTab(index); + } + + /** + * Scrolls the tab at the given index into view + * @param index THe index of the tab + */ + scrollIntoView(index: number) { + this.foundation_.scrollIntoView(index); + } + + /** + * Returns all the tab elements in a nice clean array + */ + private getTabElements_(): Element[] { + return [].slice.call(this.root_.querySelectorAll(MDCTabBarFoundation.strings.TAB_SELECTOR)); + } + + /** + * Instantiates tab components on all child tab elements + */ + private instantiateTabs_(tabFactory: TabFactory) { + return this.getTabElements_().map((el) => { + el.id = el.id || `mdc-tab-${++tabIdCounter}`; + return tabFactory(el); + }); + } + + /** + * Instantiates tab scroller component on the child tab scroller element + */ + private instantiateTabScroller_(tabScrollerFactory: TabScrollerFactory): MDCTabScroller | null { + const tabScrollerElement = this.root_.querySelector(MDCTabBarFoundation.strings.TAB_SCROLLER_SELECTOR); + if (tabScrollerElement) { + return tabScrollerFactory(tabScrollerElement); + } + return null; + } +} + +export {MDCTabBar as default, MDCTabBar}; +export * from './adapter'; +export * from './foundation'; +export * from './types'; diff --git a/packages/mdc-tab-bar/types.ts b/packages/mdc-tab-bar/types.ts new file mode 100644 index 00000000000..f36458e9728 --- /dev/null +++ b/packages/mdc-tab-bar/types.ts @@ -0,0 +1,28 @@ +/** + * @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 {MDCTabScroller} from '@material/tab-scroller/index'; +import {MDCTab} from '@material/tab/index'; + +export type TabFactory = (el: Element) => MDCTab; +export type TabScrollerFactory = (el: Element) => MDCTabScroller; diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index efd6b478409..5f65c1d57b0 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -179,7 +179,7 @@ class JsBundleFactory { snackbar: getAbsolutePath('/packages/mdc-snackbar/index.ts'), switch: getAbsolutePath('/packages/mdc-switch/index.ts'), tab: getAbsolutePath('/packages/mdc-tab/index.ts'), - tabBar: getAbsolutePath('/packages/mdc-tab-bar/index.js'), + tabBar: getAbsolutePath('/packages/mdc-tab-bar/index.ts'), tabIndicator: getAbsolutePath('/packages/mdc-tab-indicator/index.ts'), tabScroller: getAbsolutePath('/packages/mdc-tab-scroller/index.ts'), tabs: getAbsolutePath('/packages/mdc-tabs/index.js'),