UNPKG

@material/dialog

Version:
383 lines • 17.2 kB
/** * @license * Copyright 2017 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 { __assign, __extends } from "tslib"; import { AnimationFrame } from '@material/animation/animationframe'; import { MDCFoundation } from '@material/base/foundation'; import { cssClasses, numbers, strings } from './constants'; var AnimationKeys; (function (AnimationKeys) { AnimationKeys["POLL_SCROLL_POS"] = "poll_scroll_position"; AnimationKeys["POLL_LAYOUT_CHANGE"] = "poll_layout_change"; })(AnimationKeys || (AnimationKeys = {})); var MDCDialogFoundation = /** @class */ (function (_super) { __extends(MDCDialogFoundation, _super); function MDCDialogFoundation(adapter) { var _this = _super.call(this, __assign(__assign({}, MDCDialogFoundation.defaultAdapter), adapter)) || this; _this.dialogOpen = false; _this.isFullscreen = false; _this.animationFrame = 0; _this.animationTimer = 0; _this.escapeKeyAction = strings.CLOSE_ACTION; _this.scrimClickAction = strings.CLOSE_ACTION; _this.autoStackButtons = true; _this.areButtonsStacked = false; _this.suppressDefaultPressSelector = strings.SUPPRESS_DEFAULT_PRESS_SELECTOR; _this.animFrame = new AnimationFrame(); _this.contentScrollHandler = function () { _this.handleScrollEvent(); }; _this.windowResizeHandler = function () { _this.layout(); }; _this.windowOrientationChangeHandler = function () { _this.layout(); }; return _this; } Object.defineProperty(MDCDialogFoundation, "cssClasses", { get: function () { return cssClasses; }, enumerable: false, configurable: true }); Object.defineProperty(MDCDialogFoundation, "strings", { get: function () { return strings; }, enumerable: false, configurable: true }); Object.defineProperty(MDCDialogFoundation, "numbers", { get: function () { return numbers; }, enumerable: false, configurable: true }); Object.defineProperty(MDCDialogFoundation, "defaultAdapter", { get: function () { return { addBodyClass: function () { return undefined; }, addClass: function () { return undefined; }, areButtonsStacked: function () { return false; }, clickDefaultButton: function () { return undefined; }, eventTargetMatches: function () { return false; }, getActionFromEvent: function () { return ''; }, getInitialFocusEl: function () { return null; }, hasClass: function () { return false; }, isContentScrollable: function () { return false; }, notifyClosed: function () { return undefined; }, notifyClosing: function () { return undefined; }, notifyOpened: function () { return undefined; }, notifyOpening: function () { return undefined; }, releaseFocus: function () { return undefined; }, removeBodyClass: function () { return undefined; }, removeClass: function () { return undefined; }, reverseButtons: function () { return undefined; }, trapFocus: function () { return undefined; }, registerContentEventHandler: function () { return undefined; }, deregisterContentEventHandler: function () { return undefined; }, isScrollableContentAtTop: function () { return false; }, isScrollableContentAtBottom: function () { return false; }, registerWindowEventHandler: function () { return undefined; }, deregisterWindowEventHandler: function () { return undefined; }, }; }, enumerable: false, configurable: true }); MDCDialogFoundation.prototype.init = function () { if (this.adapter.hasClass(cssClasses.STACKED)) { this.setAutoStackButtons(false); } this.isFullscreen = this.adapter.hasClass(cssClasses.FULLSCREEN); }; MDCDialogFoundation.prototype.destroy = function () { if (this.animationTimer) { clearTimeout(this.animationTimer); this.handleAnimationTimerEnd(); } if (this.isFullscreen) { this.adapter.deregisterContentEventHandler('scroll', this.contentScrollHandler); } this.animFrame.cancelAll(); this.adapter.deregisterWindowEventHandler('resize', this.windowResizeHandler); this.adapter.deregisterWindowEventHandler('orientationchange', this.windowOrientationChangeHandler); }; MDCDialogFoundation.prototype.open = function (dialogOptions) { var _this = this; this.dialogOpen = true; this.adapter.notifyOpening(); this.adapter.addClass(cssClasses.OPENING); if (this.isFullscreen) { // A scroll event listener is registered even if the dialog is not // scrollable on open, since the window resize event, or orientation // change may make the dialog scrollable after it is opened. this.adapter.registerContentEventHandler('scroll', this.contentScrollHandler); } if (dialogOptions && dialogOptions.isAboveFullscreenDialog) { this.adapter.addClass(cssClasses.SCRIM_HIDDEN); } this.adapter.registerWindowEventHandler('resize', this.windowResizeHandler); this.adapter.registerWindowEventHandler('orientationchange', this.windowOrientationChangeHandler); // Wait a frame once display is no longer "none", to establish basis for // animation this.runNextAnimationFrame(function () { _this.adapter.addClass(cssClasses.OPEN); _this.adapter.addBodyClass(cssClasses.SCROLL_LOCK); _this.layout(); _this.animationTimer = setTimeout(function () { _this.handleAnimationTimerEnd(); _this.adapter.trapFocus(_this.adapter.getInitialFocusEl()); _this.adapter.notifyOpened(); }, numbers.DIALOG_ANIMATION_OPEN_TIME_MS); }); }; MDCDialogFoundation.prototype.close = function (action) { var _this = this; if (action === void 0) { action = ''; } if (!this.dialogOpen) { // Avoid redundant close calls (and events), e.g. from keydown on elements // that inherently emit click return; } this.dialogOpen = false; this.adapter.notifyClosing(action); this.adapter.addClass(cssClasses.CLOSING); this.adapter.removeClass(cssClasses.OPEN); this.adapter.removeBodyClass(cssClasses.SCROLL_LOCK); if (this.isFullscreen) { this.adapter.deregisterContentEventHandler('scroll', this.contentScrollHandler); } this.adapter.deregisterWindowEventHandler('resize', this.windowResizeHandler); this.adapter.deregisterWindowEventHandler('orientationchange', this.windowOrientationChangeHandler); cancelAnimationFrame(this.animationFrame); this.animationFrame = 0; clearTimeout(this.animationTimer); this.animationTimer = setTimeout(function () { _this.adapter.releaseFocus(); _this.handleAnimationTimerEnd(); _this.adapter.notifyClosed(action); }, numbers.DIALOG_ANIMATION_CLOSE_TIME_MS); }; /** * Used only in instances of showing a secondary dialog over a full-screen * dialog. Shows the "surface scrim" displayed over the full-screen dialog. */ MDCDialogFoundation.prototype.showSurfaceScrim = function () { var _this = this; this.adapter.addClass(cssClasses.SURFACE_SCRIM_SHOWING); this.runNextAnimationFrame(function () { _this.adapter.addClass(cssClasses.SURFACE_SCRIM_SHOWN); }); }; /** * Used only in instances of showing a secondary dialog over a full-screen * dialog. Hides the "surface scrim" displayed over the full-screen dialog. */ MDCDialogFoundation.prototype.hideSurfaceScrim = function () { this.adapter.removeClass(cssClasses.SURFACE_SCRIM_SHOWN); this.adapter.addClass(cssClasses.SURFACE_SCRIM_HIDING); }; /** * Handles `transitionend` event triggered when surface scrim animation is * finished. */ MDCDialogFoundation.prototype.handleSurfaceScrimTransitionEnd = function () { this.adapter.removeClass(cssClasses.SURFACE_SCRIM_HIDING); this.adapter.removeClass(cssClasses.SURFACE_SCRIM_SHOWING); }; MDCDialogFoundation.prototype.isOpen = function () { return this.dialogOpen; }; MDCDialogFoundation.prototype.getEscapeKeyAction = function () { return this.escapeKeyAction; }; MDCDialogFoundation.prototype.setEscapeKeyAction = function (action) { this.escapeKeyAction = action; }; MDCDialogFoundation.prototype.getScrimClickAction = function () { return this.scrimClickAction; }; MDCDialogFoundation.prototype.setScrimClickAction = function (action) { this.scrimClickAction = action; }; MDCDialogFoundation.prototype.getAutoStackButtons = function () { return this.autoStackButtons; }; MDCDialogFoundation.prototype.setAutoStackButtons = function (autoStack) { this.autoStackButtons = autoStack; }; MDCDialogFoundation.prototype.getSuppressDefaultPressSelector = function () { return this.suppressDefaultPressSelector; }; MDCDialogFoundation.prototype.setSuppressDefaultPressSelector = function (selector) { this.suppressDefaultPressSelector = selector; }; MDCDialogFoundation.prototype.layout = function () { var _this = this; this.animFrame.request(AnimationKeys.POLL_LAYOUT_CHANGE, function () { _this.layoutInternal(); }); }; /** Handles click on the dialog root element. */ MDCDialogFoundation.prototype.handleClick = function (evt) { var isScrim = this.adapter.eventTargetMatches(evt.target, strings.SCRIM_SELECTOR); // Check for scrim click first since it doesn't require querying ancestors. if (isScrim && this.scrimClickAction !== '') { this.close(this.scrimClickAction); } else { var action = this.adapter.getActionFromEvent(evt); if (action) { this.close(action); } } }; /** Handles keydown on the dialog root element. */ MDCDialogFoundation.prototype.handleKeydown = function (evt) { var isEnter = evt.key === 'Enter' || evt.keyCode === 13; if (!isEnter) { return; } var action = this.adapter.getActionFromEvent(evt); if (action) { // Action button callback is handled in `handleClick`, // since space/enter keydowns on buttons trigger click events. return; } // `composedPath` is used here, when available, to account for use cases // where a target meant to suppress the default press behaviour // may exist in a shadow root. // For example, a textarea inside a web component: // <mwc-dialog> // <horizontal-layout> // #shadow-root (open) // <mwc-textarea> // #shadow-root (open) // <textarea></textarea> // </mwc-textarea> // </horizontal-layout> // </mwc-dialog> var target = evt.composedPath ? evt.composedPath()[0] : evt.target; var isDefault = this.suppressDefaultPressSelector ? !this.adapter.eventTargetMatches(target, this.suppressDefaultPressSelector) : true; if (isEnter && isDefault) { this.adapter.clickDefaultButton(); } }; /** Handles keydown on the document. */ MDCDialogFoundation.prototype.handleDocumentKeydown = function (evt) { var isEscape = evt.key === 'Escape' || evt.keyCode === 27; if (isEscape && this.escapeKeyAction !== '') { this.close(this.escapeKeyAction); } }; /** * Handles scroll event on the dialog's content element -- showing a scroll * divider on the header or footer based on the scroll position. This handler * should only be registered on full-screen dialogs with scrollable content. */ MDCDialogFoundation.prototype.handleScrollEvent = function () { var _this = this; // Since scroll events can fire at a high rate, we throttle these events by // using requestAnimationFrame. this.animFrame.request(AnimationKeys.POLL_SCROLL_POS, function () { _this.toggleScrollDividerHeader(); _this.toggleScrollDividerFooter(); }); }; MDCDialogFoundation.prototype.layoutInternal = function () { if (this.autoStackButtons) { this.detectStackedButtons(); } this.toggleScrollableClasses(); }; MDCDialogFoundation.prototype.handleAnimationTimerEnd = function () { this.animationTimer = 0; this.adapter.removeClass(cssClasses.OPENING); this.adapter.removeClass(cssClasses.CLOSING); }; /** * Runs the given logic on the next animation frame, using setTimeout to * factor in Firefox reflow behavior. */ MDCDialogFoundation.prototype.runNextAnimationFrame = function (callback) { var _this = this; cancelAnimationFrame(this.animationFrame); this.animationFrame = requestAnimationFrame(function () { _this.animationFrame = 0; clearTimeout(_this.animationTimer); _this.animationTimer = setTimeout(callback, 0); }); }; MDCDialogFoundation.prototype.detectStackedButtons = function () { // Remove the class first to let us measure the buttons' natural positions. this.adapter.removeClass(cssClasses.STACKED); var areButtonsStacked = this.adapter.areButtonsStacked(); if (areButtonsStacked) { this.adapter.addClass(cssClasses.STACKED); } if (areButtonsStacked !== this.areButtonsStacked) { this.adapter.reverseButtons(); this.areButtonsStacked = areButtonsStacked; } }; MDCDialogFoundation.prototype.toggleScrollableClasses = function () { // Remove the class first to let us measure the natural height of the // content. this.adapter.removeClass(cssClasses.SCROLLABLE); if (this.adapter.isContentScrollable()) { this.adapter.addClass(cssClasses.SCROLLABLE); if (this.isFullscreen) { // If dialog is full-screen and scrollable, check if a scroll divider // should be shown. this.toggleScrollDividerHeader(); this.toggleScrollDividerFooter(); } } }; MDCDialogFoundation.prototype.toggleScrollDividerHeader = function () { if (!this.adapter.isScrollableContentAtTop()) { this.adapter.addClass(cssClasses.SCROLL_DIVIDER_HEADER); } else if (this.adapter.hasClass(cssClasses.SCROLL_DIVIDER_HEADER)) { this.adapter.removeClass(cssClasses.SCROLL_DIVIDER_HEADER); } }; MDCDialogFoundation.prototype.toggleScrollDividerFooter = function () { if (!this.adapter.isScrollableContentAtBottom()) { this.adapter.addClass(cssClasses.SCROLL_DIVIDER_FOOTER); } else if (this.adapter.hasClass(cssClasses.SCROLL_DIVIDER_FOOTER)) { this.adapter.removeClass(cssClasses.SCROLL_DIVIDER_FOOTER); } }; return MDCDialogFoundation; }(MDCFoundation)); export { MDCDialogFoundation }; // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier. export default MDCDialogFoundation; //# sourceMappingURL=foundation.js.map