const toasts = new Map(); const toastsContainer = t.div({ className: "toasts-container" }); /** * Removes a single notification by its reference (key or content). * * @param {string|Node} toastRef * @param {boolean} [animate] */ function removeToast(toastRef, animate = true) { const toast = toasts.get(toastRef); if (!toast || !toast.isConnected) { return; } toasts.delete(toastRef); clearTimeout(toast._removeTimeout); if (animate) { toast.classList.add("removing"); setTimeout(() => { toast.remove(); }, 300); } else { toast.remove(); } } /** * Removes all registered notifications. * * @param {boolean} [animate] */ function removeAllToasts(animate = true) { toasts.forEach((_, key) => { window.app.toasts.remove(key, animate); }); } /** * Adds "info" notification. * * @see {@link addToast} */ function infoToast(textOrElem, options = {}) { options.type = "info"; options.duration = options.duration || 3000; addToast(textOrElem, options); } /** * Adds "success" notification. * * @see {@link addToast} */ function successToast(textOrElem, options = {}) { options.type = "success"; options.duration = options.duration || 3000; addToast(textOrElem, options); } /** * Adds "error" notification. * * @see {@link addToast} */ function errorToast(textOrElem, options = {}) { options.type = "error"; options.duration = options.duration || 3500; addToast(textOrElem, options); } /** * Creates and registers a new toast notification. * * @param {string|Node} textOrElem The content of the notification as plain text or DOM node. * @param {object} [options] Toast options. * @param {number} [options.duration] Duration time in ms the notifaction would be active. * @param {string} [options.key] Optional identifier that could be used to manually remove the specific notification (default to `textOrElem`). * @param {string} [options.type] The CSS class type of the notification. */ function addToast(textOrElem, options = {}) { options = Object.assign({ duration: 3000, key: undefined, type: "info" }, options); if (!toastsContainer.isConnected) { document.body.appendChild(toastsContainer); } const toastRef = options.key || textOrElem; if (toasts.has(toastRef)) { removeToast(toastRef, false); } function initRemoveTimer(el) { if (el?._removeTimeout) { clearTimeout(el?._removeTimeout); } el._removeTimeout = setTimeout(() => { removeToast(toastRef); }, options.duration); } let newToast = t.div( { className: `toast ${options.type || ""}`, onmount: (el) => { initRemoveTimer(el); }, onunmount: (el) => { if (el?._removeTimeout) { clearTimeout(el?._removeTimeout); newToast = null; } }, onmouseover: () => { clearTimeout(newToast?._removeTimeout); }, onmouseout: () => { initRemoveTimer(newToast); }, }, t.div( { className: "toast-container" }, t.div({ className: "toast-icon" }), t.div( { className: "toast-content" }, textOrElem, t.button( { type: "button", className: "m-l-auto btn circle sm transparent secondary toast-remove", title: "Clear", onclick: () => removeToast(toastRef), }, t.i({ className: "ri-close-line", ariaHidden: true }), ), ), ), ); toasts.set(toastRef, newToast); toastsContainer.prepend(newToast); } // ------------------------------------------------------------------- window.app = window.app || {}; window.app.toasts = window.app.toasts || {}; window.app.toasts.info = infoToast; window.app.toasts.error = errorToast; window.app.toasts.success = successToast; window.app.toasts.remove = removeToast; window.app.toasts.removeAll = removeAllToasts;