From 1ed2e5e2c0c126c46168334be3ad2f0114fb441c Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 11 Jun 2024 16:46:04 +1000 Subject: [PATCH 1/9] Replaced uglifer with a proper minifying setup --- package.json | 12 +++++++++--- webpack.config.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 webpack.config.js diff --git a/package.json b/package.json index 5c69c24..c8daa8f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dist" ], "scripts": { - "prepare": "uglifyjs src/sonner.js -o dist/sonner.min.js && uglifycss src/sonner.css --output dist/sonner.min.css" + "build": "webpack" }, "repository": { "type": "git", @@ -28,7 +28,13 @@ }, "homepage": "https://github.com/fernandojpps/sonner-js#readme", "devDependencies": { - "uglify-js": "^3.6.0", - "uglifycss": "^0.0.29" + "css-loader": "^7.1.2", + "css-minimizer-webpack-plugin": "^7.0.0", + "mini-css-extract-plugin": "^2.9.0", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "style-loader": "^4.0.0" } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..10554cd --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,32 @@ +const path = require('path'); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); + +module.exports = { + plugins: [new MiniCssExtractPlugin({ + filename: "sonner.min.css", + })], + entry: [ + './src/sonner.js', + './src/sonner.css', + ], + output: { + filename: 'sonner.min.js', + path: path.resolve(__dirname, 'dist'), + }, + module: { + rules: [ + { + test: /\.css$/i, + use: [MiniCssExtractPlugin.loader, "css-loader"], + }, + ], + }, + optimization: { + minimizer: [ + // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line + `...`, + new CssMinimizerPlugin(), + ], + }, +}; \ No newline at end of file From 27d578ba021b432cf9381b6ccef00ed02ba24eb6 Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 11 Jun 2024 16:47:35 +1000 Subject: [PATCH 2/9] ground work for better Toast() support --- src/sonner.js | 69 ++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/src/sonner.js b/src/sonner.js index 4d708f5..5d158be 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -50,32 +50,32 @@ window.Sonner = { * @param {string} msg - The message to display in the toast. * @returns {void} */ - success(msg) { - Sonner.show(msg, { type: "success" }); + success(msg, opts = {}) { + return Sonner.show(msg, { icon: 'success', type: "success", ...opts }); }, /** * Shows a new error toast with a specific message. * @param {string} msg - The message to display in the toast. * @returns {void} */ - error(msg) { - Sonner.show(msg, { type: "error" }); + error(msg, opts = {}) { + return Sonner.show(msg, { icon: 'error', type: "error", ...opts }); }, /** * Shows a new info toast with a specific message. * @param {string} msg - The message to display in the toast. * @returns {void} */ - info(msg) { - Sonner.show(msg, { type: "info" }); + info(msg, opts = {}) { + return Sonner.show(msg, { icon: 'info', type: "info", ...opts }); }, /** * Shows a new warning toast with a specific message. * @param {string} msg - The message to display in the toast. * @returns {void} */ - warning(msg) { - Sonner.show(msg, { type: "warning" }); + warning(msg, opts = {}) { + return Sonner.show(msg, { icon: 'warning', type: "warning", ...opts }); }, /** * Shows a new toast with a specific message, description, and type. @@ -85,9 +85,9 @@ window.Sonner = { * @param {string} options.description - The description to display in the toast. * @returns {void} */ - show(msg, { description, type } = {}) { + show(msg, opts = {}) { const list = document.getElementById("sonner-toaster-list"); - const { toast, id } = renderToast(list, msg, { description, type }); + const { toast, id } = renderToast(list, msg, opts); // Wait for the toast to be mounted before registering swipe events window.setTimeout(function () { @@ -101,8 +101,9 @@ window.Sonner = { registerSwipe(id); refreshProperties(); - registerRemoveTimeout(el); + registerRemoveTimeout(el, opts.duration ?? TOAST_LIFETIME); }, 16); + return toast; }, /** * Removes an element with a specific id from the DOM after a delay. @@ -132,7 +133,7 @@ window.Sonner = { // Assets //////////////////////// -const getAsset = (type) => { +const getIcon = (type) => { switch (type) { case "success": return SuccessIcon; @@ -147,7 +148,7 @@ const getAsset = (type) => { return ErrorIcon; default: - return null; + return undefined; } }; @@ -239,19 +240,19 @@ function genid() { * @returns {Element} toast - The toast element. * @returns {string} id - The unique id of the toast. */ -function renderToast(list, msg, { type, description }) { +function renderToast(list, msg, opts = {}) { const toast = document.createElement("div"); list.prepend(toast); const id = genid(); const count = list.children.length; - const asset = getAsset(type); + const asset = getIcon(opts.icon) ?? opts.icon; toast.outerHTML = `
  • - ${ - list.getAttribute("data-close-button") === "true" - ? ` ` - : "" - } - ${ - asset - ? ` -
    - ${getAsset(type)} -
    -` - : "" - } + : "" + } + ${asset + ? `
    ${asset}
    ` + : "" + }
    ${msg}
    - ${ - description - ? `
    ${description}
    ` - : "" - } + ${opts.description + ? `
    ${opts.description}
    ` + : "" + }
  • `; @@ -325,12 +319,13 @@ function renderToast(list, msg, { type, description }) { * The function sets a new timeout to remove the element from its parent after a delay. * The timeout ensures that all CSS transitions complete before the element is removed. * @param {Element} el - The element to register the remove timeout for. + * @param {number} lifetime - How long the toast will last for * @returns {void} */ -function registerRemoveTimeout(el) { +function registerRemoveTimeout(el, lifetime = TOAST_LIFETIME) { const tid = window.setTimeout(function () { Sonner.remove(el.getAttribute("data-id")); - }, TOAST_LIFETIME); + }, lifetime); el.setAttribute("data-remove-tid", tid); } From ba67d575366392a183192bb53d9993b52aeb70e2 Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 11 Jun 2024 17:25:17 +1000 Subject: [PATCH 3/9] added promise support --- example/index.html | 18 +++++++++++ src/sonner.js | 81 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/example/index.html b/example/index.html index cde335d..189940c 100644 --- a/example/index.html +++ b/example/index.html @@ -12,6 +12,20 @@ position: "bottom-center", }); }); + + function loading() { + const promise = new Promise((resolve) => { + setTimeout(() => { + resolve(Date.now()) + }, 1_000); + }); + + Sonner.promise(promise, { + loading: 'Waiting for 1s...', + success: (data) => `The current time is now: ${data}`, + error: 'Oops! Failed :c', + }); + } @@ -29,5 +43,9 @@ + + diff --git a/src/sonner.js b/src/sonner.js index 5d158be..f4f74a0 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -77,6 +77,39 @@ window.Sonner = { warning(msg, opts = {}) { return Sonner.show(msg, { icon: 'warning', type: "warning", ...opts }); }, + /** + * Shows a promise loading toast + * @template T promise data type + * @param {Promise} promise + * @param {Object} opts options + * @param {string} opts.loading message to display while loading + * @param {string|(data : T) => string} opts.success function callback / message to show when loaded + * @param {string|(data : Error) => string} opts.success function callback / message to show when errored + */ + promise(promise, opts = {}) { + const toast = Sonner.show(opts.loading ?? 'Loading...', { + icon: 'loading', + type: 'loading', + ...opts, + duration: -1, + }); + + promise + .then(result => { + // Update the message and start the timeout + const msg = typeof opts.success === 'string' ? opts.success : opts.success(result); + toast.setTitle(msg).setIcon('success').setDuration(opts.duration ?? TOAST_LIFETIME); + return result; + }) + .catch(err => { + const msg = typeof opts.error === 'string' ? opts.error : opts.error(err); + toast.setTitle(msg).setIcon('error').setDuration(opts.duration ?? TOAST_LIFETIME); + throw err; + }); + + return toast; + }, + /** * Shows a new toast with a specific message, description, and type. * @param {string} msg - The message to display in the toast. @@ -147,6 +180,9 @@ const getIcon = (type) => { case "error": return ErrorIcon; + case 'loading': + return Loader; + default: return undefined; } @@ -154,6 +190,15 @@ const getIcon = (type) => { const bars = Array(12).fill(0); +const Loader = ` +
    +
    + ${bars.map((_, i) => `
    `).join('\n')} +
    +
    +`; + + const SuccessIcon = ` ${asset}` - : "" + : `
    ` }
    `; - return { toast, id }; + + return { + id, + toast: { + target: document.querySelector(`[data-id="${id}"]`), + setTitle: function (msg) { + document.querySelector(`[data-sonner-toast][data-id=${id}] [data-title]`).textContent = msg; + return this; + }, + setIcon: function (icon) { + const ico = getIcon(icon) ?? ''; + document.querySelector(`[data-sonner-toast][data-id=${id}] [data-icon]`).innerHTML = ico; + return this; + }, + setDuration: function(duration) { + registerRemoveTimeout(this.target, duration); + return this; + } + } +}; } /** @@ -323,7 +389,16 @@ function renderToast(list, msg, opts = {}) { * @returns {void} */ function registerRemoveTimeout(el, lifetime = TOAST_LIFETIME) { - const tid = window.setTimeout(function () { + if (lifetime < 0) return; + if (!el.getAttribute("data-id")) + throw new Error('invalid target for removal'); + + // Clear previous duration + if (el.getAttribute("data-remove-tid")) + window.clearTimeout(el.getAttribute("data-remove-tid")); + + // Set new timeout + const tid = window.setTimeout(() => { Sonner.remove(el.getAttribute("data-id")); }, lifetime); el.setAttribute("data-remove-tid", tid); From ac6dcb5b6152a634c13ba52c0a8422e5e43f4989 Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 11 Jun 2024 17:29:05 +1000 Subject: [PATCH 4/9] added dismiss api --- example/index.html | 14 +++++++++----- src/sonner.js | 12 +++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/example/index.html b/example/index.html index 189940c..d6cdfaf 100644 --- a/example/index.html +++ b/example/index.html @@ -13,15 +13,16 @@ }); }); - function loading() { - const promise = new Promise((resolve) => { + function loading(fail) { + const promise = new Promise((resolve, reject) => { setTimeout(() => { + if (fail) reject(new Error('OH NO!')); resolve(Date.now()) - }, 1_000); + }, 2_500); }); Sonner.promise(promise, { - loading: 'Waiting for 1s...', + loading: 'Waiting for 2.5s...', success: (data) => `The current time is now: ${data}`, error: 'Oops! Failed :c', }); @@ -44,8 +45,11 @@ Show Info Toast - + diff --git a/src/sonner.js b/src/sonner.js index f4f74a0..f77f0fa 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -363,8 +363,10 @@ function renderToast(list, msg, opts = {}) { id, toast: { target: document.querySelector(`[data-id="${id}"]`), - setTitle: function (msg) { - document.querySelector(`[data-sonner-toast][data-id=${id}] [data-title]`).textContent = msg; + setTitle: function (msg, raw = false) { + const title = document.querySelector(`[data-sonner-toast][data-id=${id}] [data-title]`); + if (raw) title.innerHTML = msg; + else title.textContent = msg; return this; }, setIcon: function (icon) { @@ -375,7 +377,11 @@ function renderToast(list, msg, opts = {}) { setDuration: function(duration) { registerRemoveTimeout(this.target, duration); return this; - } + }, + dismiss: function() { + Sonner.remove(this.target.getAttribute("data-id")) + return this; + } } }; } From c38a692c29cfca3e409f925e7a45f33df8eca615 Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 11 Jun 2024 17:32:54 +1000 Subject: [PATCH 5/9] fixed weird scaling --- src/sonner.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sonner.css b/src/sonner.css index 5492038..56e9dd5 100644 --- a/src/sonner.css +++ b/src/sonner.css @@ -308,9 +308,9 @@ html[dir="rtl"], } [data-sonner-toast][data-expanded="false"][data-front="false"] { - --scale: var(--toasts-before) * 0.05 + 1; + --scale: var(--toasts-before) * 0.05; --y: translateY(calc(var(--lift-amount) * var(--toasts-before))) - scale(calc(-1 * var(--scale))); + scale(calc(1 - var(--scale))); height: var(--front-toast-height); } From 463d47b69aee541a8a1488a51c033e018e8369d3 Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 11 Jun 2024 17:58:37 +1000 Subject: [PATCH 6/9] tweaked some settings that caused weirdness --- example/index.html | 113 +++++++++++++++++++++++++++------------------ src/sonner.js | 21 +++++---- 2 files changed, 81 insertions(+), 53 deletions(-) diff --git a/example/index.html b/example/index.html index d6cdfaf..fa06b74 100644 --- a/example/index.html +++ b/example/index.html @@ -1,55 +1,78 @@ - - sonner-js example - - - + - - - - - - - - - - - - - + } + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/sonner.js b/src/sonner.js index f77f0fa..f2b57e7 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -5,7 +5,7 @@ //////////////////////// // Constants //////////////////////// -const VISIBLE_TOASTS_AMOUNT = 3; +const VISIBLE_TOASTS_AMOUNT = 4; const VIEWPORT_OFFSET = "32px"; const TOAST_LIFETIME = 4000; const TOAST_WIDTH = 356; @@ -107,7 +107,7 @@ window.Sonner = { throw err; }); - return toast; + return promise; }, /** @@ -123,7 +123,7 @@ window.Sonner = { const { toast, id } = renderToast(list, msg, opts); // Wait for the toast to be mounted before registering swipe events - window.setTimeout(function () { + //window.setTimeout(function () { const el = list.children[0]; const height = el.getBoundingClientRect().height; @@ -134,8 +134,8 @@ window.Sonner = { registerSwipe(id); refreshProperties(); - registerRemoveTimeout(el, opts.duration ?? TOAST_LIFETIME); - }, 16); + toast.setDuration(opts.duration ?? TOAST_LIFETIME); + //}, 16); return toast; }, /** @@ -149,6 +149,7 @@ window.Sonner = { remove(id) { const el = document.querySelector(`[data-id="${id}"]`); if (!el) return; + el.setAttribute("data-removed", "true"); refreshProperties(); @@ -375,7 +376,8 @@ function renderToast(list, msg, opts = {}) { return this; }, setDuration: function(duration) { - registerRemoveTimeout(this.target, duration); + this.target.setAttribute('data-duration', duration); + registerRemoveTimeout(this.target); return this; }, dismiss: function() { @@ -394,11 +396,14 @@ function renderToast(list, msg, opts = {}) { * @param {number} lifetime - How long the toast will last for * @returns {void} */ -function registerRemoveTimeout(el, lifetime = TOAST_LIFETIME) { - if (lifetime < 0) return; +function registerRemoveTimeout(el) { if (!el.getAttribute("data-id")) throw new Error('invalid target for removal'); + const lifetime = el.getAttribute('data-duration') ?? TOAST_LIFETIME; + if (lifetime < 0) + return; + // Clear previous duration if (el.getAttribute("data-remove-tid")) window.clearTimeout(el.getAttribute("data-remove-tid")); From 0741a97aa6c22d4395fe74aebbc5ae1141d1425d Mon Sep 17 00:00:00 2001 From: Lachee Date: Mon, 14 Jul 2025 16:02:51 +1000 Subject: [PATCH 7/9] Added setType and setDuration functions --- .gitignore | 3 +++ src/sonner.js | 25 +++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5ae1430..6ac6022 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ .DS_Store node_modules/ dist/ + +# Packages package-lock.json +pnpm-lock.yaml \ No newline at end of file diff --git a/src/sonner.js b/src/sonner.js index f2b57e7..4057d5b 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -98,12 +98,12 @@ window.Sonner = { .then(result => { // Update the message and start the timeout const msg = typeof opts.success === 'string' ? opts.success : opts.success(result); - toast.setTitle(msg).setIcon('success').setDuration(opts.duration ?? TOAST_LIFETIME); + toast.setTitle(msg).setIcon('success').setType('success').setDuration(opts.duration ?? TOAST_LIFETIME); return result; }) .catch(err => { const msg = typeof opts.error === 'string' ? opts.error : opts.error(err); - toast.setTitle(msg).setIcon('error').setDuration(opts.duration ?? TOAST_LIFETIME); + toast.setTitle(msg).setIcon('error').setType('error').setDuration(opts.duration ?? TOAST_LIFETIME); throw err; }); @@ -370,6 +370,23 @@ function renderToast(list, msg, opts = {}) { else title.textContent = msg; return this; }, + setDescription: function(description, raw = false) { + let desc = document.querySelector(`[data-sonner-toast][data-id=${id}] [data-description]`); + if (description == null) { + desc?.remove(); + return this; + } + + if (!desc) { + desc = document.createElement('div'); + desc.setAttribute('data-description', ''); + document.querySelector(`[data-sonner-toast][data-id=${id}] [data-content]`).appendChild(desc); + } + + if (raw) desc.innerHTML = msg; + else desc.textContent = msg; + return this; + }, setIcon: function (icon) { const ico = getIcon(icon) ?? ''; document.querySelector(`[data-sonner-toast][data-id=${id}] [data-icon]`).innerHTML = ico; @@ -380,6 +397,10 @@ function renderToast(list, msg, opts = {}) { registerRemoveTimeout(this.target); return this; }, + setType: function (type) { + this.target.setAttribute('data-type', type); + return this; + }, dismiss: function() { Sonner.remove(this.target.getAttribute("data-id")) return this; From 834b415ff1b018a1ab0a647cb8f94e1084b51f9f Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 15 Jul 2025 10:37:13 +1000 Subject: [PATCH 8/9] Fixed description not working and added function callbacks to Promise --- src/sonner.js | 68 ++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/sonner.js b/src/sonner.js index 4057d5b..a3c390b 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -95,16 +95,18 @@ window.Sonner = { }); promise - .then(result => { - // Update the message and start the timeout - const msg = typeof opts.success === 'string' ? opts.success : opts.success(result); - toast.setTitle(msg).setIcon('success').setType('success').setDuration(opts.duration ?? TOAST_LIFETIME); - return result; + .then(value => { + // Update the message and start the timeout. We set everything first so the bind has a opportunity to change it if needed. + toast.setIcon('success').setType('success').setDuration(opts.duration ?? TOAST_LIFETIME); + const msg = typeof opts.success === 'string' ? opts.success : opts.success.bind(toast)(value); + if (msg !== undefined) toast.setTitle(msg); + return value; }) - .catch(err => { - const msg = typeof opts.error === 'string' ? opts.error : opts.error(err); - toast.setTitle(msg).setIcon('error').setType('error').setDuration(opts.duration ?? TOAST_LIFETIME); - throw err; + .catch(reason => { + toast.setIcon('error').setType('error').setDuration(opts.duration ?? TOAST_LIFETIME); + const msg = typeof opts.error === 'string' ? opts.error : opts.error.bind(toast)(reason); + if (msg !== undefined) toast.setTitle(msg); + throw reason; }); return promise; @@ -124,17 +126,17 @@ window.Sonner = { // Wait for the toast to be mounted before registering swipe events //window.setTimeout(function () { - const el = list.children[0]; - const height = el.getBoundingClientRect().height; + const el = list.children[0]; + const height = el.getBoundingClientRect().height; - el.setAttribute("data-mounted", "true"); - el.setAttribute("data-initial-height", height); - el.style.setProperty("--initial-height", `${height}px`); - list.style.setProperty("--front-toast-height", `${height}px`); + el.setAttribute("data-mounted", "true"); + el.setAttribute("data-initial-height", height); + el.style.setProperty("--initial-height", `${height}px`); + list.style.setProperty("--front-toast-height", `${height}px`); - registerSwipe(id); - refreshProperties(); - toast.setDuration(opts.duration ?? TOAST_LIFETIME); + registerSwipe(id); + refreshProperties(); + toast.setDuration(opts.duration ?? TOAST_LIFETIME); //}, 16); return toast; }, @@ -292,7 +294,7 @@ function renderToast(list, msg, opts = {}) { const id = genid(); const count = list.children.length; const asset = getIcon(opts.icon) ?? opts.icon; - + toast.outerHTML = `
  • `; - - return { - id, + + return { + id, toast: { target: document.querySelector(`[data-id="${id}"]`), setTitle: function (msg, raw = false) { const title = document.querySelector(`[data-sonner-toast][data-id=${id}] [data-title]`); - if (raw) title.innerHTML = msg; - else title.textContent = msg; + if (raw) title.innerHTML = msg; + else title.textContent = msg; return this; }, - setDescription: function(description, raw = false) { + setDescription: function (description, raw = false) { let desc = document.querySelector(`[data-sonner-toast][data-id=${id}] [data-description]`); if (description == null) { desc?.remove(); @@ -383,16 +385,16 @@ function renderToast(list, msg, opts = {}) { document.querySelector(`[data-sonner-toast][data-id=${id}] [data-content]`).appendChild(desc); } - if (raw) desc.innerHTML = msg; - else desc.textContent = msg; + if (raw) desc.innerHTML = description; + else desc.textContent = description; return this; }, setIcon: function (icon) { - const ico = getIcon(icon) ?? ''; + const ico = getIcon(icon) ?? ''; document.querySelector(`[data-sonner-toast][data-id=${id}] [data-icon]`).innerHTML = ico; return this; }, - setDuration: function(duration) { + setDuration: function (duration) { this.target.setAttribute('data-duration', duration); registerRemoveTimeout(this.target); return this; @@ -401,12 +403,12 @@ function renderToast(list, msg, opts = {}) { this.target.setAttribute('data-type', type); return this; }, - dismiss: function() { + dismiss: function () { Sonner.remove(this.target.getAttribute("data-id")) return this; } } -}; + }; } /** @@ -418,11 +420,11 @@ function renderToast(list, msg, opts = {}) { * @returns {void} */ function registerRemoveTimeout(el) { - if (!el.getAttribute("data-id")) + if (!el.getAttribute("data-id")) throw new Error('invalid target for removal'); const lifetime = el.getAttribute('data-duration') ?? TOAST_LIFETIME; - if (lifetime < 0) + if (lifetime < 0) return; // Clear previous duration From 7ff9386b3a29dc1053ab2365de720c72cf78ff26 Mon Sep 17 00:00:00 2001 From: Lachee Date: Tue, 15 Jul 2025 10:44:30 +1000 Subject: [PATCH 9/9] Added a loading function --- src/sonner.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/sonner.js b/src/sonner.js index a3c390b..95e3c7c 100644 --- a/src/sonner.js +++ b/src/sonner.js @@ -77,6 +77,15 @@ window.Sonner = { warning(msg, opts = {}) { return Sonner.show(msg, { icon: 'warning', type: "warning", ...opts }); }, + /** + * Shows a new loading toast with a specific message. + * The toast by default will not disapear until dismissed. + * @param {string} msg - The message to display in the toast. + * @returns {object} + */ + loading(msg, opts = {}) { + return Sonner.show(msg, { icon: 'loading', type: 'loading', duration: -1, ...opts }); + }, /** * Shows a promise loading toast * @template T promise data type @@ -87,13 +96,7 @@ window.Sonner = { * @param {string|(data : Error) => string} opts.success function callback / message to show when errored */ promise(promise, opts = {}) { - const toast = Sonner.show(opts.loading ?? 'Loading...', { - icon: 'loading', - type: 'loading', - ...opts, - duration: -1, - }); - + const toast = Sonner.loading(opts.loading ?? 'Loading...', { ...opts, duration: -1 }); promise .then(value => { // Update the message and start the timeout. We set everything first so the bind has a opportunity to change it if needed. @@ -118,7 +121,7 @@ window.Sonner = { * @param {Object} options - An object with the following properties: * @param {string} options.type - The type of the toast. The type can be one of the following values: "success", "error", "info", "warning", or "neutral". * @param {string} options.description - The description to display in the toast. - * @returns {void} + * @returns {object} */ show(msg, opts = {}) { const list = document.getElementById("sonner-toaster-list");