1/** 2 * toastr.js v3.0.0 â Vanilla JS drop-in replacement for toastr 2.1.0 3 * 4 * Produces the identical DOM structure as jQuery-based toastr so existing 5 * SCSS (in _popover.scss) and JS consumers (channel-control.js, calendar.js, 6 * etc.) continue to work without modification. 7 * 8 * Zero dependencies. No jQuery required. 9 */ 10(function () { 11 'use strict'; 12 13 const DEFAULTS = { 14 closeButton: false, 15 closeHtml: '<button class="toast-close-button" role="button"><i class="fa-sharp fa-solid fa-xmark"></i></button>', 16 debug: false, 17 newestOnTop: true, 18 progressBar: false, 19 positionClass: 'toast-top-right', 20 preventDuplicates: false, 21 tapToDismiss: true, 22 timeOut: 5000, 23 extendedTimeOut: 1000, 24 showDuration: 300, 25 hideDuration: 1000, 26 onclick: null, 27 target: 'body', 28 containerId: 'toast-container', 29 iconClasses: { 30 error: 'toast-error', 31 info: 'toast-info', 32 success: 'toast-success', 33 warning: 'toast-warning' 34 } 35 }; 36 37 let container = null; 38 let toastCounter = 0; 39 let previousMsg = ''; 40 41 // ââ Helpers ââââââââââââââââââââââââââââââââââââââââââââââ 42 43 function merge() { 44 const result = {}; 45 for (let i = 0; i < arguments.length; i++) { 46 const obj = arguments[i]; 47 if (obj) { 48 for (const key in obj) { 49 if (Object.prototype.hasOwnProperty.call(obj, key)) { 50 result[key] = obj[key]; 51 } 52 } 53 } 54 } 55 return result; 56 } 57 58 function num(v) { 59 return parseInt(v, 10) || 0; 60 } 61 62 function el(tag, className) { 63 const node = document.createElement(tag); 64 if (className) node.className = className; 65 return node; 66 } 67 68 // ââ Container âââââââââââââââââââââââââââââââââââââââââââ 69 70 function getContainer(opts, create) { 71 const id = opts.containerId; 72 container = document.getElementById(id); 73 74 if (container) { 75 // Reposition if the position class changed 76 if (!container.classList.contains(opts.positionClass)) { 77 container.className = opts.positionClass; 78 } 79 return container; 80 } 81 82 if (!create) return null; 83 84 container = el('div'); 85 container.id = id; 86 container.className = opts.positionClass; 87 container.setAttribute('aria-live', 'polite'); 88 container.setAttribute('role', 'alert'); 89 90 const target = document.querySelector(opts.target) || document.body; 91 target.appendChild(container); 92 93 return container; 94 } 95 96 // ââ Core show âââââââââââââââââââââââââââââââââââââââââââ 97 98 function show(type, message, title, optionsOverride) { 99 const opts = merge(DEFAULTS, toastr.options, optionsOverride); 100 const iconClass = opts.iconClasses[type] || opts.iconClasses.info; 101 102 // preventDuplicates â respect per-call override 103 const checkDuplicates = (optionsOverride && typeof optionsOverride.preventDuplicates !== 'undefined') 104 ? optionsOverride.preventDuplicates 105 : opts.preventDuplicates; 106 107 if (checkDuplicates && message === previousMsg) { 108 return null; 109 } 110 previousMsg = message; 111 112 toastCounter++; 113 const box = getContainer(opts, true); 114 115 // Build toast element 116 const toastEl = el('div', 'toast ' + iconClass); 117 toastEl.setAttribute('aria-live', 'polite'); 118 toastEl.setAttribute('role', 'alert'); 119 120 // Close button 121 if (opts.closeButton) { 122 const tmp = el('div'); 123 tmp.innerHTML = opts.closeHtml; 124 const closeBtn = tmp.firstChild; 125 toastEl.appendChild(closeBtn); 126 } 127 128 // Progress bar 129 let progressEl = null; 130 if (opts.progressBar) { 131 progressEl = el('div', 'toast-progress'); 132 toastEl.appendChild(progressEl); 133 } 134 135 // Title 136 if (title) { 137 const titleEl = el('div', 'toast-title'); 138 titleEl.innerHTML = title; 139 toastEl.appendChild(titleEl); 140 } 141 142 // Message 143 if (message) { 144 const msgEl = el('div', 'toast-message'); 145 msgEl.innerHTML = message; 146 toastEl.appendChild(msgEl); 147 } 148 149 // Insert into container 150 if (opts.newestOnTop) { 151 box.insertBefore(toastEl, box.firstChild); 152 } else { 153 box.appendChild(toastEl); 154 } 155 156 // ââ Fade in ââââââââââââââââââââââââââââââââââââââââ 157 const showMs = num(opts.showDuration) || 300; 158 const hideMs = num(opts.hideDuration) || 1000; 159 const timeout = num(opts.timeOut); 160 const extTimeout = num(opts.extendedTimeOut); 161 162 toastEl.style.transition = 'opacity ' + showMs + 'ms ease'; 163 toastEl.style.opacity = '0'; 164 toastEl.style.display = 'block'; 165 166 // Force reflow then fade in 167 void toastEl.offsetHeight; 168 toastEl.style.opacity = '1'; 169 170 // ââ Auto-dismiss timer âââââââââââââââââââââââââââââ 171 let timerId = null; 172 let progressStart = null; 173 let progressRaf = null; 174 let remaining = timeout; 175 176 function startTimer(duration) { 177 if (duration <= 0) return; 178 remaining = duration; 179 progressStart = Date.now(); 180 timerId = setTimeout(function () { hideToast(); }, duration); 181 startProgress(duration); 182 } 183 184 function pauseTimer() { 185 if (timerId) { 186 clearTimeout(timerId); 187 timerId = null; 188 } 189 if (progressStart) { 190 remaining = remaining - (Date.now() - progressStart); 191 if (remaining < 0) remaining = 0; 192 } 193 stopProgress(); 194 } 195 196 function resumeTimer() { 197 if (extTimeout > 0) { 198 startTimer(extTimeout); 199 } else if (remaining > 0) { 200 startTimer(remaining); 201 } 202 } 203 204 // ââ Progress bar animation âââââââââââââââââââââââââ 205 function startProgress(duration) { 206 if (!progressEl || duration <= 0) return; 207 const start = Date.now(); 208 progressEl.style.width = '100%'; 209 210 function tick() {
211 const elapsed = Date.now() - start; 212 const pct = Math.max(0, 100 - (elapsed / duration) * 100); 213 progressEl.style.width = pct + '%'; 214 if (pct > 0) { 215 progressRaf = requestAnimationFrame(tick); 216 } 217 } 218 progressRaf = requestAnimationFrame(tick); 219 } 220 221 function stopProgress() { 222 if (progressRaf) { 223 cancelAnimationFrame(progressRaf); 224 progressRaf = null; 225 } 226 } 227 228 // ââ Hide / remove ââââââââââââââââââââââââââââââââââ 229 function hideToast() { 230 stopProgress(); 231 if (timerId) { clearTimeout(timerId); timerId = null; } 232 233 toastEl.style.transition = 'opacity ' + hideMs + 'ms ease'; 234 toastEl.style.opacity = '0'; 235 236 setTimeout(function () { 237 removeToast(toastEl); 238 }, hideMs); 239 } 240 241 function removeToast(node) { 242 if (node && node.parentNode) { 243 node.parentNode.removeChild(node); 244 } 245 // Clean up container if empty 246 if (container && container.children.length === 0) { 247 container.parentNode.removeChild(container); 248 container = null; 249 } 250 } 251 252 // ââ Event handlers âââââââââââââââââââââââââââââââââ 253 toastEl.addEventListener('mouseenter', function () { 254 pauseTimer(); 255 // Ensure visible while hovered 256 toastEl.style.transition = ''; 257 toastEl.style.opacity = '1'; 258 }); 259 260 toastEl.addEventListener('mouseleave', function () { 261 resumeTimer(); 262 }); 263 264 if (opts.tapToDismiss && !opts.onclick) { 265 toastEl.addEventListener('click', function (e) { 266 // Don't dismiss if clicking the close button (it has its own handler) 267 if (e.target.closest && e.target.closest('.toast-close-button')) return; 268 hideToast(); 269 }); 270 } 271 272 if (opts.onclick) { 273 toastEl.addEventListener('click', function () { 274 opts.onclick(); 275 hideToast(); 276 }); 277 } 278 279 if (opts.closeButton && toastEl.querySelector('.toast-close-button')) { 280 toastEl.querySelector('.toast-close-button').addEventListener('click', function (e) { 281 e.stopPropagation(); 282 hideToast(); 283 }); 284 } 285 286 // Start auto-dismiss 287 if (timeout > 0) { 288 startTimer(timeout); 289 } 290 291 // Return jQuery-like wrapper for backward compatibility 292 // channel-control.js does toastElement[0].querySelector(...) 293 return { 0: toastEl, length: 1 }; 294 } 295 296 // ââ Clear / Remove ââââââââââââââââââââââââââââââââââââââ 297 298 function clearAll(immediate) { 299 const box = container || document.getElementById(DEFAULTS.containerId); 300 if (!box) return; 301 302 const toasts = box.children; 303 for (let i = toasts.length - 1; i >= 0; i--) { 304 removeImmediately(toasts[i]); 305 } 306 } 307 308 function removeImmediately(node) { 309 if (node && node.parentNode) { 310 node.parentNode.removeChild(node); 311 } 312 if (container && container.children.length === 0 && container.parentNode) { 313 container.parentNode.removeChild(container); 314 container = null; 315 } 316 } 317 318 // ââ Public API ââââââââââââââââââââââââââââââââââââââââââ 319 320 const toastr = { 321 options: {}, 322 success: function (message, title, optionsOverride) { 323 return show('success', message, title, optionsOverride); 324 }, 325 error: function (message, title, optionsOverride) { 326 return show('error', message, title, optionsOverride); 327 }, 328 info: function (message, title, optionsOverride) { 329 return show('info', message, title, optionsOverride); 330 }, 331 warning: function (message, title, optionsOverride) { 332 return show('warning', message, title, optionsOverride); 333 }, 334 clear: clearAll, 335 remove: clearAll, 336 version: '3.0.0' 337 }; 338 339 window.toastr = toastr; 340})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.