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

feat(drawer): Convert JS to TypeScript #4390

Merged
merged 15 commits into from
Feb 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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