Skip to content

Commit

Permalink
feat(drawer): Convert JS to TypeScript (#4390)
Browse files Browse the repository at this point in the history
Refs #4225
  • Loading branch information
acdvorak authored Feb 14, 2019
1 parent 5052ada commit f8ba48f
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 244 deletions.
6 changes: 3 additions & 3 deletions packages/mdc-dialog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class MDCDialog extends MDCComponent<MDCDialogFoundation> {
private container_!: HTMLElement; // assigned in initialize()
private content_!: HTMLElement | null; // assigned in initialize()
private defaultButton_!: HTMLElement | null; // assigned in initialize()
private initialFocusEl_!: HTMLElement | null; // assigned in initialize()
private initialFocusEl_?: HTMLElement; // assigned in initialize()

private focusTrap_!: createFocusTrap.FocusTrap; // assigned in initialSyncWithDOM()
private focusTrapFactory_!: FocusTrapFactory; // assigned in initialize()
Expand All @@ -83,7 +83,7 @@ class MDCDialog extends MDCComponent<MDCDialogFoundation> {

initialize(
focusTrapFactory: FocusTrapFactory = createFocusTrap as unknown as FocusTrapFactory,
initialFocusEl: Element | null = null) {
initialFocusEl?: HTMLElement) {
const container = this.root_.querySelector<HTMLElement>(strings.CONTAINER_SELECTOR);
if (!container) {
throw new Error(`Dialog component requires a ${strings.CONTAINER_SELECTOR} container element`);
Expand All @@ -93,7 +93,7 @@ class MDCDialog extends MDCComponent<MDCDialogFoundation> {
this.buttons_ = [].slice.call(this.root_.querySelectorAll<HTMLElement>(strings.BUTTON_SELECTOR));
this.defaultButton_ = this.root_.querySelector<HTMLElement>(strings.DEFAULT_BUTTON_SELECTOR);
this.focusTrapFactory_ = focusTrapFactory;
this.initialFocusEl_ = initialFocusEl as HTMLElement;
this.initialFocusEl_ = initialFocusEl;
this.buttonRipples_ = [];

for (const buttonEl of this.buttons_) {
Expand Down
18 changes: 8 additions & 10 deletions packages/mdc-dialog/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,24 @@
import * as createFocusTrap from 'focus-trap';
import {FocusTrapFactory} from './types';

function createFocusTrapInstance(
export function createFocusTrapInstance(
surfaceEl: HTMLElement,
focusTrapFactory: FocusTrapFactory = createFocusTrap as unknown as FocusTrapFactory,
initialFocusEl: createFocusTrap.FocusTarget | null,
initialFocusEl?: createFocusTrap.FocusTarget,
): createFocusTrap.FocusTrap {
return focusTrapFactory(surfaceEl, ({
clickOutsideDeactivates: true, // Allow handling of scrim clicks
escapeDeactivates: false, // Dialog foundation handles escape key
return focusTrapFactory(surfaceEl, {
clickOutsideDeactivates: true, // Allow handling of scrim clicks.
escapeDeactivates: false, // Foundation handles ESC key.
initialFocus: initialFocusEl,
} as createFocusTrap.Options));
});
}

function isScrollable(el: HTMLElement | null): boolean {
export function isScrollable(el: HTMLElement | null): boolean {
return el ? el.scrollHeight > el.offsetHeight : false;
}

function areTopsMisaligned(els: HTMLElement[]): boolean {
export function areTopsMisaligned(els: HTMLElement[]): boolean {
const tops = new Set();
[].forEach.call(els, (el: HTMLElement) => tops.add(el.offsetTop));
return tops.size > 1;
}

export {createFocusTrapInstance, isScrollable, areTopsMisaligned};
49 changes: 20 additions & 29 deletions packages/mdc-drawer/adapter.js → packages/mdc-drawer/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,79 +21,70 @@
* THE SOFTWARE.
*/

/* eslint no-unused-vars: [2, {"args": "none"}] */

/**
* Adapter for MDC Drawer
*
* Defines the shape of the adapter expected by the foundation. Implement this
* adapter to integrate the Drawer 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 MDCDrawerAdapter {
interface MDCDrawerAdapter {
/**
* Adds a class to the root Element.
* @param {string} className
*/
addClass(className) {}
addClass(className: string): void;

/**
* Removes a class from the root Element.
* @param {string} className
*/
removeClass(className) {}
removeClass(className: string): void;

/**
* Returns true if the root Element contains the given class.
* @param {string} className
* @return {boolean}
*/
hasClass(className) {}
hasClass(className: string): boolean;

/**
* @param {!Element} element target element to verify class name
* @param {string} className class name
* @param element target element to verify class name
* @param className class name
*/
elementHasClass(element, className) {}
elementHasClass(element: Element, className: string): boolean;

/**
* Saves the focus of currently active element.
*/
saveFocus() {}
saveFocus(): void;

/**
* Restores focus to element previously saved with 'saveFocus'.
*/
restoreFocus() {}
restoreFocus(): void;

/**
* Focuses the active / selected navigation item.
*/
focusActiveNavigationItem() {}
focusActiveNavigationItem(): void;

/**
* Emits a custom event "MDCDrawer:closed" denoting the drawer has closed.
*/
notifyClose() {}
notifyClose(): void;

/**
* Emits a custom event "MDCDrawer:opened" denoting the drawer has opened.
*/
notifyOpen() {}
notifyOpen(): void;

/**
* Traps focus on root element and focuses the active navigation element.
*/
trapFocus() {}
trapFocus(): void;

/**
* Releases focus trap from root element which was set by `trapFocus`
* and restores focus to where it was prior to calling `trapFocus`.
*/
releaseFocus() {}
releaseFocus(): void;
}

export default MDCDrawerAdapter;
export {MDCDrawerAdapter as default, MDCDrawerAdapter};
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,22 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/** @enum {string} */

const cssClasses = {
ROOT: 'mdc-drawer',
ANIMATE: 'mdc-drawer--animate',
CLOSING: 'mdc-drawer--closing',
DISMISSIBLE: 'mdc-drawer--dismissible',
MODAL: 'mdc-drawer--modal',
OPEN: 'mdc-drawer--open',
ANIMATE: 'mdc-drawer--animate',
OPENING: 'mdc-drawer--opening',
CLOSING: 'mdc-drawer--closing',
ROOT: 'mdc-drawer',
};

/** @enum {string} */
const strings = {
APP_CONTENT_SELECTOR: '.mdc-drawer-app-content',
SCRIM_SELECTOR: '.mdc-drawer-scrim',
CLOSE_EVENT: 'MDCDrawer:closed',
OPEN_EVENT: 'MDCDrawer:opened',
SCRIM_SELECTOR: '.mdc-drawer-scrim',
};

export {cssClasses, strings};
Original file line number Diff line number Diff line change
Expand Up @@ -21,48 +21,42 @@
* THE SOFTWARE.
*/

import MDCDrawerAdapter from '../adapter';
import {MDCFoundation} from '@material/base/foundation';
import {MDCDrawerAdapter} from '../adapter';
import {cssClasses, strings} from '../constants';

/**
* @extends {MDCFoundation<!MDCDrawerAdapter>}
*/
class MDCDismissibleDrawerFoundation extends MDCFoundation {
/** @return enum {string} */
class MDCDismissibleDrawerFoundation extends MDCFoundation<MDCDrawerAdapter> {
static get strings() {
return strings;
}

/** @return enum {string} */
static get cssClasses() {
return cssClasses;
}

static get defaultAdapter() {
return /** @type {!MDCDrawerAdapter} */ ({
addClass: (/* className: string */) => {},
removeClass: (/* className: string */) => {},
hasClass: (/* className: string */) => {},
elementHasClass: (/* element: !Element, className: string */) => {},
notifyClose: () => {},
notifyOpen: () => {},
saveFocus: () => {},
restoreFocus: () => {},
focusActiveNavigationItem: () => {},
trapFocus: () => {},
releaseFocus: () => {},
});
static get defaultAdapter(): MDCDrawerAdapter {
// tslint:disable:object-literal-sort-keys
return {
addClass: () => undefined,
removeClass: () => undefined,
hasClass: () => false,
elementHasClass: () => false,
notifyClose: () => undefined,
notifyOpen: () => undefined,
saveFocus: () => undefined,
restoreFocus: () => undefined,
focusActiveNavigationItem: () => undefined,
trapFocus: () => undefined,
releaseFocus: () => undefined,
};
// tslint:enable:object-literal-sort-keys
}

constructor(adapter) {
super(Object.assign(MDCDismissibleDrawerFoundation.defaultAdapter, adapter));

/** @private {number} */
this.animationFrame_ = 0;
private animationFrame_ = 0;
private animationTimer_ = 0;

/** @private {number} */
this.animationTimer_ = 0;
constructor(adapter?: Partial<MDCDrawerAdapter>) {
super({...MDCDismissibleDrawerFoundation.defaultAdapter, ...adapter});
}

destroy() {
Expand All @@ -74,9 +68,6 @@ class MDCDismissibleDrawerFoundation extends MDCFoundation {
}
}

/**
* Function to open the drawer.
*/
open() {
if (this.isOpen() || this.isOpening() || this.isClosing()) {
return;
Expand All @@ -93,9 +84,6 @@ class MDCDismissibleDrawerFoundation extends MDCFoundation {
this.adapter_.saveFocus();
}

/**
* Function to close the drawer.
*/
close() {
if (!this.isOpen() || this.isOpening() || this.isClosing()) {
return;
Expand All @@ -105,48 +93,31 @@ class MDCDismissibleDrawerFoundation extends MDCFoundation {
}

/**
* Extension point for when drawer finishes open animation.
* @protected
* @return true if drawer is in open state.
*/
opened() {}

/**
* Extension point for when drawer finishes close animation.
* @protected
*/
closed() {}

/**
* Returns true if drawer is in open state.
* @return {boolean}
*/
isOpen() {
isOpen(): boolean {
return this.adapter_.hasClass(cssClasses.OPEN);
}

/**
* Returns true if drawer is animating open.
* @return {boolean}
* @return true if drawer is animating open.
*/
isOpening() {
isOpening(): boolean {
return this.adapter_.hasClass(cssClasses.OPENING) || this.adapter_.hasClass(cssClasses.ANIMATE);
}

/**
* Returns true if drawer is animating closed.
* @return {boolean}
* @return true if drawer is animating closed.
*/
isClosing() {
isClosing(): boolean {
return this.adapter_.hasClass(cssClasses.CLOSING);
}

/**
* Keydown handler to close drawer when key is escape.
* @param evt
*/
handleKeydown(evt) {
handleKeydown(evt: KeyboardEvent) {
const {keyCode, key} = evt;

const isEscape = key === 'Escape' || keyCode === 27;
if (isEscape) {
this.close();
Expand All @@ -155,14 +126,13 @@ class MDCDismissibleDrawerFoundation extends MDCFoundation {

/**
* Handles a transition end event on the root element.
* @param {!Event} evt
*/
handleTransitionEnd(evt) {
handleTransitionEnd(evt: TransitionEvent) {
const {OPENING, CLOSING, OPEN, ANIMATE, ROOT} = cssClasses;

// In Edge, transitionend on ripple pseudo-elements yields a target without classList, so check for Element first.
const isElement = evt.target instanceof Element;
if (!isElement || !this.adapter_.elementHasClass(/** @type {!Element} */ (evt.target), ROOT)) {
const isRootElement = this.isElement_(evt.target) && this.adapter_.elementHasClass(evt.target, ROOT);
if (!isRootElement) {
return;
}

Expand All @@ -182,19 +152,32 @@ class MDCDismissibleDrawerFoundation extends MDCFoundation {
this.adapter_.removeClass(CLOSING);
}

/**
* Extension point for when drawer finishes open animation.
*/
protected opened() {} // tslint:disable-line:no-empty

/**
* Extension point for when drawer finishes close animation.
*/
protected closed() {} // tslint:disable-line:no-empty

/**
* Runs the given logic on the next animation frame, using setTimeout to factor in Firefox reflow behavior.
* @param {Function} callback
* @private
*/
runNextAnimationFrame_(callback) {
private runNextAnimationFrame_(callback: () => void) {
cancelAnimationFrame(this.animationFrame_);
this.animationFrame_ = requestAnimationFrame(() => {
this.animationFrame_ = 0;
clearTimeout(this.animationTimer_);
this.animationTimer_ = setTimeout(callback, 0);
});
}

private isElement_(element: unknown): element is Element {
// In Edge, transitionend on ripple pseudo-elements yields a target without classList.
return Boolean((element as Element).classList);
}
}

export default MDCDismissibleDrawerFoundation;
export {MDCDismissibleDrawerFoundation as default, MDCDismissibleDrawerFoundation};
Loading

0 comments on commit f8ba48f

Please sign in to comment.