toastify-pro 1.5.0 → 1.7.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 +143 -845
- package/assets/screenshots/desktop-demo.png +0 -0
- package/dist/toastify-pro.esm.js +1294 -59
- package/dist/toastify-pro.esm.js.map +1 -1
- package/dist/toastify-pro.umd.js +1294 -59
- package/dist/toastify-pro.umd.js.map +1 -1
- package/dist/toastify-pro.umd.min.js +10 -3
- package/dist/toastify-pro.umd.min.js.map +1 -1
- package/package.json +5 -2
- package/src/toastify-pro.js +1294 -59
- package/assets/site/site.webmanifest +0 -217
package/dist/toastify-pro.esm.js
CHANGED
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
* - Responsive design for mobile devices
|
|
15
15
|
* - Framework agnostic (works with React, Vue, Angular, etc.)
|
|
16
16
|
* - Confirmation dialogs with customizable buttons and callbacks
|
|
17
|
+
* - Input prompts with validation and async support
|
|
17
18
|
* - Confirmation overlay with blur effect for focus
|
|
18
19
|
* - Center position support for enhanced focus
|
|
19
20
|
* - Independent positioning for confirmations
|
|
21
|
+
* - Action buttons in toasts with customizable callbacks
|
|
22
|
+
* - Pause on hover functionality
|
|
23
|
+
* - Queue management (maxToasts, newestOnTop)
|
|
24
|
+
* - Full accessibility support (ARIA, keyboard navigation, reduced motion)
|
|
25
|
+
* - Focus management for confirmation and input dialogs
|
|
26
|
+
* - Improved dismiss handling (no hover interference)
|
|
20
27
|
*
|
|
21
|
-
* @version 1.
|
|
28
|
+
* @version 1.7.0
|
|
22
29
|
* @author ToastifyPro Team
|
|
23
30
|
* @license MIT
|
|
24
31
|
*/
|
|
@@ -36,6 +43,10 @@ class ToastifyPro {
|
|
|
36
43
|
* @param {number} options.maxLength - Maximum message length
|
|
37
44
|
* @param {string} options.primaryColor - Primary color for custom() method
|
|
38
45
|
* @param {string} options.secondaryColor - Secondary color for gradient in custom() method
|
|
46
|
+
* @param {boolean} options.pauseOnHover - Pause timeout when hovering over toast (default: true)
|
|
47
|
+
* @param {number} options.maxToasts - Maximum number of visible toasts (0 for unlimited)
|
|
48
|
+
* @param {boolean} options.newestOnTop - Show newest toasts on top (default: true)
|
|
49
|
+
* @param {boolean} options.ariaLive - ARIA live region setting: 'polite' or 'assertive' (default: 'polite')
|
|
39
50
|
*/
|
|
40
51
|
constructor(options = {}) {
|
|
41
52
|
// Validate options parameter
|
|
@@ -52,7 +63,14 @@ class ToastifyPro {
|
|
|
52
63
|
maxLength: options.maxLength || 100,
|
|
53
64
|
primaryColor: options.primaryColor || null, // Custom primary color for custom() method
|
|
54
65
|
secondaryColor: options.secondaryColor || null, // Custom secondary color for gradient
|
|
66
|
+
pauseOnHover: options.pauseOnHover !== false, // default true - pause timeout on hover
|
|
67
|
+
maxToasts: options.maxToasts || 0, // 0 = unlimited
|
|
68
|
+
newestOnTop: options.newestOnTop !== false, // default true
|
|
69
|
+
ariaLive: options.ariaLive || 'polite', // 'polite' or 'assertive'
|
|
55
70
|
};
|
|
71
|
+
|
|
72
|
+
// Track active toasts for queue management
|
|
73
|
+
this.activeToasts = [];
|
|
56
74
|
|
|
57
75
|
// Validate position
|
|
58
76
|
const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center', 'center'];
|
|
@@ -85,6 +103,46 @@ class ToastifyPro {
|
|
|
85
103
|
|
|
86
104
|
// Inject styles once
|
|
87
105
|
this.injectStyles();
|
|
106
|
+
|
|
107
|
+
// Setup global keyboard event listener for accessibility
|
|
108
|
+
this.setupKeyboardNavigation();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sets up keyboard navigation for accessibility
|
|
113
|
+
* - Escape key dismisses the most recent toast or confirmation
|
|
114
|
+
* - Tab key cycles through focusable elements in confirmations
|
|
115
|
+
*/
|
|
116
|
+
setupKeyboardNavigation() {
|
|
117
|
+
// Only setup once globally
|
|
118
|
+
if (window._toastifyProKeyboardSetup) return;
|
|
119
|
+
window._toastifyProKeyboardSetup = true;
|
|
120
|
+
|
|
121
|
+
document.addEventListener('keydown', (e) => {
|
|
122
|
+
// Escape key - dismiss toast or confirmation
|
|
123
|
+
if (e.key === 'Escape') {
|
|
124
|
+
// First check for active confirmation
|
|
125
|
+
if (globalActiveConfirmation && globalActiveConfirmation.element) {
|
|
126
|
+
const loadingBtn = globalActiveConfirmation.element.querySelector('.toast-btn-confirm.loading');
|
|
127
|
+
if (!loadingBtn) {
|
|
128
|
+
globalActiveConfirmation.close();
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Otherwise dismiss the most recent toast
|
|
134
|
+
const containers = document.querySelectorAll('.toastify-pro-container');
|
|
135
|
+
containers.forEach(container => {
|
|
136
|
+
const toasts = container.querySelectorAll('.toastify-pro:not(.confirmation)');
|
|
137
|
+
if (toasts.length > 0) {
|
|
138
|
+
const lastToast = toasts[toasts.length - 1];
|
|
139
|
+
if (lastToast && lastToast._toastInstance) {
|
|
140
|
+
lastToast._toastInstance.removeToast(lastToast);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
88
146
|
}
|
|
89
147
|
|
|
90
148
|
/**
|
|
@@ -208,19 +266,19 @@ class ToastifyPro {
|
|
|
208
266
|
@keyframes airdropPop {
|
|
209
267
|
0% {
|
|
210
268
|
opacity: 0;
|
|
211
|
-
transform: scale(0.
|
|
269
|
+
transform: scale(0.5) rotateY(-15deg) translateY(20px);
|
|
212
270
|
}
|
|
213
|
-
|
|
214
|
-
opacity: 0.
|
|
215
|
-
transform: scale(1.
|
|
271
|
+
50% {
|
|
272
|
+
opacity: 0.95;
|
|
273
|
+
transform: scale(1.03) rotateY(5deg) translateY(-3px);
|
|
216
274
|
}
|
|
217
|
-
|
|
275
|
+
75% {
|
|
218
276
|
opacity: 1;
|
|
219
|
-
transform: scale(0.
|
|
277
|
+
transform: scale(0.99) rotateY(-1deg) translateY(1px);
|
|
220
278
|
}
|
|
221
279
|
100% {
|
|
222
280
|
opacity: 1;
|
|
223
|
-
transform: scale(1) rotateY(0deg);
|
|
281
|
+
transform: scale(1) rotateY(0deg) translateY(0);
|
|
224
282
|
}
|
|
225
283
|
}
|
|
226
284
|
|
|
@@ -229,13 +287,13 @@ class ToastifyPro {
|
|
|
229
287
|
opacity: 1;
|
|
230
288
|
transform: scale(1) translateY(0);
|
|
231
289
|
}
|
|
232
|
-
|
|
290
|
+
12% {
|
|
233
291
|
opacity: 1;
|
|
234
|
-
transform: scale(1.
|
|
292
|
+
transform: scale(1.015) translateY(-6px);
|
|
235
293
|
}
|
|
236
294
|
100% {
|
|
237
295
|
opacity: 0;
|
|
238
|
-
transform: scale(0.
|
|
296
|
+
transform: scale(0.85) translateY(150px);
|
|
239
297
|
}
|
|
240
298
|
}
|
|
241
299
|
|
|
@@ -244,13 +302,13 @@ class ToastifyPro {
|
|
|
244
302
|
opacity: 1;
|
|
245
303
|
transform: scale(1) translateY(0);
|
|
246
304
|
}
|
|
247
|
-
|
|
305
|
+
12% {
|
|
248
306
|
opacity: 1;
|
|
249
|
-
transform: scale(1.
|
|
307
|
+
transform: scale(1.015) translateY(6px);
|
|
250
308
|
}
|
|
251
309
|
100% {
|
|
252
310
|
opacity: 0;
|
|
253
|
-
transform: scale(0.
|
|
311
|
+
transform: scale(0.85) translateY(-150px);
|
|
254
312
|
}
|
|
255
313
|
}
|
|
256
314
|
|
|
@@ -259,13 +317,13 @@ class ToastifyPro {
|
|
|
259
317
|
opacity: 1;
|
|
260
318
|
transform: scale(1) translateX(0);
|
|
261
319
|
}
|
|
262
|
-
|
|
320
|
+
12% {
|
|
263
321
|
opacity: 1;
|
|
264
|
-
transform: scale(1.
|
|
322
|
+
transform: scale(1.015) translateX(6px);
|
|
265
323
|
}
|
|
266
324
|
100% {
|
|
267
325
|
opacity: 0;
|
|
268
|
-
transform: scale(0.
|
|
326
|
+
transform: scale(0.85) translateX(-250px);
|
|
269
327
|
}
|
|
270
328
|
}
|
|
271
329
|
|
|
@@ -274,13 +332,13 @@ class ToastifyPro {
|
|
|
274
332
|
opacity: 1;
|
|
275
333
|
transform: scale(1) translateX(0);
|
|
276
334
|
}
|
|
277
|
-
|
|
335
|
+
12% {
|
|
278
336
|
opacity: 1;
|
|
279
|
-
transform: scale(1.
|
|
337
|
+
transform: scale(1.015) translateX(-6px);
|
|
280
338
|
}
|
|
281
339
|
100% {
|
|
282
340
|
opacity: 0;
|
|
283
|
-
transform: scale(0.
|
|
341
|
+
transform: scale(0.85) translateX(250px);
|
|
284
342
|
}
|
|
285
343
|
}
|
|
286
344
|
|
|
@@ -289,13 +347,13 @@ class ToastifyPro {
|
|
|
289
347
|
opacity: 1;
|
|
290
348
|
transform: scale(1) translateY(0);
|
|
291
349
|
}
|
|
292
|
-
|
|
350
|
+
12% {
|
|
293
351
|
opacity: 1;
|
|
294
|
-
transform: scale(1.
|
|
352
|
+
transform: scale(1.015) translateY(-4px);
|
|
295
353
|
}
|
|
296
354
|
100% {
|
|
297
355
|
opacity: 0;
|
|
298
|
-
transform: scale(0.
|
|
356
|
+
transform: scale(0.7) translateY(120px);
|
|
299
357
|
}
|
|
300
358
|
}
|
|
301
359
|
|
|
@@ -332,7 +390,7 @@ class ToastifyPro {
|
|
|
332
390
|
.toastify-pro.show {
|
|
333
391
|
opacity: 1;
|
|
334
392
|
transform: scale(1);
|
|
335
|
-
animation: airdropPop 0.
|
|
393
|
+
animation: airdropPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
336
394
|
}
|
|
337
395
|
|
|
338
396
|
.toastify-pro.success {
|
|
@@ -411,9 +469,9 @@ class ToastifyPro {
|
|
|
411
469
|
}
|
|
412
470
|
|
|
413
471
|
@keyframes iconBounce {
|
|
414
|
-
0% { transform: scale(0.
|
|
415
|
-
|
|
416
|
-
70% { transform: scale(0.
|
|
472
|
+
0% { transform: scale(0.3) rotate(-10deg); opacity: 0; }
|
|
473
|
+
50% { transform: scale(1.1) rotate(5deg); opacity: 1; }
|
|
474
|
+
70% { transform: scale(0.97) rotate(-2deg); }
|
|
417
475
|
100% { transform: scale(1) rotate(0deg); }
|
|
418
476
|
}
|
|
419
477
|
|
|
@@ -422,12 +480,12 @@ class ToastifyPro {
|
|
|
422
480
|
transform: scale(1) rotate(0deg);
|
|
423
481
|
opacity: 1;
|
|
424
482
|
}
|
|
425
|
-
|
|
426
|
-
transform: scale(1.
|
|
427
|
-
opacity: 0.
|
|
483
|
+
25% {
|
|
484
|
+
transform: scale(1.08) rotate(-8deg);
|
|
485
|
+
opacity: 0.85;
|
|
428
486
|
}
|
|
429
487
|
100% {
|
|
430
|
-
transform: scale(0.
|
|
488
|
+
transform: scale(0.4) rotate(120deg);
|
|
431
489
|
opacity: 0;
|
|
432
490
|
}
|
|
433
491
|
}
|
|
@@ -436,6 +494,11 @@ class ToastifyPro {
|
|
|
436
494
|
0% { transform: rotate(0deg); }
|
|
437
495
|
100% { transform: rotate(360deg); }
|
|
438
496
|
}
|
|
497
|
+
|
|
498
|
+
@keyframes spinFast {
|
|
499
|
+
0% { transform: rotate(0deg); }
|
|
500
|
+
100% { transform: rotate(360deg); }
|
|
501
|
+
}
|
|
439
502
|
|
|
440
503
|
.toastify-pro .toast-icon svg {
|
|
441
504
|
width: 18px;
|
|
@@ -479,6 +542,7 @@ class ToastifyPro {
|
|
|
479
542
|
transition: all 0.2s ease;
|
|
480
543
|
flex-shrink: 0;
|
|
481
544
|
width: 32px;
|
|
545
|
+
border: none;
|
|
482
546
|
height: 32px;
|
|
483
547
|
display: flex;
|
|
484
548
|
align-items: center;
|
|
@@ -710,6 +774,9 @@ class ToastifyPro {
|
|
|
710
774
|
}
|
|
711
775
|
|
|
712
776
|
.toast-btn-confirm {
|
|
777
|
+
display: flex;
|
|
778
|
+
align-items: center;
|
|
779
|
+
justify-content: center;
|
|
713
780
|
color: white;
|
|
714
781
|
font-weight: 700;
|
|
715
782
|
border: 2px solid rgba(255, 255, 255, 0.4);
|
|
@@ -748,17 +815,20 @@ class ToastifyPro {
|
|
|
748
815
|
|
|
749
816
|
.toast-btn-confirm .btn-spinner {
|
|
750
817
|
display: none;
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
818
|
+
align-items: center;
|
|
819
|
+
justify-content: center;
|
|
820
|
+
margin-left: 8px;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.toast-btn-confirm .btn-spinner svg {
|
|
824
|
+
width: 25px;
|
|
825
|
+
height: 25px;
|
|
826
|
+
animation: spinFast 0.5s linear infinite;
|
|
827
|
+
color: currentColor;
|
|
758
828
|
}
|
|
759
829
|
|
|
760
830
|
.toast-btn-confirm.loading .btn-spinner {
|
|
761
|
-
display: inline-
|
|
831
|
+
display: inline-flex;
|
|
762
832
|
}
|
|
763
833
|
|
|
764
834
|
.toast-btn-confirm.loading .btn-text {
|
|
@@ -848,6 +918,356 @@ class ToastifyPro {
|
|
|
848
918
|
.toastify-pro-overlay.show {
|
|
849
919
|
opacity: 1;
|
|
850
920
|
}
|
|
921
|
+
|
|
922
|
+
/* Action Button Styles */
|
|
923
|
+
.toastify-pro .toast-action {
|
|
924
|
+
display: inline-flex;
|
|
925
|
+
align-items: center;
|
|
926
|
+
gap: 6px;
|
|
927
|
+
padding: 6px 12px;
|
|
928
|
+
margin-top: 8px;
|
|
929
|
+
border: none;
|
|
930
|
+
border-radius: 8px;
|
|
931
|
+
font-weight: 600;
|
|
932
|
+
font-size: 13px;
|
|
933
|
+
cursor: pointer;
|
|
934
|
+
transition: all 0.2s ease;
|
|
935
|
+
background: rgba(255, 255, 255, 0.2);
|
|
936
|
+
color: inherit;
|
|
937
|
+
backdrop-filter: blur(10px);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
.toastify-pro .toast-action:hover {
|
|
941
|
+
background: rgba(255, 255, 255, 0.3);
|
|
942
|
+
transform: translateY(-1px);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
.toastify-pro .toast-action:active {
|
|
946
|
+
transform: translateY(0);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.toastify-pro.light .toast-action {
|
|
950
|
+
background: rgba(15, 23, 42, 0.1);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.toastify-pro.light .toast-action:hover {
|
|
954
|
+
background: rgba(15, 23, 42, 0.15);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/* Paused state - pause progress bar */
|
|
958
|
+
.toastify-pro.paused::after {
|
|
959
|
+
animation-play-state: paused;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* Progress restart - used after hover to restart progress bar only */
|
|
963
|
+
.toastify-pro.progress-restart::after {
|
|
964
|
+
animation: none;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* Focus styles for accessibility */
|
|
968
|
+
.toastify-pro .close-btn:focus,
|
|
969
|
+
.toastify-pro .toast-action:focus,
|
|
970
|
+
.toast-btn:focus {
|
|
971
|
+
outline: 1px solid rgba(255, 255, 255, 0.8);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
.toastify-pro.light .close-btn:focus,
|
|
975
|
+
.toastify-pro.light .toast-action:focus {
|
|
976
|
+
outline-color: 1px solid rgba(15, 23, 42, 0.5);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/* Screen reader only class */
|
|
980
|
+
.sr-only {
|
|
981
|
+
position: absolute;
|
|
982
|
+
width: 1px;
|
|
983
|
+
height: 1px;
|
|
984
|
+
padding: 0;
|
|
985
|
+
margin: -1px;
|
|
986
|
+
overflow: hidden;
|
|
987
|
+
clip: rect(0, 0, 0, 0);
|
|
988
|
+
white-space: nowrap;
|
|
989
|
+
border: 0;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/* ===== INPUT TOAST STYLES ===== */
|
|
993
|
+
.toastify-pro.input-toast {
|
|
994
|
+
min-width: 340px;
|
|
995
|
+
max-width: 450px;
|
|
996
|
+
padding: 24px 24px 20px;
|
|
997
|
+
flex-direction: column;
|
|
998
|
+
align-items: stretch;
|
|
999
|
+
gap: 16px;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.toastify-pro.input-toast .toast-input-wrapper {
|
|
1003
|
+
display: flex;
|
|
1004
|
+
flex-direction: column;
|
|
1005
|
+
gap: 16px;
|
|
1006
|
+
width: 100%;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.toastify-pro.input-toast .toast-input-header {
|
|
1010
|
+
display: flex;
|
|
1011
|
+
align-items: flex-start;
|
|
1012
|
+
gap: 12px;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
.toastify-pro.input-toast .toast-input {
|
|
1016
|
+
width: 100%;
|
|
1017
|
+
padding: 12px 16px;
|
|
1018
|
+
border: 1.5px solid rgba(255, 255, 255, 0.2);
|
|
1019
|
+
border-radius: 12px;
|
|
1020
|
+
background: rgba(255, 255, 255, 0.08);
|
|
1021
|
+
backdrop-filter: blur(12px);
|
|
1022
|
+
color: inherit;
|
|
1023
|
+
font-family: inherit;
|
|
1024
|
+
font-size: 14px;
|
|
1025
|
+
font-weight: 450;
|
|
1026
|
+
outline: none;
|
|
1027
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1028
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
.toastify-pro.input-toast .toast-input::placeholder {
|
|
1032
|
+
color: rgba(255, 255, 255, 0.5);
|
|
1033
|
+
font-weight: 400;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.toastify-pro.input-toast .toast-input:focus {
|
|
1037
|
+
border-color: rgba(255, 255, 255, 0.45);
|
|
1038
|
+
background: rgba(255, 255, 255, 0.12);
|
|
1039
|
+
box-shadow:
|
|
1040
|
+
inset 0 1px 2px rgba(0, 0, 0, 0.1),
|
|
1041
|
+
0 0 0 3px rgba(255, 255, 255, 0.08);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
.toastify-pro.input-toast .toast-input:hover:not(:focus) {
|
|
1045
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
1046
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
.toastify-pro.input-toast.light .toast-input {
|
|
1050
|
+
border-color: rgba(15, 23, 42, 0.15);
|
|
1051
|
+
background: rgba(15, 23, 42, 0.04);
|
|
1052
|
+
box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.06);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.toastify-pro.input-toast.light .toast-input::placeholder {
|
|
1056
|
+
color: rgba(15, 23, 42, 0.45);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
.toastify-pro.input-toast.light .toast-input:focus {
|
|
1060
|
+
border-color: rgba(15, 23, 42, 0.35);
|
|
1061
|
+
background: rgba(15, 23, 42, 0.06);
|
|
1062
|
+
box-shadow:
|
|
1063
|
+
inset 0 1px 2px rgba(15, 23, 42, 0.06),
|
|
1064
|
+
0 0 0 3px rgba(15, 23, 42, 0.06);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
.toastify-pro.input-toast.light .toast-input:hover:not(:focus) {
|
|
1068
|
+
border-color: rgba(15, 23, 42, 0.25);
|
|
1069
|
+
background: rgba(15, 23, 42, 0.06);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
.toastify-pro.input-toast .toast-input-actions {
|
|
1073
|
+
display: flex;
|
|
1074
|
+
gap: 10px;
|
|
1075
|
+
justify-content: flex-end;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.toastify-pro.input-toast .input-btn {
|
|
1079
|
+
padding: 10px 20px;
|
|
1080
|
+
border-radius: 10px;
|
|
1081
|
+
font-size: 14px;
|
|
1082
|
+
font-weight: 550;
|
|
1083
|
+
cursor: pointer;
|
|
1084
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
1085
|
+
border: 1.5px solid transparent;
|
|
1086
|
+
position: relative;
|
|
1087
|
+
overflow: hidden;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.toastify-pro.input-toast .input-btn-cancel {
|
|
1091
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1092
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1093
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.toastify-pro.input-toast .input-btn-cancel:hover {
|
|
1097
|
+
background: rgba(255, 255, 255, 0.18);
|
|
1098
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.toastify-pro.input-toast .input-btn-submit {
|
|
1102
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%);
|
|
1103
|
+
color: #1e293b;
|
|
1104
|
+
border-color: rgba(255, 255, 255, 0.4);
|
|
1105
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.toastify-pro.input-toast .input-btn-submit:hover {
|
|
1109
|
+
background: linear-gradient(135deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.95) 100%);
|
|
1110
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
|
1111
|
+
transform: translateY(-1px);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.toastify-pro.input-toast .input-btn-submit:active {
|
|
1115
|
+
transform: translateY(0);
|
|
1116
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.toastify-pro.input-toast.light .input-btn-cancel {
|
|
1120
|
+
background: rgba(15, 23, 42, 0.06);
|
|
1121
|
+
color: rgba(15, 23, 42, 0.85);
|
|
1122
|
+
border-color: rgba(15, 23, 42, 0.15);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.toastify-pro.input-toast.light .input-btn-cancel:hover {
|
|
1126
|
+
background: rgba(15, 23, 42, 0.1);
|
|
1127
|
+
border-color: rgba(15, 23, 42, 0.25);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.toastify-pro.input-toast.light .input-btn-submit {
|
|
1131
|
+
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
|
1132
|
+
color: white;
|
|
1133
|
+
border-color: rgba(15, 23, 42, 0.3);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.toastify-pro.input-toast.light .input-btn-submit:hover {
|
|
1137
|
+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
1138
|
+
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.25);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/* Input toast loading state */
|
|
1142
|
+
.toastify-pro.input-toast .input-btn-submit.loading {
|
|
1143
|
+
opacity: 0.7;
|
|
1144
|
+
cursor: not-allowed;
|
|
1145
|
+
pointer-events: none;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.toastify-pro.input-toast .input-btn-submit .btn-spinner {
|
|
1149
|
+
display: none;
|
|
1150
|
+
align-items: center;
|
|
1151
|
+
justify-content: center;
|
|
1152
|
+
margin-left: 6px;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.toastify-pro.input-toast .input-btn-submit .btn-spinner svg {
|
|
1156
|
+
width: 16px;
|
|
1157
|
+
height: 16px;
|
|
1158
|
+
animation: spinFast 0.5s linear infinite;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.toastify-pro.input-toast .input-btn-submit.loading .btn-spinner {
|
|
1162
|
+
display: inline-flex;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.toastify-pro.input-toast .input-btn-submit.loading .btn-text {
|
|
1166
|
+
opacity: 0.7;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/* Input validation error state */
|
|
1170
|
+
.toastify-pro.input-toast .toast-input.error {
|
|
1171
|
+
border-color: rgba(239, 68, 68, 0.6);
|
|
1172
|
+
background: rgba(239, 68, 68, 0.08);
|
|
1173
|
+
animation: inputShake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.toastify-pro.input-toast .toast-input-error {
|
|
1177
|
+
color: #f87171;
|
|
1178
|
+
font-size: 12px;
|
|
1179
|
+
font-weight: 450;
|
|
1180
|
+
margin-top: -8px;
|
|
1181
|
+
opacity: 0;
|
|
1182
|
+
transform: translateY(-4px);
|
|
1183
|
+
transition: all 0.2s ease;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.toastify-pro.input-toast .toast-input-error.visible {
|
|
1187
|
+
opacity: 1;
|
|
1188
|
+
transform: translateY(0);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.toastify-pro.input-toast.light .toast-input.error {
|
|
1192
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
1193
|
+
background: rgba(239, 68, 68, 0.06);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.toastify-pro.input-toast.light .toast-input-error {
|
|
1197
|
+
color: #dc2626;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
@keyframes inputShake {
|
|
1201
|
+
0%, 100% { transform: translateX(0); }
|
|
1202
|
+
20%, 60% { transform: translateX(-6px); }
|
|
1203
|
+
40%, 80% { transform: translateX(6px); }
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/* Hide progress bar for input toasts */
|
|
1207
|
+
.toastify-pro.input-toast::after {
|
|
1208
|
+
display: none;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
@media (max-width: 640px) {
|
|
1212
|
+
.toastify-pro.input-toast {
|
|
1213
|
+
min-width: 280px;
|
|
1214
|
+
max-width: calc(100vw - 32px);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.toastify-pro.input-toast .toast-input-actions {
|
|
1218
|
+
flex-direction: column;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.toastify-pro.input-toast .input-btn {
|
|
1222
|
+
width: 100%;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/* Reduced motion support */
|
|
1227
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1228
|
+
.toastify-pro {
|
|
1229
|
+
transition: opacity 0.3s ease;
|
|
1230
|
+
transform: none !important;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
.toastify-pro.show {
|
|
1234
|
+
animation: none !important;
|
|
1235
|
+
opacity: 1;
|
|
1236
|
+
transform: none !important;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.toastify-pro .toast-icon {
|
|
1240
|
+
animation: none !important;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.toastify-pro::before {
|
|
1244
|
+
animation: none !important;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.toastify-pro::after {
|
|
1248
|
+
animation: progress var(--duration, 5s) linear !important;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.toastify-pro-overlay {
|
|
1252
|
+
transition: opacity 0.2s ease;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.toast-btn::after {
|
|
1256
|
+
display: none;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.toast-btn:hover {
|
|
1260
|
+
transform: none;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.toastify-pro.confirmation .conf-close-btn:hover {
|
|
1264
|
+
transform: scale(1.05);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
.btn-spinner svg {
|
|
1268
|
+
animation: spinFast 0.5s linear infinite !important;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
851
1271
|
`;
|
|
852
1272
|
document.head.appendChild(style);
|
|
853
1273
|
} catch (error) {
|
|
@@ -864,6 +1284,9 @@ class ToastifyPro {
|
|
|
864
1284
|
* @param {number} opts.timeout - Override default timeout
|
|
865
1285
|
* @param {boolean} opts.allowClose - Override close button setting
|
|
866
1286
|
* @param {number} opts.maxLength - Override max message length
|
|
1287
|
+
* @param {Object} opts.action - Action button configuration { label, onClick }
|
|
1288
|
+
* @param {boolean} opts.pauseOnHover - Pause timeout on hover
|
|
1289
|
+
* @param {string} opts.ariaLive - ARIA live region type ('polite' or 'assertive')
|
|
867
1290
|
*/
|
|
868
1291
|
show(message, type = "dark", opts = {}) {
|
|
869
1292
|
// Input validation
|
|
@@ -893,10 +1316,30 @@ class ToastifyPro {
|
|
|
893
1316
|
const options = { ...this.defaultOptions, ...opts };
|
|
894
1317
|
|
|
895
1318
|
try {
|
|
1319
|
+
// Queue management - remove oldest toasts if limit exceeded
|
|
1320
|
+
if (options.maxToasts > 0 && this.activeToasts.length >= options.maxToasts) {
|
|
1321
|
+
const toastsToRemove = this.activeToasts.length - options.maxToasts + 1;
|
|
1322
|
+
for (let i = 0; i < toastsToRemove; i++) {
|
|
1323
|
+
const oldestToast = this.activeToasts.shift();
|
|
1324
|
+
if (oldestToast && oldestToast.element) {
|
|
1325
|
+
this.removeToast(oldestToast.element);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
896
1330
|
// Create toast element
|
|
897
1331
|
const toast = document.createElement("div");
|
|
898
1332
|
toast.className = `toastify-pro ${type}`;
|
|
899
1333
|
|
|
1334
|
+
// Store reference to this instance for keyboard navigation
|
|
1335
|
+
toast._toastInstance = this;
|
|
1336
|
+
|
|
1337
|
+
// ARIA accessibility attributes
|
|
1338
|
+
const ariaLive = type === 'error' || type === 'warning' ? 'assertive' : (options.ariaLive || 'polite');
|
|
1339
|
+
toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
|
|
1340
|
+
toast.setAttribute('aria-live', ariaLive);
|
|
1341
|
+
toast.setAttribute('aria-atomic', 'true');
|
|
1342
|
+
|
|
900
1343
|
// Set duration for progress bar animation
|
|
901
1344
|
if (options.timeout > 0) {
|
|
902
1345
|
toast.style.setProperty('--duration', `${options.timeout}ms`);
|
|
@@ -905,6 +1348,7 @@ class ToastifyPro {
|
|
|
905
1348
|
// Create icon wrapper
|
|
906
1349
|
const iconWrapper = document.createElement("div");
|
|
907
1350
|
iconWrapper.className = "toast-icon";
|
|
1351
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
908
1352
|
iconWrapper.innerHTML = this.getIconSVG(type);
|
|
909
1353
|
toast.appendChild(iconWrapper);
|
|
910
1354
|
|
|
@@ -926,20 +1370,51 @@ class ToastifyPro {
|
|
|
926
1370
|
contentWrapper.appendChild(descriptionElement);
|
|
927
1371
|
}
|
|
928
1372
|
|
|
1373
|
+
// Action button support
|
|
1374
|
+
if (options.action && typeof options.action === 'object') {
|
|
1375
|
+
const actionBtn = document.createElement("button");
|
|
1376
|
+
actionBtn.className = "toast-action";
|
|
1377
|
+
actionBtn.textContent = options.action.label || 'Action';
|
|
1378
|
+
actionBtn.setAttribute('type', 'button');
|
|
1379
|
+
if (typeof options.action.onClick === 'function') {
|
|
1380
|
+
actionBtn.onclick = (e) => {
|
|
1381
|
+
e.stopPropagation();
|
|
1382
|
+
options.action.onClick({ close: () => this.removeToast(toast), event: e });
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
contentWrapper.appendChild(actionBtn);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
929
1388
|
toast.appendChild(contentWrapper);
|
|
930
1389
|
|
|
931
1390
|
// Add close button if enabled
|
|
932
1391
|
if (options.allowClose) {
|
|
933
|
-
const closeBtn = document.createElement("
|
|
1392
|
+
const closeBtn = document.createElement("button");
|
|
934
1393
|
closeBtn.className = "close-btn";
|
|
935
1394
|
closeBtn.innerHTML = "×";
|
|
1395
|
+
closeBtn.setAttribute('type', 'button');
|
|
936
1396
|
closeBtn.setAttribute('aria-label', 'Close notification');
|
|
937
1397
|
closeBtn.onclick = () => this.removeToast(toast);
|
|
938
1398
|
toast.appendChild(closeBtn);
|
|
939
1399
|
}
|
|
940
1400
|
|
|
941
|
-
// Add toast to container
|
|
942
|
-
this.container.
|
|
1401
|
+
// Add toast to container (respect newestOnTop setting)
|
|
1402
|
+
if (options.newestOnTop && this.container.firstChild) {
|
|
1403
|
+
this.container.insertBefore(toast, this.container.firstChild);
|
|
1404
|
+
} else {
|
|
1405
|
+
this.container.appendChild(toast);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// Track toast for queue management
|
|
1409
|
+
const toastData = {
|
|
1410
|
+
element: toast,
|
|
1411
|
+
timeout: null,
|
|
1412
|
+
remainingTime: options.timeout,
|
|
1413
|
+
startTime: null,
|
|
1414
|
+
isPaused: false,
|
|
1415
|
+
isRemoving: false // Flag to prevent hover interference during removal
|
|
1416
|
+
};
|
|
1417
|
+
this.activeToasts.push(toastData);
|
|
943
1418
|
|
|
944
1419
|
// Apple AirDrop-style entrance animation
|
|
945
1420
|
setTimeout(() => {
|
|
@@ -951,16 +1426,94 @@ class ToastifyPro {
|
|
|
951
1426
|
}
|
|
952
1427
|
}, 10);
|
|
953
1428
|
|
|
1429
|
+
// Pause on hover functionality
|
|
1430
|
+
if (options.pauseOnHover && options.timeout > 0) {
|
|
1431
|
+
toast.addEventListener('mouseenter', () => {
|
|
1432
|
+
// Don't pause if toast is being removed
|
|
1433
|
+
if (toastData.isRemoving) return;
|
|
1434
|
+
if (toastData.timeout) {
|
|
1435
|
+
clearTimeout(toastData.timeout);
|
|
1436
|
+
toastData.isPaused = true;
|
|
1437
|
+
toastData.remainingTime -= (Date.now() - toastData.startTime);
|
|
1438
|
+
toast.classList.add('paused');
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
toast.addEventListener('mouseleave', () => {
|
|
1443
|
+
// Don't restart timer if toast is being removed
|
|
1444
|
+
if (toastData.isRemoving) return;
|
|
1445
|
+
if (toastData.isPaused && toastData.remainingTime > 0) {
|
|
1446
|
+
toastData.isPaused = false;
|
|
1447
|
+
toastData.startTime = Date.now();
|
|
1448
|
+
toast.classList.remove('paused');
|
|
1449
|
+
// Update CSS variable for remaining progress
|
|
1450
|
+
toast.style.setProperty('--duration', `${toastData.remainingTime}ms`);
|
|
1451
|
+
// Restart the progress bar animation only (not the main toast animation)
|
|
1452
|
+
// Using class toggle to reset pseudo-element animation without affecting main element
|
|
1453
|
+
toast.classList.add('progress-restart');
|
|
1454
|
+
void toast.offsetHeight; // Force reflow
|
|
1455
|
+
toast.classList.remove('progress-restart');
|
|
1456
|
+
|
|
1457
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), toastData.remainingTime);
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
|
|
954
1462
|
// Auto-remove after timeout
|
|
955
1463
|
if (options.timeout > 0) {
|
|
956
|
-
|
|
1464
|
+
toastData.startTime = Date.now();
|
|
1465
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), options.timeout);
|
|
957
1466
|
}
|
|
958
1467
|
|
|
959
|
-
|
|
1468
|
+
// Return toast control object
|
|
1469
|
+
return {
|
|
1470
|
+
element: toast,
|
|
1471
|
+
dismiss: () => this.removeToast(toast),
|
|
1472
|
+
update: (newMessage, newOpts) => this.updateToast(toast, newMessage, newOpts)
|
|
1473
|
+
};
|
|
960
1474
|
} catch (error) {
|
|
961
1475
|
console.error('ToastifyPro: Failed to create toast:', error);
|
|
962
1476
|
}
|
|
963
1477
|
}
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* Updates an existing toast's content
|
|
1481
|
+
* @param {HTMLElement} toast - Toast element to update
|
|
1482
|
+
* @param {string} message - New message text
|
|
1483
|
+
* @param {Object} opts - Options to update
|
|
1484
|
+
*/
|
|
1485
|
+
updateToast(toast, message, opts = {}) {
|
|
1486
|
+
if (!toast || !toast.parentNode) return;
|
|
1487
|
+
|
|
1488
|
+
const messageEl = toast.querySelector('.toast-message');
|
|
1489
|
+
const descEl = toast.querySelector('.toast-description');
|
|
1490
|
+
|
|
1491
|
+
if (message && messageEl) {
|
|
1492
|
+
messageEl.textContent = message;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (opts.description && descEl) {
|
|
1496
|
+
descEl.textContent = opts.description;
|
|
1497
|
+
} else if (opts.description) {
|
|
1498
|
+
const descriptionElement = document.createElement("div");
|
|
1499
|
+
descriptionElement.className = "toast-description";
|
|
1500
|
+
descriptionElement.textContent = opts.description;
|
|
1501
|
+
toast.querySelector('.toast-content')?.appendChild(descriptionElement);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// Update type/style if provided
|
|
1505
|
+
if (opts.type) {
|
|
1506
|
+
const validTypes = ['success', 'error', 'info', 'warning', 'dark', 'light'];
|
|
1507
|
+
if (validTypes.includes(opts.type)) {
|
|
1508
|
+
validTypes.forEach(t => toast.classList.remove(t));
|
|
1509
|
+
toast.classList.add(opts.type);
|
|
1510
|
+
const iconWrapper = toast.querySelector('.toast-icon');
|
|
1511
|
+
if (iconWrapper) {
|
|
1512
|
+
iconWrapper.innerHTML = this.getIconSVG(opts.type);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
964
1517
|
|
|
965
1518
|
/**
|
|
966
1519
|
* Removes a toast with position-aware car swipe animation
|
|
@@ -973,6 +1526,25 @@ class ToastifyPro {
|
|
|
973
1526
|
}
|
|
974
1527
|
|
|
975
1528
|
try {
|
|
1529
|
+
// Remove from active toasts tracking and set removal flag
|
|
1530
|
+
const toastIndex = this.activeToasts.findIndex(t => t.element === toast);
|
|
1531
|
+
if (toastIndex > -1) {
|
|
1532
|
+
const toastData = this.activeToasts[toastIndex];
|
|
1533
|
+
// Prevent hover events from interfering during removal
|
|
1534
|
+
toastData.isRemoving = true;
|
|
1535
|
+
if (toastData.timeout) {
|
|
1536
|
+
clearTimeout(toastData.timeout);
|
|
1537
|
+
}
|
|
1538
|
+
this.activeToasts.splice(toastIndex, 1);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// Mark the toast element as removing to prevent double-removal
|
|
1542
|
+
if (toast.dataset.removing === 'true') return;
|
|
1543
|
+
toast.dataset.removing = 'true';
|
|
1544
|
+
|
|
1545
|
+
// Disable pointer events during exit animation to prevent hover issues
|
|
1546
|
+
toast.style.pointerEvents = 'none';
|
|
1547
|
+
|
|
976
1548
|
// Detect position to choose the right swipe direction
|
|
977
1549
|
const container = toast.parentNode;
|
|
978
1550
|
const position = container.className.split(' ')[1]; // get position class
|
|
@@ -993,12 +1565,12 @@ class ToastifyPro {
|
|
|
993
1565
|
}
|
|
994
1566
|
|
|
995
1567
|
// Apply fast car swipe animation with improved easing
|
|
996
|
-
toast.style.animation = `${swipeAnimation} 0.
|
|
1568
|
+
toast.style.animation = `${swipeAnimation} 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards`;
|
|
997
1569
|
|
|
998
1570
|
// Add spinning icon animation for extra polish
|
|
999
1571
|
const icon = toast.querySelector('.toast-icon');
|
|
1000
1572
|
if (icon) {
|
|
1001
|
-
icon.style.animation = 'iconCarExit 0.
|
|
1573
|
+
icon.style.animation = 'iconCarExit 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards';
|
|
1002
1574
|
}
|
|
1003
1575
|
|
|
1004
1576
|
// Remove element after animation completes
|
|
@@ -1006,7 +1578,7 @@ class ToastifyPro {
|
|
|
1006
1578
|
if (toast.parentNode) {
|
|
1007
1579
|
toast.remove();
|
|
1008
1580
|
}
|
|
1009
|
-
},
|
|
1581
|
+
}, 350);
|
|
1010
1582
|
} catch (error) {
|
|
1011
1583
|
console.error('ToastifyPro: Error removing toast:', error);
|
|
1012
1584
|
// Fallback: remove immediately if animation fails
|
|
@@ -1015,6 +1587,33 @@ class ToastifyPro {
|
|
|
1015
1587
|
}
|
|
1016
1588
|
}
|
|
1017
1589
|
}
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* Dismisses all active toasts
|
|
1593
|
+
* @param {string} type - Optional: only dismiss toasts of this type
|
|
1594
|
+
*/
|
|
1595
|
+
dismissAll(type = null) {
|
|
1596
|
+
const toastsCopy = [...this.activeToasts];
|
|
1597
|
+
toastsCopy.forEach(toastData => {
|
|
1598
|
+
if (toastData.element) {
|
|
1599
|
+
if (type) {
|
|
1600
|
+
if (toastData.element.classList.contains(type)) {
|
|
1601
|
+
this.removeToast(toastData.element);
|
|
1602
|
+
}
|
|
1603
|
+
} else {
|
|
1604
|
+
this.removeToast(toastData.element);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* Gets the count of active toasts
|
|
1612
|
+
* @returns {number} Number of active toasts
|
|
1613
|
+
*/
|
|
1614
|
+
getActiveCount() {
|
|
1615
|
+
return this.activeToasts.length;
|
|
1616
|
+
}
|
|
1018
1617
|
|
|
1019
1618
|
/**
|
|
1020
1619
|
* Shows a success toast notification
|
|
@@ -1189,6 +1788,14 @@ class ToastifyPro {
|
|
|
1189
1788
|
const toast = document.createElement("div");
|
|
1190
1789
|
toast.className = `toastify-pro custom${options.customTextLight ? ' light-text' : ''}`;
|
|
1191
1790
|
|
|
1791
|
+
// Store reference to this instance
|
|
1792
|
+
toast._toastInstance = this;
|
|
1793
|
+
|
|
1794
|
+
// ARIA accessibility attributes
|
|
1795
|
+
toast.setAttribute('role', 'status');
|
|
1796
|
+
toast.setAttribute('aria-live', options.ariaLive || 'polite');
|
|
1797
|
+
toast.setAttribute('aria-atomic', 'true');
|
|
1798
|
+
|
|
1192
1799
|
// Apply custom gradient
|
|
1193
1800
|
if (options.customGradient) {
|
|
1194
1801
|
toast.style.background = options.customGradient;
|
|
@@ -1201,6 +1808,7 @@ class ToastifyPro {
|
|
|
1201
1808
|
// Create icon wrapper
|
|
1202
1809
|
const iconWrapper = document.createElement("div");
|
|
1203
1810
|
iconWrapper.className = "toast-icon";
|
|
1811
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
1204
1812
|
iconWrapper.innerHTML = this.getIconSVG('success'); // Use success icon for custom
|
|
1205
1813
|
toast.appendChild(iconWrapper);
|
|
1206
1814
|
|
|
@@ -1223,15 +1831,53 @@ class ToastifyPro {
|
|
|
1223
1831
|
toast.appendChild(contentWrapper);
|
|
1224
1832
|
|
|
1225
1833
|
if (options.allowClose) {
|
|
1226
|
-
const closeBtn = document.createElement("
|
|
1834
|
+
const closeBtn = document.createElement("button");
|
|
1227
1835
|
closeBtn.className = "close-btn";
|
|
1228
1836
|
closeBtn.innerHTML = "×";
|
|
1837
|
+
closeBtn.setAttribute('type', 'button');
|
|
1229
1838
|
closeBtn.setAttribute('aria-label', 'Close notification');
|
|
1230
1839
|
closeBtn.onclick = () => this.removeToast(toast);
|
|
1231
1840
|
toast.appendChild(closeBtn);
|
|
1232
1841
|
}
|
|
1233
1842
|
|
|
1234
|
-
|
|
1843
|
+
// Add toast to container (respect newestOnTop setting)
|
|
1844
|
+
if (options.newestOnTop && this.container.firstChild) {
|
|
1845
|
+
this.container.insertBefore(toast, this.container.firstChild);
|
|
1846
|
+
} else {
|
|
1847
|
+
this.container.appendChild(toast);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Track toast for queue management
|
|
1851
|
+
const toastData = {
|
|
1852
|
+
element: toast,
|
|
1853
|
+
timeout: null,
|
|
1854
|
+
remainingTime: options.timeout,
|
|
1855
|
+
startTime: null,
|
|
1856
|
+
isPaused: false
|
|
1857
|
+
};
|
|
1858
|
+
this.activeToasts.push(toastData);
|
|
1859
|
+
|
|
1860
|
+
// Pause on hover functionality
|
|
1861
|
+
if (options.pauseOnHover && options.timeout > 0) {
|
|
1862
|
+
toast.addEventListener('mouseenter', () => {
|
|
1863
|
+
if (toastData.timeout) {
|
|
1864
|
+
clearTimeout(toastData.timeout);
|
|
1865
|
+
toastData.isPaused = true;
|
|
1866
|
+
toastData.remainingTime -= (Date.now() - toastData.startTime);
|
|
1867
|
+
toast.classList.add('paused');
|
|
1868
|
+
}
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1871
|
+
toast.addEventListener('mouseleave', () => {
|
|
1872
|
+
if (toastData.isPaused && toastData.remainingTime > 0) {
|
|
1873
|
+
toastData.isPaused = false;
|
|
1874
|
+
toastData.startTime = Date.now();
|
|
1875
|
+
toast.classList.remove('paused');
|
|
1876
|
+
toast.style.setProperty('--duration', `${toastData.remainingTime}ms`);
|
|
1877
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), toastData.remainingTime);
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1235
1881
|
|
|
1236
1882
|
setTimeout(() => {
|
|
1237
1883
|
toast.classList.add("show");
|
|
@@ -1242,10 +1888,15 @@ class ToastifyPro {
|
|
|
1242
1888
|
}, 10);
|
|
1243
1889
|
|
|
1244
1890
|
if (options.timeout > 0) {
|
|
1245
|
-
|
|
1891
|
+
toastData.startTime = Date.now();
|
|
1892
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), options.timeout);
|
|
1246
1893
|
}
|
|
1247
1894
|
|
|
1248
|
-
return
|
|
1895
|
+
return {
|
|
1896
|
+
element: toast,
|
|
1897
|
+
dismiss: () => this.removeToast(toast),
|
|
1898
|
+
update: (newMessage, newOpts) => this.updateToast(toast, newMessage, newOpts)
|
|
1899
|
+
};
|
|
1249
1900
|
} catch (error) {
|
|
1250
1901
|
console.error('ToastifyPro: Failed to create custom toast:', error);
|
|
1251
1902
|
}
|
|
@@ -1555,9 +2206,10 @@ class ToastifyPro {
|
|
|
1555
2206
|
}
|
|
1556
2207
|
|
|
1557
2208
|
// Create close button for confirmation
|
|
1558
|
-
const closeBtn = document.createElement("
|
|
2209
|
+
const closeBtn = document.createElement("button");
|
|
1559
2210
|
closeBtn.className = "conf-close-btn";
|
|
1560
2211
|
closeBtn.innerHTML = "×";
|
|
2212
|
+
closeBtn.setAttribute('type', 'button');
|
|
1561
2213
|
closeBtn.setAttribute('aria-label', 'Cancel confirmation');
|
|
1562
2214
|
closeBtn.onclick = () => {
|
|
1563
2215
|
if (!isLoading) {
|
|
@@ -1573,6 +2225,7 @@ class ToastifyPro {
|
|
|
1573
2225
|
// Create icon wrapper
|
|
1574
2226
|
const iconWrapper = document.createElement("div");
|
|
1575
2227
|
iconWrapper.className = "toast-icon";
|
|
2228
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
1576
2229
|
iconWrapper.innerHTML = this.getIconSVG('info'); // Default to info icon
|
|
1577
2230
|
if (confirmOptions.primaryColor) {
|
|
1578
2231
|
iconWrapper.style.color = textColor;
|
|
@@ -1593,8 +2246,9 @@ class ToastifyPro {
|
|
|
1593
2246
|
contentWrapper.appendChild(messageElement);
|
|
1594
2247
|
|
|
1595
2248
|
// Optional description
|
|
2249
|
+
let descriptionElement = null;
|
|
1596
2250
|
if (description) {
|
|
1597
|
-
|
|
2251
|
+
descriptionElement = document.createElement("div");
|
|
1598
2252
|
descriptionElement.className = "toast-description";
|
|
1599
2253
|
descriptionElement.textContent = description.substring(0, this.defaultOptions.maxLength * 2);
|
|
1600
2254
|
if (confirmOptions.primaryColor) {
|
|
@@ -1612,6 +2266,7 @@ class ToastifyPro {
|
|
|
1612
2266
|
// Cancel button
|
|
1613
2267
|
const cancelBtn = document.createElement("button");
|
|
1614
2268
|
cancelBtn.className = "toast-btn toast-btn-cancel";
|
|
2269
|
+
cancelBtn.setAttribute('type', 'button');
|
|
1615
2270
|
cancelBtn.textContent = confirmOptions.cancelText;
|
|
1616
2271
|
cancelBtn.onclick = () => {
|
|
1617
2272
|
if (!isLoading) {
|
|
@@ -1633,18 +2288,25 @@ class ToastifyPro {
|
|
|
1633
2288
|
// Confirm button
|
|
1634
2289
|
const confirmBtn = document.createElement("button");
|
|
1635
2290
|
confirmBtn.className = `toast-btn toast-btn-confirm`;
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
const spinner = document.createElement("span");
|
|
1639
|
-
spinner.className = "btn-spinner";
|
|
1640
|
-
confirmBtn.appendChild(spinner);
|
|
1641
|
-
|
|
2291
|
+
confirmBtn.setAttribute('type', 'button');
|
|
2292
|
+
|
|
1642
2293
|
// Create text wrapper
|
|
1643
2294
|
const textWrapper = document.createElement("span");
|
|
1644
2295
|
textWrapper.className = "btn-text";
|
|
1645
2296
|
textWrapper.textContent = confirmOptions.confirmText;
|
|
1646
2297
|
confirmBtn.appendChild(textWrapper);
|
|
1647
2298
|
|
|
2299
|
+
// Create spinner element with custom SVG
|
|
2300
|
+
const spinner = document.createElement("span");
|
|
2301
|
+
spinner.className = "btn-spinner";
|
|
2302
|
+
spinner.innerHTML = `
|
|
2303
|
+
<svg width="25" height="25" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2304
|
+
<path d="M9.5 2.9375V5.5625M9.5 13.4375V16.0625M2.9375 9.5H5.5625M13.4375 9.5H16.0625" stroke="currentColor" stroke-width="1.875" stroke-linecap="round" />
|
|
2305
|
+
<path d="M4.86011 4.85961L6.71627 6.71577M12.2847 12.2842L14.1409 14.1404M4.86011 14.1404L6.71627 12.2842M12.2847 6.71577L14.1409 4.85961" stroke="currentColor" stroke-width="1.875" stroke-linecap="round" />
|
|
2306
|
+
</svg>
|
|
2307
|
+
`;
|
|
2308
|
+
confirmBtn.appendChild(spinner);
|
|
2309
|
+
|
|
1648
2310
|
confirmBtn.onclick = () => {
|
|
1649
2311
|
if (!isLoading) {
|
|
1650
2312
|
handleConfirmation(true);
|
|
@@ -1695,6 +2357,53 @@ class ToastifyPro {
|
|
|
1695
2357
|
setLoading(true);
|
|
1696
2358
|
}
|
|
1697
2359
|
|
|
2360
|
+
// ARIA accessibility for confirmation dialog
|
|
2361
|
+
toast.setAttribute('role', 'alertdialog');
|
|
2362
|
+
toast.setAttribute('aria-modal', 'true');
|
|
2363
|
+
toast.setAttribute('aria-labelledby', 'toast-conf-title');
|
|
2364
|
+
if (description) {
|
|
2365
|
+
toast.setAttribute('aria-describedby', 'toast-conf-desc');
|
|
2366
|
+
}
|
|
2367
|
+
messageElement.id = 'toast-conf-title';
|
|
2368
|
+
if (description && descriptionElement) {
|
|
2369
|
+
descriptionElement.id = 'toast-conf-desc';
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// Store previously focused element for restoration
|
|
2373
|
+
const previouslyFocused = document.activeElement;
|
|
2374
|
+
|
|
2375
|
+
// Focus trap for confirmation dialog
|
|
2376
|
+
const focusableElements = [cancelBtn, confirmBtn, closeBtn].filter(Boolean);
|
|
2377
|
+
let currentFocusIndex = 0;
|
|
2378
|
+
|
|
2379
|
+
const handleTabKey = (e) => {
|
|
2380
|
+
if (e.key === 'Tab' && toastElement && toastElement.parentNode) {
|
|
2381
|
+
e.preventDefault();
|
|
2382
|
+
if (e.shiftKey) {
|
|
2383
|
+
currentFocusIndex = (currentFocusIndex - 1 + focusableElements.length) % focusableElements.length;
|
|
2384
|
+
} else {
|
|
2385
|
+
currentFocusIndex = (currentFocusIndex + 1) % focusableElements.length;
|
|
2386
|
+
}
|
|
2387
|
+
focusableElements[currentFocusIndex]?.focus();
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
|
|
2391
|
+
document.addEventListener('keydown', handleTabKey);
|
|
2392
|
+
|
|
2393
|
+
// Store cleanup function
|
|
2394
|
+
const originalClose = closeConfirmation;
|
|
2395
|
+
const cleanupAndClose = () => {
|
|
2396
|
+
document.removeEventListener('keydown', handleTabKey);
|
|
2397
|
+
// Restore focus to previously focused element
|
|
2398
|
+
if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
|
|
2399
|
+
setTimeout(() => previouslyFocused.focus(), 100);
|
|
2400
|
+
}
|
|
2401
|
+
originalClose();
|
|
2402
|
+
};
|
|
2403
|
+
|
|
2404
|
+
// Update control object with enhanced close
|
|
2405
|
+
controlObject.close = cleanupAndClose;
|
|
2406
|
+
|
|
1698
2407
|
// Entrance animation
|
|
1699
2408
|
setTimeout(() => {
|
|
1700
2409
|
toast.classList.add("show");
|
|
@@ -1702,6 +2411,11 @@ class ToastifyPro {
|
|
|
1702
2411
|
if (icon) {
|
|
1703
2412
|
icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
|
1704
2413
|
}
|
|
2414
|
+
|
|
2415
|
+
// Focus the confirm button after animation
|
|
2416
|
+
setTimeout(() => {
|
|
2417
|
+
confirmBtn.focus();
|
|
2418
|
+
}, 100);
|
|
1705
2419
|
}, 10);
|
|
1706
2420
|
|
|
1707
2421
|
// Return control object with toast element and control functions
|
|
@@ -1720,6 +2434,527 @@ class ToastifyPro {
|
|
|
1720
2434
|
confirm(message, descriptionOrCallback, callback) {
|
|
1721
2435
|
return this.conf(message, descriptionOrCallback, callback);
|
|
1722
2436
|
}
|
|
2437
|
+
|
|
2438
|
+
/**
|
|
2439
|
+
* Shows an input prompt toast with a text field
|
|
2440
|
+
* @param {string} message - Main prompt question
|
|
2441
|
+
* @param {string|Function|Object} descriptionOrCallback - Description text, callback function, or options object
|
|
2442
|
+
* @param {Function} callback - Callback function (if description provided)
|
|
2443
|
+
* @returns {Object} Control object with element, close, setValue, getValue methods
|
|
2444
|
+
*
|
|
2445
|
+
* Options object:
|
|
2446
|
+
* - description: {string} Optional description text
|
|
2447
|
+
* - placeholder: {string} Input placeholder text (default: 'Enter your response...')
|
|
2448
|
+
* - submitText: {string} Submit button text (default: 'Submit')
|
|
2449
|
+
* - cancelText: {string} Cancel button text (default: 'Cancel')
|
|
2450
|
+
* - defaultValue: {string} Default input value
|
|
2451
|
+
* - required: {boolean} Whether input is required (default: true)
|
|
2452
|
+
* - type: {string} Input type: 'text', 'email', 'number', 'password', 'url' (default: 'text')
|
|
2453
|
+
* - validate: {Function} Custom validation function (receives value, returns true or error string)
|
|
2454
|
+
* - onSubmit: {Function} Called when user submits (receives value, control object)
|
|
2455
|
+
* - onCancel: {Function} Called when user cancels
|
|
2456
|
+
* - theme: {string} Toast theme: 'dark' or 'light' (default: 'dark')
|
|
2457
|
+
* - position: {string} Override default position
|
|
2458
|
+
* - overlay: {boolean} Show overlay behind toast (default: true)
|
|
2459
|
+
* - primaryColor: {string} Custom primary color
|
|
2460
|
+
* - secondaryColor: {string} Custom secondary color for gradient
|
|
2461
|
+
*
|
|
2462
|
+
* @example
|
|
2463
|
+
* // Simple usage
|
|
2464
|
+
* toast.input('What is your name?', (value) => console.log(value));
|
|
2465
|
+
*
|
|
2466
|
+
* // With description
|
|
2467
|
+
* toast.input('Enter your email', 'We will send you updates', (value) => {...});
|
|
2468
|
+
*
|
|
2469
|
+
* // Full options
|
|
2470
|
+
* toast.input('Enter password', {
|
|
2471
|
+
* type: 'password',
|
|
2472
|
+
* placeholder: 'Minimum 8 characters',
|
|
2473
|
+
* validate: (val) => val.length >= 8 || 'Password must be at least 8 characters',
|
|
2474
|
+
* onSubmit: (value) => { ... },
|
|
2475
|
+
* theme: 'light'
|
|
2476
|
+
* });
|
|
2477
|
+
*/
|
|
2478
|
+
input(message, descriptionOrCallback, callback) {
|
|
2479
|
+
try {
|
|
2480
|
+
// Parse arguments like confirm method
|
|
2481
|
+
let description = '';
|
|
2482
|
+
let options = {};
|
|
2483
|
+
let resultCallback = null;
|
|
2484
|
+
|
|
2485
|
+
if (typeof descriptionOrCallback === 'string') {
|
|
2486
|
+
description = descriptionOrCallback;
|
|
2487
|
+
if (typeof callback === 'function') {
|
|
2488
|
+
resultCallback = callback;
|
|
2489
|
+
} else if (typeof callback === 'object') {
|
|
2490
|
+
options = callback || {};
|
|
2491
|
+
resultCallback = options.onSubmit || null;
|
|
2492
|
+
}
|
|
2493
|
+
} else if (typeof descriptionOrCallback === 'function') {
|
|
2494
|
+
resultCallback = descriptionOrCallback;
|
|
2495
|
+
} else if (typeof descriptionOrCallback === 'object') {
|
|
2496
|
+
options = descriptionOrCallback || {};
|
|
2497
|
+
description = options.description || '';
|
|
2498
|
+
resultCallback = options.onSubmit || null;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
// Default options
|
|
2502
|
+
const inputOptions = {
|
|
2503
|
+
placeholder: options.placeholder || 'Enter your response...',
|
|
2504
|
+
submitText: options.submitText || 'Submit',
|
|
2505
|
+
cancelText: options.cancelText || 'Cancel',
|
|
2506
|
+
defaultValue: options.defaultValue || '',
|
|
2507
|
+
required: options.required !== false, // default true
|
|
2508
|
+
type: options.type || 'text',
|
|
2509
|
+
validate: options.validate || null,
|
|
2510
|
+
onCancel: options.onCancel || null,
|
|
2511
|
+
theme: options.theme || 'dark',
|
|
2512
|
+
position: options.position || this.defaultOptions.position,
|
|
2513
|
+
overlay: options.overlay !== false, // default true
|
|
2514
|
+
primaryColor: options.primaryColor || null,
|
|
2515
|
+
secondaryColor: options.secondaryColor || null,
|
|
2516
|
+
};
|
|
2517
|
+
|
|
2518
|
+
// Validate input type
|
|
2519
|
+
const validTypes = ['text', 'email', 'number', 'password', 'url', 'tel'];
|
|
2520
|
+
if (!validTypes.includes(inputOptions.type)) {
|
|
2521
|
+
inputOptions.type = 'text';
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// Create or get container for the specified position
|
|
2525
|
+
let inputContainer;
|
|
2526
|
+
const positionClass = inputOptions.position.replace(' ', '-');
|
|
2527
|
+
const existingContainer = document.querySelector(
|
|
2528
|
+
`.toastify-pro-container.${positionClass}`
|
|
2529
|
+
);
|
|
2530
|
+
|
|
2531
|
+
if (existingContainer) {
|
|
2532
|
+
inputContainer = existingContainer;
|
|
2533
|
+
} else {
|
|
2534
|
+
inputContainer = document.createElement("div");
|
|
2535
|
+
inputContainer.className = `toastify-pro-container ${positionClass}`;
|
|
2536
|
+
document.body.appendChild(inputContainer);
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// Create overlay if enabled
|
|
2540
|
+
let overlay = null;
|
|
2541
|
+
if (inputOptions.overlay) {
|
|
2542
|
+
overlay = document.createElement("div");
|
|
2543
|
+
overlay.className = "toastify-pro-overlay";
|
|
2544
|
+
document.body.appendChild(overlay);
|
|
2545
|
+
setTimeout(() => overlay.classList.add("visible"), 10);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// Create input toast element
|
|
2549
|
+
const toast = document.createElement("div");
|
|
2550
|
+
toast.className = `toastify-pro input-toast ${inputOptions.theme}`;
|
|
2551
|
+
toast.setAttribute('role', 'dialog');
|
|
2552
|
+
toast.setAttribute('aria-modal', 'true');
|
|
2553
|
+
toast.setAttribute('aria-labelledby', 'toast-input-title');
|
|
2554
|
+
|
|
2555
|
+
// Track state
|
|
2556
|
+
let isLoading = false;
|
|
2557
|
+
let isClosed = false;
|
|
2558
|
+
let inputElement = null;
|
|
2559
|
+
let submitBtnElement = null;
|
|
2560
|
+
let cancelBtnElement = null;
|
|
2561
|
+
let errorElement = null;
|
|
2562
|
+
|
|
2563
|
+
// Helper: Check if a color is light or dark
|
|
2564
|
+
const isLightColor = (color) => {
|
|
2565
|
+
if (!color) return false;
|
|
2566
|
+
const hex = color.replace('#', '');
|
|
2567
|
+
if (hex.length === 3) {
|
|
2568
|
+
const r = parseInt(hex[0] + hex[0], 16);
|
|
2569
|
+
const g = parseInt(hex[1] + hex[1], 16);
|
|
2570
|
+
const b = parseInt(hex[2] + hex[2], 16);
|
|
2571
|
+
return (r * 299 + g * 587 + b * 114) / 1000 > 128;
|
|
2572
|
+
}
|
|
2573
|
+
const r = parseInt(hex.substr(0, 2), 16);
|
|
2574
|
+
const g = parseInt(hex.substr(2, 2), 16);
|
|
2575
|
+
const b = parseInt(hex.substr(4, 2), 16);
|
|
2576
|
+
return (r * 299 + g * 587 + b * 114) / 1000 > 128;
|
|
2577
|
+
};
|
|
2578
|
+
|
|
2579
|
+
// Apply custom colors
|
|
2580
|
+
let textColor = inputOptions.theme === 'light' ? '#1e293b' : 'white';
|
|
2581
|
+
if (inputOptions.primaryColor) {
|
|
2582
|
+
const primary = inputOptions.primaryColor;
|
|
2583
|
+
const secondary = inputOptions.secondaryColor;
|
|
2584
|
+
|
|
2585
|
+
if (secondary) {
|
|
2586
|
+
toast.style.background = `linear-gradient(135deg, ${primary} 0%, ${secondary} 100%)`;
|
|
2587
|
+
} else {
|
|
2588
|
+
toast.style.background = `linear-gradient(135deg, ${primary} 0%, ${primary}dd 100%)`;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
textColor = isLightColor(primary) ? '#1e293b' : 'white';
|
|
2592
|
+
toast.style.color = textColor;
|
|
2593
|
+
|
|
2594
|
+
const borderOpacity = isLightColor(primary) ? '0.2' : '0.15';
|
|
2595
|
+
toast.style.borderColor = `rgba(255, 255, 255, ${borderOpacity})`;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
// Set loading state
|
|
2599
|
+
const setLoading = (loading) => {
|
|
2600
|
+
isLoading = loading;
|
|
2601
|
+
if (submitBtnElement) {
|
|
2602
|
+
if (loading) {
|
|
2603
|
+
submitBtnElement.classList.add('loading');
|
|
2604
|
+
} else {
|
|
2605
|
+
submitBtnElement.classList.remove('loading');
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
if (cancelBtnElement) {
|
|
2609
|
+
cancelBtnElement.disabled = loading;
|
|
2610
|
+
cancelBtnElement.style.opacity = loading ? '0.5' : '1';
|
|
2611
|
+
cancelBtnElement.style.cursor = loading ? 'not-allowed' : 'pointer';
|
|
2612
|
+
}
|
|
2613
|
+
if (inputElement) {
|
|
2614
|
+
inputElement.disabled = loading;
|
|
2615
|
+
inputElement.style.opacity = loading ? '0.6' : '1';
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
|
|
2619
|
+
// Close the input toast
|
|
2620
|
+
const closeInput = () => {
|
|
2621
|
+
if (isClosed) return;
|
|
2622
|
+
isClosed = true;
|
|
2623
|
+
|
|
2624
|
+
// Remove overlay
|
|
2625
|
+
if (overlay) {
|
|
2626
|
+
overlay.classList.remove("visible");
|
|
2627
|
+
setTimeout(() => overlay.remove(), 300);
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
// Exit animation
|
|
2631
|
+
toast.style.pointerEvents = 'none';
|
|
2632
|
+
toast.style.animation = 'carSwipeCenter 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards';
|
|
2633
|
+
|
|
2634
|
+
const icon = toast.querySelector('.toast-icon');
|
|
2635
|
+
if (icon) {
|
|
2636
|
+
icon.style.animation = 'iconCarExit 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards';
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
setTimeout(() => {
|
|
2640
|
+
if (toast.parentNode) {
|
|
2641
|
+
toast.remove();
|
|
2642
|
+
}
|
|
2643
|
+
}, 350);
|
|
2644
|
+
|
|
2645
|
+
// Restore focus
|
|
2646
|
+
if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
|
|
2647
|
+
setTimeout(() => previouslyFocused.focus(), 100);
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
// Cleanup keyboard handler
|
|
2651
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2654
|
+
// Validate input
|
|
2655
|
+
const validateInput = (value) => {
|
|
2656
|
+
// Required check
|
|
2657
|
+
if (inputOptions.required && !value.trim()) {
|
|
2658
|
+
return 'This field is required';
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
// Type-specific validation
|
|
2662
|
+
if (value.trim()) {
|
|
2663
|
+
if (inputOptions.type === 'email') {
|
|
2664
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2665
|
+
if (!emailRegex.test(value)) {
|
|
2666
|
+
return 'Please enter a valid email address';
|
|
2667
|
+
}
|
|
2668
|
+
} else if (inputOptions.type === 'url') {
|
|
2669
|
+
try {
|
|
2670
|
+
new URL(value);
|
|
2671
|
+
} catch {
|
|
2672
|
+
return 'Please enter a valid URL';
|
|
2673
|
+
}
|
|
2674
|
+
} else if (inputOptions.type === 'number') {
|
|
2675
|
+
if (isNaN(Number(value))) {
|
|
2676
|
+
return 'Please enter a valid number';
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
// Custom validation
|
|
2682
|
+
if (inputOptions.validate) {
|
|
2683
|
+
const customResult = inputOptions.validate(value);
|
|
2684
|
+
if (customResult !== true && customResult) {
|
|
2685
|
+
return customResult;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
return null; // Valid
|
|
2690
|
+
};
|
|
2691
|
+
|
|
2692
|
+
// Show error message
|
|
2693
|
+
const showError = (message) => {
|
|
2694
|
+
if (errorElement) {
|
|
2695
|
+
errorElement.textContent = message;
|
|
2696
|
+
errorElement.classList.add('visible');
|
|
2697
|
+
inputElement.classList.add('error');
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
|
|
2701
|
+
// Clear error message
|
|
2702
|
+
const clearError = () => {
|
|
2703
|
+
if (errorElement) {
|
|
2704
|
+
errorElement.classList.remove('visible');
|
|
2705
|
+
inputElement.classList.remove('error');
|
|
2706
|
+
}
|
|
2707
|
+
};
|
|
2708
|
+
|
|
2709
|
+
// Handle submit
|
|
2710
|
+
const handleSubmit = async () => {
|
|
2711
|
+
if (isLoading || isClosed) return;
|
|
2712
|
+
|
|
2713
|
+
const value = inputElement.value;
|
|
2714
|
+
const error = validateInput(value);
|
|
2715
|
+
|
|
2716
|
+
if (error) {
|
|
2717
|
+
showError(error);
|
|
2718
|
+
inputElement.focus();
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
clearError();
|
|
2723
|
+
|
|
2724
|
+
if (resultCallback) {
|
|
2725
|
+
const result = resultCallback(value, { setLoading, close: closeInput, setValue: (v) => inputElement.value = v });
|
|
2726
|
+
|
|
2727
|
+
// Handle async callbacks
|
|
2728
|
+
if (result && typeof result.then === 'function') {
|
|
2729
|
+
setLoading(true);
|
|
2730
|
+
try {
|
|
2731
|
+
await result;
|
|
2732
|
+
if (!isClosed) {
|
|
2733
|
+
closeInput();
|
|
2734
|
+
}
|
|
2735
|
+
} catch (err) {
|
|
2736
|
+
setLoading(false);
|
|
2737
|
+
if (err && typeof err === 'string') {
|
|
2738
|
+
showError(err);
|
|
2739
|
+
} else if (err && err.message) {
|
|
2740
|
+
showError(err.message);
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
} else if (!isLoading) {
|
|
2744
|
+
closeInput();
|
|
2745
|
+
}
|
|
2746
|
+
} else {
|
|
2747
|
+
closeInput();
|
|
2748
|
+
}
|
|
2749
|
+
};
|
|
2750
|
+
|
|
2751
|
+
// Handle cancel
|
|
2752
|
+
const handleCancel = () => {
|
|
2753
|
+
if (isLoading || isClosed) return;
|
|
2754
|
+
|
|
2755
|
+
if (inputOptions.onCancel) {
|
|
2756
|
+
inputOptions.onCancel();
|
|
2757
|
+
}
|
|
2758
|
+
closeInput();
|
|
2759
|
+
};
|
|
2760
|
+
|
|
2761
|
+
// Store previously focused element
|
|
2762
|
+
const previouslyFocused = document.activeElement;
|
|
2763
|
+
|
|
2764
|
+
// Keyboard handler
|
|
2765
|
+
const handleKeyDown = (e) => {
|
|
2766
|
+
if (isClosed) return;
|
|
2767
|
+
|
|
2768
|
+
if (e.key === 'Escape' && !isLoading) {
|
|
2769
|
+
handleCancel();
|
|
2770
|
+
} else if (e.key === 'Enter' && e.target === inputElement && !isLoading) {
|
|
2771
|
+
e.preventDefault();
|
|
2772
|
+
handleSubmit();
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
|
|
2776
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
2777
|
+
|
|
2778
|
+
// Build toast content
|
|
2779
|
+
// Header with icon and content
|
|
2780
|
+
const headerWrapper = document.createElement("div");
|
|
2781
|
+
headerWrapper.className = "toast-input-header";
|
|
2782
|
+
|
|
2783
|
+
// Icon
|
|
2784
|
+
const iconWrapper = document.createElement("div");
|
|
2785
|
+
iconWrapper.className = "toast-icon";
|
|
2786
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
2787
|
+
iconWrapper.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2788
|
+
<path d="M11 4H4C3.46957 4 2.96086 4.21071 2.58579 4.58579C2.21071 4.96086 2 5.46957 2 6V20C2 20.5304 2.21071 21.0391 2.58579 21.4142C2.96086 21.7893 3.46957 22 4 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
2789
|
+
<path d="M18.5 2.50001C18.8978 2.10219 19.4374 1.87869 20 1.87869C20.5626 1.87869 21.1022 2.10219 21.5 2.50001C21.8978 2.89784 22.1213 3.43739 22.1213 4.00001C22.1213 4.56262 21.8978 5.10219 21.5 5.50001L12 15L8 16L9 12L18.5 2.50001Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
2790
|
+
</svg>`;
|
|
2791
|
+
if (inputOptions.primaryColor) {
|
|
2792
|
+
iconWrapper.style.color = textColor;
|
|
2793
|
+
}
|
|
2794
|
+
headerWrapper.appendChild(iconWrapper);
|
|
2795
|
+
|
|
2796
|
+
// Content wrapper
|
|
2797
|
+
const contentWrapper = document.createElement("div");
|
|
2798
|
+
contentWrapper.className = "toast-content";
|
|
2799
|
+
|
|
2800
|
+
// Message
|
|
2801
|
+
const messageElement = document.createElement("div");
|
|
2802
|
+
messageElement.className = "toast-message";
|
|
2803
|
+
messageElement.id = "toast-input-title";
|
|
2804
|
+
messageElement.textContent = message.substring(0, this.defaultOptions.maxLength);
|
|
2805
|
+
if (inputOptions.primaryColor) {
|
|
2806
|
+
messageElement.style.color = textColor;
|
|
2807
|
+
}
|
|
2808
|
+
contentWrapper.appendChild(messageElement);
|
|
2809
|
+
|
|
2810
|
+
// Description
|
|
2811
|
+
if (description) {
|
|
2812
|
+
const descriptionElement = document.createElement("div");
|
|
2813
|
+
descriptionElement.className = "toast-description";
|
|
2814
|
+
descriptionElement.id = "toast-input-desc";
|
|
2815
|
+
descriptionElement.textContent = description.substring(0, this.defaultOptions.maxLength * 2);
|
|
2816
|
+
if (inputOptions.primaryColor) {
|
|
2817
|
+
descriptionElement.style.color = textColor;
|
|
2818
|
+
}
|
|
2819
|
+
contentWrapper.appendChild(descriptionElement);
|
|
2820
|
+
toast.setAttribute('aria-describedby', 'toast-input-desc');
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
headerWrapper.appendChild(contentWrapper);
|
|
2824
|
+
|
|
2825
|
+
// Input wrapper
|
|
2826
|
+
const inputWrapper = document.createElement("div");
|
|
2827
|
+
inputWrapper.className = "toast-input-wrapper";
|
|
2828
|
+
inputWrapper.appendChild(headerWrapper);
|
|
2829
|
+
|
|
2830
|
+
// Input field
|
|
2831
|
+
inputElement = document.createElement("input");
|
|
2832
|
+
inputElement.className = "toast-input";
|
|
2833
|
+
inputElement.type = inputOptions.type;
|
|
2834
|
+
inputElement.placeholder = inputOptions.placeholder;
|
|
2835
|
+
inputElement.value = inputOptions.defaultValue;
|
|
2836
|
+
inputElement.setAttribute('aria-label', message);
|
|
2837
|
+
|
|
2838
|
+
if (inputOptions.primaryColor) {
|
|
2839
|
+
const isLight = isLightColor(inputOptions.primaryColor);
|
|
2840
|
+
inputElement.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.2)' : 'rgba(255, 255, 255, 0.2)';
|
|
2841
|
+
inputElement.style.background = isLight ? 'rgba(15, 23, 42, 0.06)' : 'rgba(255, 255, 255, 0.08)';
|
|
2842
|
+
inputElement.style.color = textColor;
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
// Clear error on input
|
|
2846
|
+
inputElement.addEventListener('input', () => {
|
|
2847
|
+
if (errorElement && errorElement.classList.contains('visible')) {
|
|
2848
|
+
clearError();
|
|
2849
|
+
}
|
|
2850
|
+
});
|
|
2851
|
+
|
|
2852
|
+
inputWrapper.appendChild(inputElement);
|
|
2853
|
+
|
|
2854
|
+
// Error message element
|
|
2855
|
+
errorElement = document.createElement("div");
|
|
2856
|
+
errorElement.className = "toast-input-error";
|
|
2857
|
+
errorElement.setAttribute('role', 'alert');
|
|
2858
|
+
inputWrapper.appendChild(errorElement);
|
|
2859
|
+
|
|
2860
|
+
// Actions wrapper
|
|
2861
|
+
const actionsWrapper = document.createElement("div");
|
|
2862
|
+
actionsWrapper.className = "toast-input-actions";
|
|
2863
|
+
|
|
2864
|
+
// Cancel button
|
|
2865
|
+
cancelBtnElement = document.createElement("button");
|
|
2866
|
+
cancelBtnElement.className = "input-btn input-btn-cancel";
|
|
2867
|
+
cancelBtnElement.type = "button";
|
|
2868
|
+
cancelBtnElement.textContent = inputOptions.cancelText;
|
|
2869
|
+
cancelBtnElement.onclick = handleCancel;
|
|
2870
|
+
|
|
2871
|
+
if (inputOptions.primaryColor) {
|
|
2872
|
+
const isLight = isLightColor(inputOptions.primaryColor);
|
|
2873
|
+
cancelBtnElement.style.background = isLight ? 'rgba(15, 23, 42, 0.08)' : 'rgba(255, 255, 255, 0.1)';
|
|
2874
|
+
cancelBtnElement.style.color = textColor;
|
|
2875
|
+
cancelBtnElement.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.2)' : 'rgba(255, 255, 255, 0.2)';
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
// Submit button
|
|
2879
|
+
submitBtnElement = document.createElement("button");
|
|
2880
|
+
submitBtnElement.className = "input-btn input-btn-submit";
|
|
2881
|
+
submitBtnElement.type = "button";
|
|
2882
|
+
|
|
2883
|
+
const textWrapper = document.createElement("span");
|
|
2884
|
+
textWrapper.className = "btn-text";
|
|
2885
|
+
textWrapper.textContent = inputOptions.submitText;
|
|
2886
|
+
submitBtnElement.appendChild(textWrapper);
|
|
2887
|
+
|
|
2888
|
+
// Spinner
|
|
2889
|
+
const spinner = document.createElement("span");
|
|
2890
|
+
spinner.className = "btn-spinner";
|
|
2891
|
+
spinner.innerHTML = `<svg width="16" height="16" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2892
|
+
<path d="M9.5 2.9375V5.5625M9.5 13.4375V16.0625M2.9375 9.5H5.5625M13.4375 9.5H16.0625" stroke="currentColor" stroke-width="1.875" stroke-linecap="round" />
|
|
2893
|
+
<path d="M4.86011 4.85961L6.71627 6.71577M12.2847 12.2842L14.1409 14.1404M4.86011 14.1404L6.71627 12.2842M12.2847 6.71577L14.1409 4.85961" stroke="currentColor" stroke-width="1.875" stroke-linecap="round" />
|
|
2894
|
+
</svg>`;
|
|
2895
|
+
submitBtnElement.appendChild(spinner);
|
|
2896
|
+
|
|
2897
|
+
submitBtnElement.onclick = handleSubmit;
|
|
2898
|
+
|
|
2899
|
+
if (inputOptions.primaryColor) {
|
|
2900
|
+
const primary = inputOptions.primaryColor;
|
|
2901
|
+
const isLight = isLightColor(primary);
|
|
2902
|
+
submitBtnElement.style.background = isLight
|
|
2903
|
+
? 'linear-gradient(135deg, #1e293b 0%, #334155 100%)'
|
|
2904
|
+
: 'linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%)';
|
|
2905
|
+
submitBtnElement.style.color = isLight ? 'white' : '#1e293b';
|
|
2906
|
+
submitBtnElement.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.3)' : 'rgba(255, 255, 255, 0.4)';
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
actionsWrapper.appendChild(cancelBtnElement);
|
|
2910
|
+
actionsWrapper.appendChild(submitBtnElement);
|
|
2911
|
+
inputWrapper.appendChild(actionsWrapper);
|
|
2912
|
+
|
|
2913
|
+
toast.appendChild(inputWrapper);
|
|
2914
|
+
inputContainer.appendChild(toast);
|
|
2915
|
+
|
|
2916
|
+
// Entrance animation
|
|
2917
|
+
setTimeout(() => {
|
|
2918
|
+
toast.classList.add("show");
|
|
2919
|
+
const icon = toast.querySelector('.toast-icon');
|
|
2920
|
+
if (icon) {
|
|
2921
|
+
icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
// Focus input after animation
|
|
2925
|
+
setTimeout(() => {
|
|
2926
|
+
inputElement.focus();
|
|
2927
|
+
// Select default value if present
|
|
2928
|
+
if (inputOptions.defaultValue) {
|
|
2929
|
+
inputElement.select();
|
|
2930
|
+
}
|
|
2931
|
+
}, 150);
|
|
2932
|
+
}, 10);
|
|
2933
|
+
|
|
2934
|
+
// Return control object
|
|
2935
|
+
return {
|
|
2936
|
+
element: toast,
|
|
2937
|
+
close: closeInput,
|
|
2938
|
+
setLoading,
|
|
2939
|
+
getValue: () => inputElement.value,
|
|
2940
|
+
setValue: (value) => { inputElement.value = value; },
|
|
2941
|
+
setError: showError,
|
|
2942
|
+
clearError
|
|
2943
|
+
};
|
|
2944
|
+
} catch (error) {
|
|
2945
|
+
console.error('ToastifyPro: Failed to create input toast:', error);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
/**
|
|
2950
|
+
* Alias for input() method - shows an input prompt
|
|
2951
|
+
* @param {string} message - Main prompt question
|
|
2952
|
+
* @param {string|Function|Object} descriptionOrCallback - Description text, callback function, or options object
|
|
2953
|
+
* @param {Function} callback - Callback function (if description provided)
|
|
2954
|
+
*/
|
|
2955
|
+
prompt(message, descriptionOrCallback, callback) {
|
|
2956
|
+
return this.input(message, descriptionOrCallback, callback);
|
|
2957
|
+
}
|
|
1723
2958
|
}
|
|
1724
2959
|
|
|
1725
2960
|
/**
|