@tsirosgeorge/toastnotification 5.3.3 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +13 -3
- package/package.json +28 -13
- package/toast.d.ts +89 -0
- package/toast.js +79 -58
- package/toast.min.js +1 -1
- package/toast.module.js +563 -512
- package/LICENCE +0 -16
- package/assets/img/old/error.gif +0 -0
- package/assets/img/old/info.gif +0 -0
- package/assets/img/old/success.gif +0 -0
- package/assets/img/old/warning.gif +0 -0
package/toast.module.js
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
// Single source of truth for the CDN this build points at.
|
|
4
|
+
// `npm run sync:version` rewrites it from package.json, so it can never go stale.
|
|
5
|
+
const TS_TOAST_VERSION = "5.4.0";
|
|
6
|
+
// Point this at your own copy of assets/ to self-host the CSS and icons
|
|
7
|
+
// (useful offline, behind a strict CSP, or when you don't want a CDN dependency):
|
|
8
|
+
// window.TS_TOAST_ASSET_BASE = '/vendor/toastnotification';
|
|
9
|
+
const TS_TOAST_CDN = (typeof window !== 'undefined' && window.TS_TOAST_ASSET_BASE)
|
|
10
|
+
? String(window.TS_TOAST_ASSET_BASE).replace(/\/+$/, '')
|
|
11
|
+
: `https://cdn.jsdelivr.net/npm/@tsirosgeorge/toastnotification@${TS_TOAST_VERSION}`;
|
|
12
|
+
|
|
13
|
+
// Load the stylesheet from the CDN, unless the page opted out by importing it itself
|
|
14
|
+
// (set window.TS_TOAST_NO_CSS = true before loading, or ship assets/css/toast.css yourself).
|
|
15
|
+
(function loadStylesheet() {
|
|
16
|
+
const LINK_ID = 'ts-toast-stylesheet';
|
|
17
|
+
if (typeof window !== 'undefined' && window.TS_TOAST_NO_CSS) return;
|
|
18
|
+
if (document.getElementById(LINK_ID)) return;
|
|
19
|
+
const link = document.createElement("link");
|
|
20
|
+
link.id = LINK_ID;
|
|
21
|
+
link.rel = "stylesheet";
|
|
22
|
+
link.href = `${TS_TOAST_CDN}/assets/css/toast.min.css`;
|
|
23
|
+
document.head.appendChild(link);
|
|
24
|
+
})();
|
|
25
|
+
|
|
26
|
+
// Inject minimal styles for confirm actions and overlay (kept tiny to avoid breaking existing CSS)
|
|
27
|
+
(function injectInlineStyles() {
|
|
28
|
+
const STYLE_ID = 'ts-toast-inline-extras';
|
|
29
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
30
|
+
const style = document.createElement('style');
|
|
31
|
+
style.id = STYLE_ID;
|
|
32
|
+
style.textContent = `
|
|
16
33
|
/* Ensure center positions exist even if external CSS lacks them */
|
|
17
34
|
.ts-toast-container.top-center { top: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
|
|
18
35
|
.ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
|
|
@@ -35,507 +52,541 @@ document.head.appendChild(link);
|
|
|
35
52
|
.ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }
|
|
36
53
|
.ts-toast.ts-toast-confirm.ts-toast-error .ts-toast-icon { background: #fee2e2; }
|
|
37
54
|
`;
|
|
38
|
-
|
|
39
|
-
})();
|
|
55
|
+
document.head.appendChild(style);
|
|
56
|
+
})();
|
|
40
57
|
|
|
41
|
-
// Full implementation copied from toast.js but exposed as ES module
|
|
42
58
|
const toast = function (message, options = {}) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
toast
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
};
|
|
59
|
+
const {
|
|
60
|
+
position = 'top-right',
|
|
61
|
+
animation = 'slide-right', // Default fallback animation
|
|
62
|
+
type = 'info',
|
|
63
|
+
duration = 3000,
|
|
64
|
+
icon = null,
|
|
65
|
+
showLoader = false,
|
|
66
|
+
// behavior/mode: 'alert' (default) or 'confirm'/'swal'
|
|
67
|
+
mode = 'alert',
|
|
68
|
+
// confirm options (used when mode is 'confirm' or 'swal')
|
|
69
|
+
title = null,
|
|
70
|
+
confirmText = 'Yes',
|
|
71
|
+
cancelText = 'No',
|
|
72
|
+
// input field options
|
|
73
|
+
input = false, // 'text', 'email', 'password', 'number', 'textarea', or false
|
|
74
|
+
inputPlaceholder = '',
|
|
75
|
+
inputValue = '',
|
|
76
|
+
// confirm button color customization (optional)
|
|
77
|
+
confirmButtonBg = null,
|
|
78
|
+
confirmButtonColor = null,
|
|
79
|
+
cancelButtonBg = null,
|
|
80
|
+
cancelButtonColor = null,
|
|
81
|
+
onConfirm = null,
|
|
82
|
+
onCancel = null,
|
|
83
|
+
onResult = null,
|
|
84
|
+
useOverlay = true,
|
|
85
|
+
closeOnOverlayClick = true,
|
|
86
|
+
showClose = false,
|
|
87
|
+
// interactions
|
|
88
|
+
dismissOnClick = true, // ignored if confirm-mode
|
|
89
|
+
onClick = null, // Custom onClick event listener
|
|
90
|
+
onShow = null, // Custom onShow event listener
|
|
91
|
+
onDismiss = null // Custom onDismiss event listener
|
|
92
|
+
} = options;
|
|
93
|
+
|
|
94
|
+
const isConfirm = (mode === 'confirm' || mode === 'swal');
|
|
95
|
+
|
|
96
|
+
// Pick an animation intelligently when one wasn't explicitly provided
|
|
97
|
+
const resolvedAnimation = (typeof options.animation === 'string' && options.animation.trim())
|
|
98
|
+
? (function mapAnim(a){
|
|
99
|
+
const m = {
|
|
100
|
+
'slide-top':'ts-toast-slide-top',
|
|
101
|
+
'slide-bottom':'ts-toast-slide-bottom',
|
|
102
|
+
'slide-left':'ts-toast-slide-left',
|
|
103
|
+
'slide-right':'ts-toast-slide-right',
|
|
104
|
+
'zoom-in':'ts-toast-zoom-in',
|
|
105
|
+
'zoom-out':'ts-toast-zoom-out',
|
|
106
|
+
'flip':'ts-toast-flip'
|
|
107
|
+
};
|
|
108
|
+
return m[a] || a;
|
|
109
|
+
})(options.animation.trim())
|
|
110
|
+
: (isConfirm ? 'ts-toast-zoom-in' : (position.startsWith('top') ? 'ts-toast-slide-top'
|
|
111
|
+
: position.startsWith('bottom') ? 'ts-toast-slide-bottom'
|
|
112
|
+
: position.endsWith('left') ? 'ts-toast-slide-left'
|
|
113
|
+
: 'ts-toast-slide-right'));
|
|
114
|
+
|
|
115
|
+
// helper: remove with smooth CSS transition and cleanup (used by alerts)
|
|
116
|
+
const removeWithAnimation = (el, callback) => {
|
|
117
|
+
const anim = el.dataset && el.dataset.anim ? el.dataset.anim : (el.style.animation || '');
|
|
118
|
+
let transform = '';
|
|
119
|
+
if (anim.includes('ts-toast-slide-top')) {
|
|
120
|
+
// Entered from top, exit upwards
|
|
121
|
+
transform = 'translateY(-100%)';
|
|
122
|
+
} else if (anim.includes('ts-toast-slide-bottom')) {
|
|
123
|
+
// Entered from bottom, exit upwards
|
|
124
|
+
transform = 'translateY(100%)';
|
|
125
|
+
} else if (anim.includes('ts-toast-slide-left')) {
|
|
126
|
+
// Entered from left, exit to right
|
|
127
|
+
transform = 'translateX(100%)';
|
|
128
|
+
} else if (anim.includes('ts-toast-slide-right')) {
|
|
129
|
+
// Entered from right, exit to right
|
|
130
|
+
transform = 'translateX(100%)';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
el.classList.add('ts-toast-slide-out');
|
|
134
|
+
el.classList.remove('ts-toast-show');
|
|
135
|
+
// Stop any running keyframe animation and drive exit via CSS transition
|
|
136
|
+
el.style.animation = '';
|
|
137
|
+
if (transform) {
|
|
138
|
+
el.style.transform = transform;
|
|
139
|
+
}
|
|
140
|
+
el.style.opacity = '0';
|
|
141
|
+
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
el.classList.remove('ts-toast-slide-out');
|
|
144
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
145
|
+
if (typeof callback === 'function') callback();
|
|
146
|
+
}, 500);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const toastElement = document.createElement('div');
|
|
150
|
+
toastElement.className = `ts-toast ts-toast-${type}${isConfirm ? ' ts-toast-confirm' : ''}`;
|
|
151
|
+
toastElement.dataset.anim = resolvedAnimation;
|
|
152
|
+
toastElement.style.animation = `${resolvedAnimation} 0.5s ease`;
|
|
153
|
+
// In confirm mode, we stack content vertically; in alert mode keep original layout
|
|
154
|
+
if (!isConfirm) {
|
|
155
|
+
toastElement.style.flexDirection = 'row-reverse';
|
|
156
|
+
toastElement.style.justifyContent = 'flex-end';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Create Icon Element
|
|
160
|
+
const iconElement = document.createElement('span');
|
|
161
|
+
iconElement.className = 'ts-toast-icon';
|
|
162
|
+
iconElement.style.display = 'flex';
|
|
163
|
+
if (icon) {
|
|
164
|
+
iconElement.textContent = icon;
|
|
165
|
+
} else {
|
|
166
|
+
const img = document.createElement('img');
|
|
167
|
+
img.alt = '';
|
|
168
|
+
img.setAttribute('aria-hidden', 'true');
|
|
169
|
+
img.style.width = '30px';
|
|
170
|
+
img.style.height = '30px';
|
|
171
|
+
img.style.objectFit = 'contain';
|
|
172
|
+
|
|
173
|
+
// No cache-buster: these GIFs are immutable per version, so let the
|
|
174
|
+
// browser and the CDN actually cache them.
|
|
175
|
+
const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
|
|
176
|
+
if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
|
|
177
|
+
|
|
178
|
+
iconElement.appendChild(img);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Create Body
|
|
182
|
+
const toastBody = document.createElement('div');
|
|
183
|
+
toastBody.className = 'ts-toast-body';
|
|
184
|
+
toastBody.innerHTML = message; // Allow HTML content in message
|
|
185
|
+
|
|
186
|
+
// Content row for confirm (icon + text side-by-side)
|
|
187
|
+
let contentRow = null;
|
|
188
|
+
if (isConfirm) {
|
|
189
|
+
contentRow = document.createElement('div');
|
|
190
|
+
contentRow.className = 'ts-toast-content';
|
|
191
|
+
contentRow.appendChild(iconElement);
|
|
192
|
+
if (title) {
|
|
193
|
+
const titleEl = document.createElement('div');
|
|
194
|
+
titleEl.className = 'ts-toast-title';
|
|
195
|
+
titleEl.textContent = title;
|
|
196
|
+
contentRow.appendChild(titleEl);
|
|
197
|
+
}
|
|
198
|
+
contentRow.appendChild(toastBody);
|
|
199
|
+
toastElement.appendChild(contentRow);
|
|
200
|
+
} else {
|
|
201
|
+
toastElement.appendChild(toastBody);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Input field (for confirm mode with input)
|
|
205
|
+
let inputElement = null;
|
|
206
|
+
if (isConfirm && input) {
|
|
207
|
+
if (input === 'textarea') {
|
|
208
|
+
inputElement = document.createElement('textarea');
|
|
209
|
+
inputElement.rows = 3;
|
|
210
|
+
} else {
|
|
211
|
+
inputElement = document.createElement('input');
|
|
212
|
+
inputElement.type = input === 'text' || input === 'email' || input === 'password' || input === 'number' ? input : 'text';
|
|
213
|
+
}
|
|
214
|
+
inputElement.className = 'ts-toast-input';
|
|
215
|
+
inputElement.placeholder = inputPlaceholder;
|
|
216
|
+
inputElement.value = inputValue;
|
|
217
|
+
toastElement.appendChild(inputElement);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Actions (for confirm mode)
|
|
221
|
+
let actionsContainer = null;
|
|
222
|
+
let resultResolver = null;
|
|
223
|
+
// Assigned in confirm mode; the overlay and (x) handlers below call it so that
|
|
224
|
+
// *every* way of dismissing the dialog settles the promise and the callbacks.
|
|
225
|
+
let resolveAndClose = null;
|
|
226
|
+
if (isConfirm) {
|
|
227
|
+
actionsContainer = document.createElement('div');
|
|
228
|
+
actionsContainer.className = 'ts-toast-actions';
|
|
229
|
+
|
|
230
|
+
const cancelBtn = document.createElement('button');
|
|
231
|
+
cancelBtn.className = 'ts-toast-btn cancel';
|
|
232
|
+
cancelBtn.textContent = cancelText;
|
|
233
|
+
|
|
234
|
+
const confirmBtn = document.createElement('button');
|
|
235
|
+
confirmBtn.className = 'ts-toast-btn confirm';
|
|
236
|
+
confirmBtn.textContent = confirmText;
|
|
237
|
+
|
|
238
|
+
// Apply custom button colors if provided (inline style overrides theme defaults)
|
|
239
|
+
if (cancelButtonBg) cancelBtn.style.background = cancelButtonBg;
|
|
240
|
+
if (cancelButtonColor) cancelBtn.style.color = cancelButtonColor;
|
|
241
|
+
if (confirmButtonBg) confirmBtn.style.background = confirmButtonBg;
|
|
242
|
+
if (confirmButtonColor) confirmBtn.style.color = confirmButtonColor;
|
|
243
|
+
|
|
244
|
+
actionsContainer.appendChild(cancelBtn);
|
|
245
|
+
actionsContainer.appendChild(confirmBtn);
|
|
246
|
+
toastElement.appendChild(actionsContainer);
|
|
247
|
+
|
|
248
|
+
// Create a Promise that resolves on user choice; expose via property
|
|
249
|
+
toastElement.result = new Promise((resolve) => { resultResolver = resolve; });
|
|
250
|
+
|
|
251
|
+
let settled = false;
|
|
252
|
+
resolveAndClose = (confirmed) => {
|
|
253
|
+
if (settled) return; // a button click and a backdrop click can race
|
|
254
|
+
settled = true;
|
|
255
|
+
// With an input, confirming always yields a string (possibly '') and
|
|
256
|
+
// cancelling yields null, so an empty submission stays distinguishable
|
|
257
|
+
// from a cancel. Without an input the contract is still true/false.
|
|
258
|
+
const result = confirmed
|
|
259
|
+
? (inputElement ? inputElement.value : true)
|
|
260
|
+
: (inputElement ? null : false);
|
|
261
|
+
if (resultResolver) resultResolver(result);
|
|
262
|
+
if (confirmed && typeof onConfirm === 'function') onConfirm(result, toastElement);
|
|
263
|
+
if (!confirmed && typeof onCancel === 'function') onCancel(toastElement);
|
|
264
|
+
if (typeof onResult === 'function') onResult(result, toastElement);
|
|
265
|
+
// Use the same slide+fade removal as alerts
|
|
266
|
+
removeWithAnimation(toastElement, () => {
|
|
267
|
+
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
268
|
+
// Remove overlay if present
|
|
269
|
+
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
cancelBtn.addEventListener('click', (e) => { e.stopPropagation(); resolveAndClose(false); });
|
|
274
|
+
confirmBtn.addEventListener('click', (e) => { e.stopPropagation(); resolveAndClose(true); });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Loader Element
|
|
278
|
+
let loader = null;
|
|
279
|
+
if (showLoader) {
|
|
280
|
+
loader = document.createElement('div');
|
|
281
|
+
loader.className = 'ts-toast-loader';
|
|
282
|
+
toastElement.appendChild(loader);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Container/Overlay
|
|
286
|
+
let overlay = null;
|
|
287
|
+
if (isConfirm && useOverlay) {
|
|
288
|
+
overlay = document.createElement('div');
|
|
289
|
+
// A modal centres by default. The `position` default of 'top-right' is meant
|
|
290
|
+
// for toasts; applying it here parked the dialog in a corner of the backdrop.
|
|
291
|
+
overlay.className = 'ts-toast-overlay' + (options.position ? ` ${position}` : '');
|
|
292
|
+
document.body.appendChild(overlay);
|
|
293
|
+
overlay.appendChild(toastElement);
|
|
294
|
+
if (showClose) {
|
|
295
|
+
const closeBtn = document.createElement('button');
|
|
296
|
+
closeBtn.className = 'ts-toast-close';
|
|
297
|
+
closeBtn.setAttribute('aria-label', 'Close');
|
|
298
|
+
closeBtn.innerHTML = '×';
|
|
299
|
+
closeBtn.addEventListener('click', (e) => {
|
|
300
|
+
e.stopPropagation();
|
|
301
|
+
// Dismissing via (x) is a cancel, so it has to settle like one.
|
|
302
|
+
resolveAndClose(false);
|
|
303
|
+
});
|
|
304
|
+
toastElement.appendChild(closeBtn);
|
|
305
|
+
}
|
|
306
|
+
if (closeOnOverlayClick) {
|
|
307
|
+
overlay.addEventListener('click', (e) => {
|
|
308
|
+
// Backdrop click is a cancel: settle the promise and onCancel/onResult,
|
|
309
|
+
// then close. Previously this resolved only the internal el.result,
|
|
310
|
+
// so `await toast.confirm(...)` hung forever.
|
|
311
|
+
if (e.target === overlay) resolveAndClose(false);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
// Standard positioned container
|
|
316
|
+
let container = document.querySelector(`.ts-toast-container.${position}`);
|
|
317
|
+
if (!container) {
|
|
318
|
+
container = document.createElement('div');
|
|
319
|
+
container.className = `ts-toast-container ${position}`;
|
|
320
|
+
document.body.appendChild(container);
|
|
321
|
+
}
|
|
322
|
+
container.appendChild(toastElement);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Trigger the onShow event if provided
|
|
326
|
+
if (onShow && typeof onShow === 'function') {
|
|
327
|
+
onShow(toastElement);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Show Toast with animation
|
|
331
|
+
setTimeout(() => {
|
|
332
|
+
toastElement.classList.add('ts-toast-show');
|
|
333
|
+
}, 100);
|
|
334
|
+
|
|
335
|
+
// Handle Loader and Icon
|
|
336
|
+
if (showLoader && loader) {
|
|
337
|
+
setTimeout(() => {
|
|
338
|
+
// Skip auto-complete if controlled by toast.loading()
|
|
339
|
+
if (toastElement._managedByLoading) return;
|
|
340
|
+
loader.classList.add('done');
|
|
341
|
+
loader.remove();
|
|
342
|
+
if (!toastElement.contains(iconElement)) {
|
|
343
|
+
if (isConfirm && contentRow) contentRow.appendChild(iconElement);
|
|
344
|
+
else toastElement.appendChild(iconElement); // Add icon only if not present
|
|
345
|
+
}
|
|
346
|
+
}, 2000); // Simulate a loading period of 2 seconds
|
|
347
|
+
}
|
|
348
|
+
if (!showLoader) {
|
|
349
|
+
// For confirm, icon already added above inside contentRow; avoid moving it
|
|
350
|
+
if (!isConfirm && !toastElement.contains(iconElement)) {
|
|
351
|
+
toastElement.appendChild(iconElement);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Auto remove after the duration (skip for confirm mode or when duration <= 0)
|
|
356
|
+
if (!isConfirm && duration > 0) {
|
|
357
|
+
const autoRemove = setTimeout(() => {
|
|
358
|
+
removeWithAnimation(toastElement, () => {
|
|
359
|
+
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
360
|
+
});
|
|
361
|
+
}, duration);
|
|
362
|
+
toastElement._autoRemove = autoRemove;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Add event listener for closing the toast when clicked (disabled in confirm mode)
|
|
366
|
+
if (!isConfirm && dismissOnClick) {
|
|
367
|
+
toastElement.addEventListener('click', () => {
|
|
368
|
+
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove); // Clear the auto-remove timeout
|
|
369
|
+
removeWithAnimation(toastElement, () => {
|
|
370
|
+
if (onClick && typeof onClick === 'function') onClick(toastElement);
|
|
371
|
+
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Add swipe event listeners for mobile dismissal
|
|
377
|
+
if (!isConfirm) {
|
|
378
|
+
let touchStartX = 0;
|
|
379
|
+
let touchEndX = 0;
|
|
380
|
+
|
|
381
|
+
toastElement.addEventListener('touchstart', (e) => {
|
|
382
|
+
touchStartX = e.changedTouches[0].screenX;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
toastElement.addEventListener('touchend', (e) => {
|
|
386
|
+
touchEndX = e.changedTouches[0].screenX;
|
|
387
|
+
if (Math.abs(touchStartX - touchEndX) > 50) { // Swipe distance threshold
|
|
388
|
+
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
389
|
+
removeWithAnimation(toastElement, () => {
|
|
390
|
+
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return toastElement;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Shorthands return the toast element so it can be passed to toast.update().
|
|
400
|
+
toast.success = function (message, options) {
|
|
401
|
+
return toast(message, { ...options, type: 'success' });
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
toast.error = function (message, options) {
|
|
405
|
+
return toast(message, { ...options, type: 'error' });
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
toast.warning = function (message, options) {
|
|
409
|
+
return toast(message, { ...options, type: 'warning' });
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
toast.info = function (message, options) {
|
|
413
|
+
return toast(message, { ...options, type: 'info' });
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// Update toast function to handle removal with animation
|
|
417
|
+
toast.update = function (toastElement, message, options = {}) {
|
|
418
|
+
const {
|
|
419
|
+
type = null,
|
|
420
|
+
icon = null,
|
|
421
|
+
showLoader = false,
|
|
422
|
+
duration = 3000, // Default duration (in ms)
|
|
423
|
+
onClick = null, // Custom onClick event listener
|
|
424
|
+
onShow = null, // Custom onShow event listener
|
|
425
|
+
onDismiss = null // Custom onDismiss event listener
|
|
426
|
+
} = options;
|
|
427
|
+
|
|
428
|
+
// Remove old loader (if any)
|
|
429
|
+
const oldLoader = toastElement.querySelector('.ts-toast-loader');
|
|
430
|
+
const oldIcon = toastElement.querySelector('.ts-toast-icon');
|
|
431
|
+
if (oldLoader) oldLoader.remove();
|
|
432
|
+
|
|
433
|
+
// Update toast class and message. Swap only the type modifier: overwriting
|
|
434
|
+
// className dropped ts-toast-show (so the toast faded out), dropped
|
|
435
|
+
// ts-toast-confirm (so confirm dialogs lost their layout), and leaked the
|
|
436
|
+
// position onto the toast instead of the container.
|
|
437
|
+
if (type) {
|
|
438
|
+
['success', 'error', 'info', 'warning'].forEach((t) => toastElement.classList.remove(`ts-toast-${t}`));
|
|
439
|
+
toastElement.classList.add('ts-toast', `ts-toast-${type}`, 'ts-toast-show');
|
|
440
|
+
}
|
|
441
|
+
const toastBody = toastElement.querySelector('.ts-toast-body');
|
|
442
|
+
if (toastBody) {
|
|
443
|
+
toastBody.innerHTML = message;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Handle Icon update only if it's new or hasn't been set yet
|
|
447
|
+
if (oldIcon) {
|
|
448
|
+
oldIcon.remove(); // Remove the old icon first
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const iconElement = document.createElement('span');
|
|
452
|
+
iconElement.className = 'ts-toast-icon';
|
|
453
|
+
iconElement.style.display = 'flex';
|
|
454
|
+
|
|
455
|
+
if (icon) {
|
|
456
|
+
iconElement.textContent = icon;
|
|
457
|
+
} else {
|
|
458
|
+
const img = document.createElement('img');
|
|
459
|
+
img.alt = '';
|
|
460
|
+
img.setAttribute('aria-hidden', 'true');
|
|
461
|
+
img.style.width = '30px';
|
|
462
|
+
img.style.height = '30px';
|
|
463
|
+
img.style.objectFit = 'contain';
|
|
464
|
+
|
|
465
|
+
const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
|
|
466
|
+
if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
|
|
467
|
+
|
|
468
|
+
iconElement.appendChild(img);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Append the new icon immediately (inside the content row for confirm dialogs)
|
|
472
|
+
const contentRow = toastElement.querySelector('.ts-toast-content');
|
|
473
|
+
(contentRow || toastElement).appendChild(iconElement);
|
|
474
|
+
|
|
475
|
+
// Handle loader if requested
|
|
476
|
+
if (showLoader) {
|
|
477
|
+
const loader = document.createElement('div');
|
|
478
|
+
loader.className = 'ts-toast-loader';
|
|
479
|
+
toastElement.appendChild(loader);
|
|
480
|
+
setTimeout(() => {
|
|
481
|
+
loader.classList.add('done');
|
|
482
|
+
}, 2000); // Simulate loader completion after 2 seconds
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Clear previous auto-remove timer if needed
|
|
486
|
+
if (toastElement._autoRemove) {
|
|
487
|
+
clearTimeout(toastElement._autoRemove);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Set the auto-remove timer again to ensure toast disappears after the duration
|
|
491
|
+
const autoRemove = setTimeout(() => {
|
|
492
|
+
const removeWithAnimation = (el, cb) => {
|
|
493
|
+
el.classList.add('ts-toast-slide-out');
|
|
494
|
+
el.classList.remove('ts-toast-show');
|
|
495
|
+
el.style.animation = '';
|
|
496
|
+
setTimeout(() => {
|
|
497
|
+
el.classList.remove('ts-toast-slide-out');
|
|
498
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
499
|
+
if (typeof cb === 'function') cb();
|
|
500
|
+
}, 500);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
removeWithAnimation(toastElement, () => {
|
|
504
|
+
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
505
|
+
});
|
|
506
|
+
}, duration);
|
|
507
|
+
|
|
508
|
+
toastElement._autoRemove = autoRemove; // Re-set the auto-remove timer
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
toast.loading = function (message, options = {}) {
|
|
512
|
+
const toastElement = toast(message, {
|
|
513
|
+
...options,
|
|
514
|
+
type: options.type || 'info', // Default type is 'info'
|
|
515
|
+
duration: 0, // Sticky until manually updated/closed
|
|
516
|
+
showLoader: true, // Always show loader during loading
|
|
517
|
+
icon: null
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Force reflow and add animation after DOM insert
|
|
521
|
+
requestAnimationFrame(() => {
|
|
522
|
+
toastElement.classList.add('ts-toast-show');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const loader = toastElement.querySelector('.ts-toast-loader');
|
|
526
|
+
let iconElement = toastElement.querySelector('.ts-toast-icon');
|
|
527
|
+
|
|
528
|
+
// Ensure the iconElement is created and appended if it doesn't exist
|
|
529
|
+
if (!iconElement) {
|
|
530
|
+
iconElement = document.createElement('span');
|
|
531
|
+
iconElement.className = 'ts-toast-icon';
|
|
532
|
+
iconElement.style.display = 'flex';
|
|
533
|
+
toastElement.appendChild(iconElement);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// mark as managed by loading flow to avoid internal auto-complete
|
|
537
|
+
toastElement._managedByLoading = true;
|
|
538
|
+
|
|
539
|
+
// Ensure loader is handled properly
|
|
540
|
+
if (loader) {
|
|
541
|
+
setTimeout(() => {
|
|
542
|
+
// Keep spinning until update() decides otherwise
|
|
543
|
+
if (!toastElement._managedByLoading) loader.classList.add('done');
|
|
544
|
+
}, 2000); // Simulate a loading period of 2 seconds
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
update: (newMessage, newOptions = {}) => {
|
|
549
|
+
// Let update manage completion: stop managing/finish loader
|
|
550
|
+
toastElement._managedByLoading = false;
|
|
551
|
+
toast.update(toastElement, newMessage, {
|
|
552
|
+
...newOptions,
|
|
553
|
+
showLoader: false // Disable loader when updating the message
|
|
554
|
+
});
|
|
555
|
+
},
|
|
556
|
+
close: () => {
|
|
557
|
+
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
558
|
+
toastElement.classList.add('ts-toast-slide-out');
|
|
559
|
+
toastElement.classList.remove('ts-toast-show');
|
|
560
|
+
toastElement.style.animation = '';
|
|
561
|
+
setTimeout(() => {
|
|
562
|
+
toastElement.classList.remove('ts-toast-slide-out');
|
|
563
|
+
if (toastElement.parentNode) toastElement.parentNode.removeChild(toastElement);
|
|
564
|
+
}, 500);
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
// Convenience API: swal-like confirm dialog
|
|
570
|
+
// Usage: toast.confirm('Are you sure?', { type: 'warning', confirmText: 'Yes', cancelText: 'No' }).then(ok => {...})
|
|
571
|
+
toast.confirm = function (message, options = {}) {
|
|
572
|
+
return new Promise((resolve) => {
|
|
573
|
+
const el = toast(message, {
|
|
574
|
+
...options,
|
|
575
|
+
mode: 'confirm',
|
|
576
|
+
duration: 0, // prevent auto-dismiss
|
|
577
|
+
dismissOnClick: false,
|
|
578
|
+
onResult: (val) => resolve(val)
|
|
579
|
+
});
|
|
580
|
+
// If consumer needs the element, it is returned by toast() but we ignore here.
|
|
581
|
+
// They can still call toast(...) with mode: 'confirm' to get the element and read el.result
|
|
582
|
+
void el; // no-op
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Expose globally for CDN / browser usage
|
|
587
|
+
if (typeof window !== 'undefined') {
|
|
588
|
+
window.toast = toast;
|
|
589
|
+
}
|
|
539
590
|
|
|
540
591
|
// ES module export
|
|
541
592
|
export default toast;
|