PageSourceSearch

https://www.kalbarriedge.com.au/html/javascript/toastr.min.js?v=c0710c4b

js kalbarriedge.com.au collected 2026-10-08 07:11:08 UTC 11,639 bytes, 340 lines download raw bytes

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.