"use strict";
/*
 * Copyright 2021 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OverlayToaster = exports.OVERLAY_TOASTER_DELAY_MS = void 0;
var tslib_1 = require("tslib");
var classnames_1 = tslib_1.__importDefault(require("classnames"));
var React = tslib_1.__importStar(require("react"));
var ReactDOM = tslib_1.__importStar(require("react-dom"));
var common_1 = require("../../common");
var errors_1 = require("../../common/errors");
var props_1 = require("../../common/props");
var utils_1 = require("../../common/utils");
var overlay2_1 = require("../overlay2/overlay2");
var toast_1 = require("./toast");
var toast2_1 = require("./toast2");
exports.OVERLAY_TOASTER_DELAY_MS = 50;
/**
 * OverlayToaster component.
 *
 * @see https://blueprintjs.com/docs/#core/components/toast
 */
var OverlayToaster = /** @class */ (function (_super) {
    tslib_1.__extends(OverlayToaster, _super);
    function OverlayToaster() {
        var _this = _super !== null && _super.apply(this, arguments) || this;
        _this.state = {
            toastRefs: {},
            toasts: [],
        };
        // Queue of toasts to be displayed. If toasts are shown too quickly back to back, it can result in cut off toasts.
        // The queue ensures that toasts are only displayed in QUEUE_TIMEOUT_MS increments.
        _this.queue = {
            cancel: undefined,
            isRunning: false,
            toasts: [],
        };
        // auto-incrementing identifier for un-keyed toasts
        _this.toastId = 0;
        _this.toastRefs = {};
        /** Compute a new collection of toast refs (usually after updating toasts) */
        _this.getToastRefs = function (toasts) {
            return toasts.reduce(function (refs, toast) {
                refs[toast.key] = React.createRef();
                return refs;
            }, {});
        };
        _this.handleQueueTimeout = function () {
            var nextToast = _this.queue.toasts.shift();
            if (nextToast != null) {
                _this.immediatelyShowToast(nextToast);
                _this.startQueueTimeout();
            }
            else {
                _this.queue.isRunning = false;
            }
        };
        _this.renderToast = function (toast) {
            return React.createElement(toast2_1.Toast2, tslib_1.__assign({}, toast, { onDismiss: _this.getDismissHandler(toast) }));
        };
        _this.getDismissHandler = function (toast) { return function (timeoutExpired) {
            _this.dismiss(toast.key, timeoutExpired);
        }; };
        _this.handleClose = function (e) {
            // NOTE that `e` isn't always a KeyboardEvent but that's the only type we care about
            if (e.key === "Escape") {
                _this.clear();
            }
        };
        return _this;
    }
    /**
     * Create a new `Toaster` instance that can be shared around your application.
     * The `Toaster` will be rendered into a new element appended to the given container.
     */
    OverlayToaster.create = function (props, container) {
        if (container === void 0) { container = document.body; }
        if (props != null && props.usePortal != null && !(0, utils_1.isNodeEnv)("production")) {
            console.warn(errors_1.TOASTER_WARN_INLINE);
        }
        var containerElement = document.createElement("div");
        container.appendChild(containerElement);
        // TODO(React 18): Replace deprecated ReactDOM methods. See: https://github.com/palantir/blueprint/issues/7166
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        var toaster = ReactDOM.render(React.createElement(OverlayToaster, tslib_1.__assign({}, props, { usePortal: false })), containerElement);
        if (toaster == null) {
            throw new Error(errors_1.TOASTER_CREATE_NULL);
        }
        return toaster;
    };
    /**
     * Similar to {@link OverlayToaster.create}, but returns a Promise to a
     * Toaster instance after it's rendered and mounted to the DOM.
     *
     * This API will replace the synchronous {@link OverlayToaster.create} in a
     * future major version of Blueprint to reflect React 18+'s new asynchronous
     * rendering API.
     */
    OverlayToaster.createAsync = function (props, options) {
        var _a, _b;
        if (props != null && props.usePortal != null && !(0, utils_1.isNodeEnv)("production")) {
            console.warn(errors_1.TOASTER_WARN_INLINE);
        }
        var container = (_a = options === null || options === void 0 ? void 0 : options.container) !== null && _a !== void 0 ? _a : document.body;
        // TODO(React 18): Replace deprecated ReactDOM methods. See: https://github.com/palantir/blueprint/issues/7166
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        var domRenderer = (_b = options === null || options === void 0 ? void 0 : options.domRenderer) !== null && _b !== void 0 ? _b : ReactDOM.render;
        var toasterComponentRoot = document.createElement("div");
        container.appendChild(toasterComponentRoot);
        return new Promise(function (resolve, reject) {
            try {
                // TODO(React 18): Replace deprecated ReactDOM methods. See: https://github.com/palantir/blueprint/issues/7166
                // eslint-disable-next-line @typescript-eslint/no-deprecated
                domRenderer(React.createElement(OverlayToaster, tslib_1.__assign({}, props, { ref: handleRef, usePortal: false })), toasterComponentRoot);
            }
            catch (error) {
                // Note that we're catching errors from the domRenderer function
                // call, but not errors when rendering <OverlayToaster>, which
                // happens in a separate scheduled tick. Wrapping the
                // OverlayToaster in an error boundary would be necessary to
                // capture rendering errors, but that's still a bit unreliable
                // and would only catch errors rendering the initial mount.
                reject(error);
            }
            // We can get a rough guarantee that the OverlayToaster has been
            // mounted to the DOM by waiting until the ref callback here has
            // been fired.
            //
            // This is the approach suggested under "What about the render
            // callback?" at https://github.com/reactwg/react-18/discussions/5.
            function handleRef(ref) {
                if (ref == null) {
                    reject(new Error(errors_1.TOASTER_CREATE_ASYNC_NULL));
                    return;
                }
                resolve(ref);
            }
        });
    };
    OverlayToaster.prototype.show = function (props, key) {
        var options = this.createToastOptions(props, key);
        var wasExistingToastUpdated = this.maybeUpdateExistingToast(options, key);
        if (wasExistingToastUpdated) {
            return options.key;
        }
        if (this.queue.isRunning) {
            // If a toast has been shown recently, push to the queued toasts to prevent toasts from being shown too
            // quickly for the animations to keep up
            this.queue.toasts.push(options);
        }
        else {
            // If we have not recently shown a toast, we can immediately show the given toast
            this.immediatelyShowToast(options);
            this.startQueueTimeout();
        }
        return options.key;
    };
    OverlayToaster.prototype.maybeUpdateExistingToast = function (options, key) {
        if (key == null) {
            return false;
        }
        var isExistingQueuedToast = this.queue.toasts.some(function (toast) { return toast.key === key; });
        if (isExistingQueuedToast) {
            this.queue.toasts = this.queue.toasts.map(function (t) { return (t.key === key ? options : t); });
            return true;
        }
        var isExistingShownToast = this.state.toasts.some(function (toast) { return toast.key === key; });
        if (isExistingShownToast) {
            this.updateToastsInState(function (toasts) { return toasts.map(function (t) { return (t.key === key ? options : t); }); });
            return true;
        }
        return false;
    };
    OverlayToaster.prototype.immediatelyShowToast = function (options) {
        if (this.props.maxToasts) {
            // check if active number of toasts are at the maxToasts limit
            this.dismissIfAtLimit();
        }
        this.updateToastsInState(function (toasts) { return tslib_1.__spreadArray([options], toasts, true); });
    };
    OverlayToaster.prototype.startQueueTimeout = function () {
        this.queue.isRunning = true;
        this.queue.cancel = this.setTimeout(this.handleQueueTimeout, exports.OVERLAY_TOASTER_DELAY_MS);
    };
    OverlayToaster.prototype.updateToastsInState = function (getNewToasts) {
        var _this = this;
        this.setState(function (prevState) {
            var toasts = getNewToasts(prevState.toasts);
            return { toastRefs: _this.getToastRefs(toasts), toasts: toasts };
        });
    };
    OverlayToaster.prototype.dismiss = function (key, timeoutExpired) {
        var _this = this;
        if (timeoutExpired === void 0) { timeoutExpired = false; }
        this.setState(function (prevState) {
            var toasts = prevState.toasts.filter(function (t) {
                var _a;
                var matchesKey = t.key === key;
                if (matchesKey) {
                    (_a = t.onDismiss) === null || _a === void 0 ? void 0 : _a.call(t, timeoutExpired);
                }
                return !matchesKey;
            });
            return { toastRefs: _this.getToastRefs(toasts), toasts: toasts };
        });
    };
    OverlayToaster.prototype.clear = function () {
        var _a, _b;
        (_b = (_a = this.queue).cancel) === null || _b === void 0 ? void 0 : _b.call(_a);
        this.queue = { cancel: undefined, isRunning: false, toasts: [] };
        this.state.toasts.forEach(function (t) { var _a; return (_a = t.onDismiss) === null || _a === void 0 ? void 0 : _a.call(t, false); });
        this.setState({ toastRefs: {}, toasts: [] });
    };
    OverlayToaster.prototype.getToasts = function () {
        return this.state.toasts;
    };
    OverlayToaster.prototype.render = function () {
        var classes = (0, classnames_1.default)(common_1.Classes.TOAST_CONTAINER, this.getPositionClasses(), this.props.className);
        return (React.createElement(overlay2_1.Overlay2, { autoFocus: this.props.autoFocus, canEscapeKeyClose: this.props.canEscapeKeyClear, canOutsideClickClose: false, className: classes, childRefs: this.toastRefs, enforceFocus: false, hasBackdrop: false, isOpen: this.state.toasts.length > 0 || this.props.children != null, onClose: this.handleClose, shouldReturnFocusOnClose: false, 
            // $pt-transition-duration * 3 + $pt-transition-duration / 2
            transitionDuration: 350, transitionName: common_1.Classes.TOAST, usePortal: this.props.usePortal },
            this.state.toasts.map(this.renderToast, this),
            this.renderChildren()));
    };
    OverlayToaster.prototype.validateProps = function (_a) {
        var maxToasts = _a.maxToasts;
        // maximum number of toasts should not be a number less than 1
        if (maxToasts !== undefined && maxToasts < 1) {
            throw new Error(errors_1.TOASTER_MAX_TOASTS_INVALID);
        }
    };
    /**
     * If provided `Toast` children, automaticaly upgrade them to `Toast2` elements so that `Overlay2` can inject
     * refs into them for use by `CSSTransition`. This is a bit hacky but ensures backwards compatibility for
     * `OverlayToaster`. It should be an uncommon code path in most applications, since we expect most usage to
     * occur via the imperative toaster APIs.
     *
     * We can remove this indirection once `Toast2` fully replaces `Toast` in a future major version.
     *
     * TODO(@adidahiya): Blueprint v6.0
     */
    OverlayToaster.prototype.renderChildren = function () {
        return React.Children.map(this.props.children, function (child) {
            // TODO(React 18): Replace deprecated ReactDOM methods. See: https://github.com/palantir/blueprint/issues/7166
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            if ((0, utils_1.isElementOfType)(child, toast_1.Toast)) {
                return React.createElement(toast2_1.Toast2, tslib_1.__assign({}, child.props));
            }
            else {
                return child;
            }
        });
    };
    OverlayToaster.prototype.dismissIfAtLimit = function () {
        if (this.state.toasts.length === this.props.maxToasts) {
            // dismiss the oldest toast to stay within the maxToasts limit
            this.dismiss(this.state.toasts[this.state.toasts.length - 1].key);
        }
    };
    OverlayToaster.prototype.createToastOptions = function (props, key) {
        if (key === void 0) { key = "toast-".concat(this.toastId++); }
        // clone the object before adding the key prop to avoid leaking the mutation
        return tslib_1.__assign(tslib_1.__assign({}, props), { key: key });
    };
    OverlayToaster.prototype.getPositionClasses = function () {
        var positions = this.props.position.split("-");
        // NOTE that there is no -center class because that's the default style
        return tslib_1.__spreadArray(tslib_1.__spreadArray([], positions.map(function (p) { return "".concat(common_1.Classes.TOAST_CONTAINER, "-").concat(p.toLowerCase()); }), true), [
            "".concat(common_1.Classes.TOAST_CONTAINER, "-").concat(this.props.usePortal ? "in-portal" : "inline"),
        ], false);
    };
    OverlayToaster.displayName = "".concat(props_1.DISPLAYNAME_PREFIX, ".OverlayToaster");
    OverlayToaster.defaultProps = {
        autoFocus: false,
        canEscapeKeyClear: true,
        position: common_1.Position.TOP,
        usePortal: true,
    };
    return OverlayToaster;
}(common_1.AbstractPureComponent));
exports.OverlayToaster = OverlayToaster;
//# sourceMappingURL=overlayToaster.js.map