Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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 5 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
20 changes: 9 additions & 11 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 | null,
): createFocusTrap.FocusTrap {
return focusTrapFactory(surfaceEl, ({
clickOutsideDeactivates: true, // Allow handling of scrim clicks
escapeDeactivates: false, // Dialog foundation handles escape key
initialFocus: initialFocusEl,
} as createFocusTrap.Options));
return focusTrapFactory(surfaceEl, {
clickOutsideDeactivates: true, // Allow handling of scrim clicks.
escapeDeactivates: false, // Foundation handles ESC key.
initialFocus: initialFocusEl || undefined,
acdvorak marked this conversation as resolved.
Show resolved Hide resolved
});
}

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
*/
opened() {}

/**
* Extension point for when drawer finishes close animation.
* @protected
* @return true if drawer is in open state.
*/
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: Event) {
acdvorak marked this conversation as resolved.
Show resolved Hide resolved
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)) {
if (!isElement || !this.adapter_.elementHasClass(evt.target as Element, ROOT)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a good place for a guard:

function isElement(element: any): element is Element {
  return element instanceof Element;
}

if (!this.adapter_.elementHasClass(isElement(evt.target), ROOT)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. instanceof checks make it harder to mock objects in unit tests, so I'm testing for the presence of classList instead.

class MDCDismissibleDrawerFoundation {
  handleTransitionEnd(evt: TransitionEvent) {
    // In Edge, transitionend on ripple pseudo-elements yields a target without classList, so check for Element first.
    const isRootElement = this.isElement_(evt.target) && this.adapter_.elementHasClass(evt.target, ROOT);
    if (!isRootElement) {
      return;
    }
  }

  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);
  }
}

return;
}

Expand All @@ -182,12 +152,20 @@ 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: Function) {
acdvorak marked this conversation as resolved.
Show resolved Hide resolved
cancelAnimationFrame(this.animationFrame_);
this.animationFrame_ = requestAnimationFrame(() => {
this.animationFrame_ = 0;
Expand All @@ -197,4 +175,4 @@ class MDCDismissibleDrawerFoundation extends MDCFoundation {
}
}

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