UNPKG

@smui/snackbar

Version:

Svelte Material UI - Snackbar

183 lines 7.09 kB
/** * @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 { MDCFoundation } from '@smui/base/foundation'; import { cssClasses, numbers, strings } from './constants'; const { OPENING, OPEN, CLOSING } = cssClasses; const { REASON_ACTION, REASON_DISMISS, REASON_SECONDARY_ACTION } = strings; /** MDC Snackbar Foundation */ export class MDCSnackbarFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } static get strings() { return strings; } static get numbers() { return numbers; } static get defaultAdapter() { return { addClass: () => undefined, announce: () => undefined, notifyClosed: () => undefined, notifyClosing: () => undefined, notifyOpened: () => undefined, notifyOpening: () => undefined, removeClass: () => undefined, }; } constructor(adapter) { super(Object.assign(Object.assign({}, MDCSnackbarFoundation.defaultAdapter), adapter)); this.opened = false; this.animationFrame = 0; this.animationTimer = 0; this.autoDismissTimer = 0; this.autoDismissTimeoutMs = numbers.DEFAULT_AUTO_DISMISS_TIMEOUT_MS; this.closeOnEscape = true; } destroy() { this.clearAutoDismissTimer(); cancelAnimationFrame(this.animationFrame); this.animationFrame = 0; clearTimeout(this.animationTimer); this.animationTimer = 0; this.adapter.removeClass(OPENING); this.adapter.removeClass(OPEN); this.adapter.removeClass(CLOSING); } open() { this.clearAutoDismissTimer(); this.opened = true; this.adapter.notifyOpening(); this.adapter.removeClass(CLOSING); this.adapter.addClass(OPENING); this.adapter.announce(); // Wait a frame once display is no longer "none", to establish basis for // animation this.runNextAnimationFrame(() => { this.adapter.addClass(OPEN); this.animationTimer = setTimeout(() => { const timeoutMs = this.getTimeoutMs(); this.handleAnimationTimerEnd(); this.adapter.notifyOpened(); if (timeoutMs !== numbers.INDETERMINATE) { this.autoDismissTimer = setTimeout(() => { this.close(REASON_DISMISS); }, timeoutMs); } }, numbers.SNACKBAR_ANIMATION_OPEN_TIME_MS); }); } /** * @param reason Why the snackbar was closed. Value will be passed to * CLOSING_EVENT and CLOSED_EVENT via the `event.detail.reason` property. * Standard values are REASON_ACTION and REASON_DISMISS, but custom * client-specific values may also be used if desired. */ close(reason = '') { if (!this.opened) { // Avoid redundant close calls (and events), e.g. repeated interactions as // the snackbar is animating closed return; } cancelAnimationFrame(this.animationFrame); this.animationFrame = 0; this.clearAutoDismissTimer(); this.opened = false; this.adapter.notifyClosing(reason); this.adapter.addClass(cssClasses.CLOSING); this.adapter.removeClass(cssClasses.OPEN); this.adapter.removeClass(cssClasses.OPENING); clearTimeout(this.animationTimer); this.animationTimer = setTimeout(() => { this.handleAnimationTimerEnd(); this.adapter.notifyClosed(reason); }, numbers.SNACKBAR_ANIMATION_CLOSE_TIME_MS); } isOpen() { return this.opened; } getTimeoutMs() { return this.autoDismissTimeoutMs; } setTimeoutMs(timeoutMs) { // Use shorter variable names to make the code more readable const minValue = numbers.MIN_AUTO_DISMISS_TIMEOUT_MS; const maxValue = numbers.MAX_AUTO_DISMISS_TIMEOUT_MS; const indeterminateValue = numbers.INDETERMINATE; if (timeoutMs === numbers.INDETERMINATE || (timeoutMs <= maxValue && timeoutMs >= minValue)) { this.autoDismissTimeoutMs = timeoutMs; } else { throw new Error(` timeoutMs must be an integer in the range ${minValue}–${maxValue} (or ${indeterminateValue} to disable), but got '${timeoutMs}'`); } } getCloseOnEscape() { return this.closeOnEscape; } setCloseOnEscape(closeOnEscape) { this.closeOnEscape = closeOnEscape; } handleKeyDown(event) { const isEscapeKey = event.key === 'Escape' || event.keyCode === 27; if (isEscapeKey && this.getCloseOnEscape()) { this.close(REASON_DISMISS); } } handleActionButtonClick(_event) { this.close(REASON_ACTION); } handleActionIconClick(_event) { this.close(REASON_DISMISS); } handleSecondaryActionButtonClick(_event) { this.close(REASON_SECONDARY_ACTION); } clearAutoDismissTimer() { clearTimeout(this.autoDismissTimer); this.autoDismissTimer = 0; } handleAnimationTimerEnd() { 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. */ runNextAnimationFrame(callback) { cancelAnimationFrame(this.animationFrame); this.animationFrame = requestAnimationFrame(() => { this.animationFrame = 0; clearTimeout(this.animationTimer); this.animationTimer = setTimeout(callback, 0); }); } } // tslint:disable-next-line:no-default-export Needed for backward compatibility with MDC Web v0.44.0 and earlier. export default MDCSnackbarFoundation; //# sourceMappingURL=foundation.js.map