toastify-pro 1.2.0 → 1.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.
@@ -12,16 +12,26 @@
12
12
  * - Progress bar with shimmer effects
13
13
  * - Responsive design for mobile devices
14
14
  * - Framework agnostic (works with React, Vue, Angular, etc.)
15
+ * - Confirmation dialogs with customizable buttons and callbacks
16
+ * - Center position support for enhanced focus
17
+ * - Independent positioning for confirmations
18
+ * - Loading states for async operations
19
+ * - Custom gradient colors with primaryColor/secondaryColor
20
+ * - Single instance mode with shake animation
15
21
  *
16
- * @version 1.2.0
22
+ * @version 1.4.0
17
23
  * @author ToastifyPro Team
18
24
  * @license MIT
19
25
  */
26
+
27
+ // Global active confirmation tracker (shared across all instances)
28
+ let globalActiveConfirmation = null;
29
+
20
30
  class ToastifyPro {
21
31
  /**
22
32
  * Creates a new ToastifyPro instance
23
33
  * @param {Object} options - Configuration options
24
- * @param {string} options.position - Toast position (top-left, top-right, bottom-left, bottom-right, top-center, bottom-center)
34
+ * @param {string} options.position - Toast position (top-left, top-right, bottom-left, bottom-right, top-center, bottom-center, center)
25
35
  * @param {number} options.timeout - Auto-dismiss timeout in milliseconds (0 to disable)
26
36
  * @param {boolean} options.allowClose - Whether to show close button
27
37
  * @param {number} options.maxLength - Maximum message length
@@ -35,14 +45,14 @@ class ToastifyPro {
35
45
 
36
46
  // Merge with default options
37
47
  this.defaultOptions = {
38
- position: options.position || "bottom-center", // top-left, top-right, bottom-left, bottom-right, top-center, bottom-center
48
+ position: options.position || "bottom-center", // top-left, top-right, bottom-left, bottom-right, top-center, bottom-center, center
39
49
  timeout: options.timeout || 3000,
40
50
  allowClose: options.allowClose !== false, // default true
41
51
  maxLength: options.maxLength || 100,
42
52
  };
43
53
 
44
54
  // Validate position
45
- const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center'];
55
+ const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center', 'center'];
46
56
  if (!validPositions.includes(this.defaultOptions.position)) {
47
57
  console.warn(`ToastifyPro: Invalid position "${this.defaultOptions.position}". Using "bottom-center".`);
48
58
  this.defaultOptions.position = "bottom-center";
@@ -138,6 +148,7 @@ class ToastifyPro {
138
148
  .toastify-pro-container.bottom-right { bottom: 50px; right: 24px; align-items: flex-end; }
139
149
  .toastify-pro-container.top-center { top: 50px; left: 50%; transform: translateX(-50%); }
140
150
  .toastify-pro-container.bottom-center { bottom: 50px; left: 50%; transform: translateX(-50%); }
151
+ .toastify-pro-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); }
141
152
 
142
153
  .toastify-pro {
143
154
  min-width: 280px;
@@ -295,6 +306,26 @@ class ToastifyPro {
295
306
  100% { transform: translateX(100%); }
296
307
  }
297
308
 
309
+ @keyframes shake {
310
+ 0%, 100% { transform: translate(0, 0); }
311
+ 10%, 30%, 50%, 70%, 90% { transform: translate(-10px, 0); }
312
+ 20%, 40%, 60%, 80% { transform: translate(10px, 0); }
313
+ }
314
+
315
+ @keyframes shakeCenter {
316
+ 0%, 100% { transform: scale(1) translateX(0); }
317
+ 10%, 30%, 50%, 70%, 90% { transform: scale(1) translateX(-10px); }
318
+ 20%, 40%, 60%, 80% { transform: scale(1) translateX(10px); }
319
+ }
320
+
321
+ .toastify-pro.shake {
322
+ animation: shake 0.6s cubic-bezier(0.36, 0.07, 0.19, 0.97) !important;
323
+ }
324
+
325
+ .toastify-pro-container.center .toastify-pro.shake {
326
+ animation: shakeCenter 0.6s cubic-bezier(0.36, 0.07, 0.19, 0.97) !important;
327
+ }
328
+
298
329
  .toastify-pro.show {
299
330
  opacity: 1;
300
331
  transform: scale(1);
@@ -397,6 +428,11 @@ class ToastifyPro {
397
428
  opacity: 0;
398
429
  }
399
430
  }
431
+
432
+ @keyframes spin {
433
+ 0% { transform: rotate(0deg); }
434
+ 100% { transform: rotate(360deg); }
435
+ }
400
436
 
401
437
  .toastify-pro .toast-icon svg {
402
438
  width: 18px;
@@ -476,6 +512,287 @@ class ToastifyPro {
476
512
  .toastify-pro-container.top-right,
477
513
  .toastify-pro-container.bottom-right { right: 16px; }
478
514
  }
515
+
516
+ /* Confirmation Toast Styles - Enhanced Modern Design */
517
+ .toastify-pro.confirmation {
518
+ min-width: 380px;
519
+ max-width: 500px;
520
+ padding: 32px 28px 28px;
521
+ flex-direction: column;
522
+ align-items: stretch;
523
+ gap: 24px;
524
+ position: relative;
525
+ backdrop-filter: blur(24px) saturate(180%);
526
+ box-shadow:
527
+ 0 24px 48px -12px rgba(0, 0, 0, 0.25),
528
+ 0 12px 24px -8px rgba(0, 0, 0, 0.15),
529
+ 0 0 0 1px rgba(255, 255, 255, 0.08),
530
+ inset 0 1px 0 0 rgba(255, 255, 255, 0.1);
531
+ border: 1.5px solid rgba(255, 255, 255, 0.15);
532
+ border-radius: 20px;
533
+ }
534
+
535
+ /* Hide progress bar for confirmation toasts */
536
+ .toastify-pro.confirmation::after {
537
+ display: none;
538
+ }
539
+
540
+ /* Shimmer effect for confirmation toasts */
541
+ .toastify-pro.confirmation::before {
542
+ opacity: 0.5;
543
+ }
544
+
545
+ /* Close button for confirmation dialogs */
546
+ .toastify-pro.confirmation .conf-close-btn {
547
+ position: absolute;
548
+ top: 14px;
549
+ right: 14px;
550
+ cursor: pointer;
551
+ font-size: 20px;
552
+ color: inherit;
553
+ opacity: 0.5;
554
+ padding: 6px;
555
+ border-radius: 8px;
556
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
557
+ width: 32px;
558
+ height: 32px;
559
+ display: flex;
560
+ align-items: center;
561
+ justify-content: center;
562
+ background: rgba(255, 255, 255, 0.08);
563
+ backdrop-filter: blur(10px);
564
+ font-weight: 300;
565
+ line-height: 1;
566
+ border: 1px solid rgba(255, 255, 255, 0.12);
567
+ }
568
+
569
+ .toastify-pro.confirmation .conf-close-btn:hover {
570
+ opacity: 0.9;
571
+ background: rgba(255, 255, 255, 0.15);
572
+ transform: scale(1.1) rotate(90deg);
573
+ border-color: rgba(255, 255, 255, 0.25);
574
+ }
575
+
576
+ .toastify-pro.confirmation.light .conf-close-btn {
577
+ background: rgba(15, 23, 42, 0.06);
578
+ border-color: rgba(15, 23, 42, 0.12);
579
+ opacity: 0.6;
580
+ }
581
+
582
+ .toastify-pro.confirmation.light .conf-close-btn:hover {
583
+ background: rgba(15, 23, 42, 0.12);
584
+ border-color: rgba(15, 23, 42, 0.2);
585
+ opacity: 1;
586
+ }
587
+
588
+ /* Icon styling for confirmation */
589
+ .toastify-pro.confirmation .toast-icon {
590
+ width: 56px;
591
+ height: 56px;
592
+ margin: 0 auto 8px;
593
+ background: rgba(255, 255, 255, 0.15);
594
+ backdrop-filter: blur(12px);
595
+ border: 2px solid rgba(255, 255, 255, 0.2);
596
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
597
+ }
598
+
599
+ .toastify-pro.confirmation .toast-icon svg {
600
+ width: 28px;
601
+ height: 28px;
602
+ }
603
+
604
+ .toastify-pro.confirmation.light .toast-icon {
605
+ background: rgba(15, 23, 42, 0.08);
606
+ border-color: rgba(15, 23, 42, 0.15);
607
+ }
608
+
609
+ .toastify-pro.confirmation .toast-content {
610
+ text-align: center;
611
+ margin-bottom: 4px;
612
+ }
613
+
614
+ .toastify-pro.confirmation .toast-message {
615
+ font-weight: 700;
616
+ font-size: 20px;
617
+ margin-bottom: 8px;
618
+ letter-spacing: -0.02em;
619
+ line-height: 1.3;
620
+ }
621
+
622
+ .toastify-pro.confirmation .toast-description {
623
+ font-size: 15px;
624
+ opacity: 0.85;
625
+ margin-top: 8px;
626
+ line-height: 1.5;
627
+ font-weight: 400;
628
+ }
629
+
630
+ /* Fix text visibility for dark/light variants */
631
+ .toastify-pro.confirmation.dark .toast-message,
632
+ .toastify-pro.confirmation.dark .toast-description {
633
+ color: white;
634
+ }
635
+
636
+ .toastify-pro.confirmation.light .toast-message,
637
+ .toastify-pro.confirmation.light .toast-description {
638
+ color: #1e293b;
639
+ }
640
+
641
+ .toast-actions {
642
+ display: flex;
643
+ gap: 14px;
644
+ margin-top: 4px;
645
+ }
646
+
647
+ .toast-btn {
648
+ flex: 1;
649
+ padding: 14px 20px;
650
+ border: none;
651
+ border-radius: 12px;
652
+ font-weight: 600;
653
+ font-size: 15px;
654
+ cursor: pointer;
655
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
656
+ backdrop-filter: blur(10px);
657
+ position: relative;
658
+ overflow: hidden;
659
+ letter-spacing: 0.01em;
660
+ }
661
+
662
+ .toast-btn::after {
663
+ content: '';
664
+ position: absolute;
665
+ top: 50%;
666
+ left: 50%;
667
+ width: 0;
668
+ height: 0;
669
+ border-radius: 50%;
670
+ background: rgba(255, 255, 255, 0.3);
671
+ transform: translate(-50%, -50%);
672
+ transition: width 0.6s, height 0.6s;
673
+ }
674
+
675
+ .toast-btn:hover::after {
676
+ width: 300px;
677
+ height: 300px;
678
+ }
679
+
680
+ .toast-btn:hover {
681
+ transform: translateY(-2px);
682
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
683
+ }
684
+
685
+ .toast-btn:active {
686
+ transform: translateY(0);
687
+ transition: all 0.1s;
688
+ }
689
+
690
+ .toast-btn-cancel {
691
+ background: rgba(255, 255, 255, 0.1);
692
+ color: rgba(255, 255, 255, 0.9);
693
+ border: 1.5px solid rgba(255, 255, 255, 0.25);
694
+ font-weight: 600;
695
+ }
696
+
697
+ .toast-btn-cancel:hover {
698
+ background: rgba(255, 255, 255, 0.15);
699
+ color: white;
700
+ border-color: rgba(255, 255, 255, 0.35);
701
+ }
702
+
703
+ .toast-btn-cancel:disabled {
704
+ opacity: 0.5;
705
+ cursor: not-allowed;
706
+ pointer-events: none;
707
+ }
708
+
709
+ .toast-btn-confirm {
710
+ color: white;
711
+ font-weight: 700;
712
+ border: 2px solid rgba(255, 255, 255, 0.4);
713
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
714
+ position: relative;
715
+ overflow: hidden;
716
+ background: linear-gradient(135deg, rgba(15, 23, 42, 0.95), rgba(30, 41, 59, 0.95));
717
+ }
718
+
719
+ .toast-btn-confirm::before {
720
+ content: '';
721
+ position: absolute;
722
+ top: 0;
723
+ left: -100%;
724
+ width: 100%;
725
+ height: 100%;
726
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.25), transparent);
727
+ transition: left 0.6s;
728
+ }
729
+
730
+ .toast-btn-confirm:hover::before {
731
+ left: 100%;
732
+ }
733
+
734
+ .toast-btn-confirm:hover {
735
+ background: linear-gradient(135deg, rgba(15, 23, 42, 1), rgba(30, 41, 59, 1));
736
+ border-color: rgba(255, 255, 255, 0.5);
737
+ box-shadow: 0 8px 28px rgba(15, 23, 42, 0.5);
738
+ }
739
+
740
+ .toast-btn-confirm.loading {
741
+ opacity: 0.7;
742
+ cursor: not-allowed;
743
+ pointer-events: none;
744
+ }
745
+
746
+ .toast-btn-confirm .btn-spinner {
747
+ display: none;
748
+ width: 16px;
749
+ height: 16px;
750
+ border: 2px solid rgba(255, 255, 255, 0.3);
751
+ border-top-color: white;
752
+ border-radius: 50%;
753
+ animation: spin 0.6s linear infinite;
754
+ margin-right: 8px;
755
+ }
756
+
757
+ .toast-btn-confirm.loading .btn-spinner {
758
+ display: inline-block;
759
+ }
760
+
761
+ .toast-btn-confirm.loading .btn-text {
762
+ opacity: 0.7;
763
+ }
764
+
765
+ .toastify-pro.light .toast-btn-cancel {
766
+ background: rgba(15, 23, 42, 0.08);
767
+ color: rgba(15, 23, 42, 0.85);
768
+ border-color: rgba(15, 23, 42, 0.2);
769
+ }
770
+
771
+ .toastify-pro.light .toast-btn-cancel:hover {
772
+ background: rgba(15, 23, 42, 0.12);
773
+ color: rgba(15, 23, 42, 1);
774
+ border-color: rgba(15, 23, 42, 0.3);
775
+ }
776
+
777
+ /* Enhanced light theme confirm buttons */
778
+ .toastify-pro.light .toast-btn-confirm {
779
+ border-color: rgba(15, 23, 42, 0.35);
780
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
781
+ color: white;
782
+ }
783
+
784
+ .toastify-pro.light .toast-btn-confirm:hover {
785
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
786
+ border-color: rgba(15, 23, 42, 0.5);
787
+ box-shadow: 0 8px 28px rgba(15, 23, 42, 0.3);
788
+ }
789
+
790
+ @media (max-width: 640px) {
791
+ .toastify-pro.confirmation {
792
+ min-width: 280px;
793
+ max-width: calc(100vw - 32px);
794
+ }
795
+ }
479
796
  `;
480
797
  document.head.appendChild(style);
481
798
  } catch (error) {
@@ -716,6 +1033,444 @@ class ToastifyPro {
716
1033
  }
717
1034
  this.show(msg, "light", opts);
718
1035
  }
1036
+
1037
+ /**
1038
+ * Shows a confirmation toast with confirm/cancel buttons
1039
+ * @param {string} message - Main confirmation question
1040
+ * @param {string|Function|Object} descriptionOrCallback - Description text, callback function, or options object
1041
+ * @param {Function} callback - Callback function (if description provided)
1042
+ */
1043
+ conf(message, descriptionOrCallback, callback) {
1044
+ // Check if there's already an active confirmation (GLOBAL CHECK)
1045
+ if (globalActiveConfirmation && globalActiveConfirmation.element && globalActiveConfirmation.element.parentNode) {
1046
+ // Trigger shake animation on existing confirmation toast element
1047
+ const existingToast = globalActiveConfirmation.element;
1048
+ existingToast.classList.remove('shake');
1049
+ // Force reflow to restart animation
1050
+ void existingToast.offsetWidth;
1051
+ existingToast.classList.add('shake');
1052
+
1053
+ // Remove shake class after animation completes
1054
+ setTimeout(() => {
1055
+ if (existingToast && existingToast.parentNode) {
1056
+ existingToast.classList.remove('shake');
1057
+ }
1058
+ }, 600);
1059
+
1060
+ return globalActiveConfirmation;
1061
+ }
1062
+
1063
+ // Parse arguments to support multiple usage patterns
1064
+ let description = '';
1065
+ let options = {};
1066
+ let resultCallback = null;
1067
+
1068
+ // Pattern 1: conf('message', callback)
1069
+ if (typeof descriptionOrCallback === 'function' && !callback) {
1070
+ resultCallback = descriptionOrCallback;
1071
+ }
1072
+ // Pattern 2: conf('message', 'description', callback)
1073
+ else if (typeof descriptionOrCallback === 'string' && typeof callback === 'function') {
1074
+ description = descriptionOrCallback;
1075
+ resultCallback = callback;
1076
+ }
1077
+ // Pattern 3: conf('message', options) with onConfirm/onCancel
1078
+ else if (typeof descriptionOrCallback === 'object' && descriptionOrCallback !== null) {
1079
+ options = descriptionOrCallback;
1080
+ description = options.description || '';
1081
+
1082
+ // Use onConfirm/onCancel if provided, otherwise use callback parameter
1083
+ if (options.onConfirm || options.onCancel) {
1084
+ // Don't use the callback parameter if onConfirm/onCancel are provided
1085
+ resultCallback = null;
1086
+ } else if (typeof callback === 'function') {
1087
+ resultCallback = callback;
1088
+ }
1089
+ }
1090
+ // Pattern 4: conf('message', 'description', options) - legacy support
1091
+ else if (typeof descriptionOrCallback === 'string' && typeof callback === 'object') {
1092
+ description = descriptionOrCallback;
1093
+ options = callback || {};
1094
+ // In this case, no unified callback, rely on onConfirm/onCancel
1095
+ resultCallback = null;
1096
+ }
1097
+
1098
+ // Default options for confirmation
1099
+ const confirmOptions = {
1100
+ timeout: 0, // No auto-dismiss for confirmations
1101
+ allowClose: false, // No close button, must choose
1102
+ confirmText: options.confirmText || 'Confirm',
1103
+ cancelText: options.cancelText || 'Cancel',
1104
+ theme: options.theme || options.color || 'dark', // Support both theme and color for backward compatibility
1105
+ position: options.position || 'center', // Default to center for confirmations
1106
+ primaryColor: options.primaryColor || null,
1107
+ secondaryColor: options.secondaryColor || null,
1108
+ loading: options.loading || false, // Support external loading state (for React/Vue)
1109
+ ...options
1110
+ };
1111
+
1112
+ // Validate and set theme to only dark or light
1113
+ if (confirmOptions.theme === 'light' || confirmOptions.theme === 'white') {
1114
+ confirmOptions.theme = 'light';
1115
+ } else {
1116
+ confirmOptions.theme = 'dark'; // Default to dark for all other values
1117
+ }
1118
+
1119
+ // Helper function to determine if a color is light or dark
1120
+ const isLightColor = (color) => {
1121
+ if (!color) return false;
1122
+ const hex = color.replace('#', '');
1123
+ const r = parseInt(hex.substr(0, 2), 16);
1124
+ const g = parseInt(hex.substr(2, 2), 16);
1125
+ const b = parseInt(hex.substr(4, 2), 16);
1126
+ const brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
1127
+ return brightness > 155;
1128
+ };
1129
+
1130
+ // Determine text color based on background
1131
+ let textColor = confirmOptions.theme === 'light' ? '#1e293b' : 'white';
1132
+ if (confirmOptions.primaryColor) {
1133
+ textColor = isLightColor(confirmOptions.primaryColor) ? '#1e293b' : 'white';
1134
+ }
1135
+
1136
+ // Validate position for confirmation toast
1137
+ const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center', 'center'];
1138
+ if (!validPositions.includes(confirmOptions.position)) {
1139
+ console.warn(`ToastifyPro: Invalid confirmation position "${confirmOptions.position}". Using default position.`);
1140
+ confirmOptions.position = this.defaultOptions.position;
1141
+ }
1142
+
1143
+ // Get or create container for the specified position
1144
+ let confirmContainer = document.querySelector(`.toastify-pro-container.${confirmOptions.position}`);
1145
+
1146
+ if (!confirmContainer) {
1147
+ try {
1148
+ confirmContainer = document.createElement("div");
1149
+ confirmContainer.className = `toastify-pro-container ${confirmOptions.position}`;
1150
+ document.body.appendChild(confirmContainer);
1151
+ } catch (error) {
1152
+ console.warn('ToastifyPro: Failed to create confirmation container. Using default container.');
1153
+ confirmContainer = this.container;
1154
+ }
1155
+ }
1156
+
1157
+ // Create control functions for loading state
1158
+ let confirmBtnElement = null;
1159
+ let cancelBtnElement = null;
1160
+ let closeBtnElement = null;
1161
+ let isLoading = false;
1162
+ let useLoading = false; // Track if user wants loading behavior
1163
+ let toastElement = null; // Reference to toast element
1164
+
1165
+ const setLoading = (loading) => {
1166
+ useLoading = true; // User is manually controlling loading
1167
+ isLoading = loading;
1168
+ if (confirmBtnElement) {
1169
+ if (loading) {
1170
+ confirmBtnElement.classList.add('loading');
1171
+ confirmBtnElement.disabled = true;
1172
+ } else {
1173
+ confirmBtnElement.classList.remove('loading');
1174
+ confirmBtnElement.disabled = false;
1175
+ }
1176
+ }
1177
+ // Disable/enable cancel and close buttons during loading
1178
+ if (cancelBtnElement) {
1179
+ cancelBtnElement.disabled = loading;
1180
+ cancelBtnElement.style.opacity = loading ? '0.5' : '1';
1181
+ cancelBtnElement.style.cursor = loading ? 'not-allowed' : 'pointer';
1182
+ }
1183
+ if (closeBtnElement) {
1184
+ closeBtnElement.style.opacity = loading ? '0.3' : '0.5';
1185
+ closeBtnElement.style.cursor = loading ? 'not-allowed' : 'pointer';
1186
+ closeBtnElement.style.pointerEvents = loading ? 'none' : 'auto';
1187
+ }
1188
+ };
1189
+
1190
+ const closeConfirmation = () => {
1191
+ if (toastElement && toastElement.parentNode) {
1192
+ globalActiveConfirmation = null;
1193
+ this.removeToast(toastElement);
1194
+ }
1195
+ };
1196
+
1197
+ // Helper function to handle confirmation result
1198
+ const handleConfirmation = async (confirmed) => {
1199
+ if (confirmed) {
1200
+ // Call onConfirm if provided
1201
+ if (options.onConfirm && typeof options.onConfirm === 'function') {
1202
+ try {
1203
+ const result = options.onConfirm({ setLoading, close: closeConfirmation });
1204
+ // Check if it's a promise
1205
+ if (result && typeof result.then === 'function') {
1206
+ // If user didn't manually set loading, auto-set it
1207
+ if (!useLoading) {
1208
+ setLoading(true);
1209
+ }
1210
+ await result;
1211
+ // Auto-close after promise resolves if user didn't manually close
1212
+ if (toastElement && toastElement.parentNode) {
1213
+ setLoading(false);
1214
+ closeConfirmation();
1215
+ }
1216
+ } else {
1217
+ // Synchronous callback - only close if user didn't start loading
1218
+ if (!useLoading) {
1219
+ closeConfirmation();
1220
+ }
1221
+ }
1222
+ } catch (error) {
1223
+ console.error('ToastifyPro: Error in onConfirm callback:', error);
1224
+ setLoading(false);
1225
+ closeConfirmation();
1226
+ }
1227
+ }
1228
+ // Call unified callback if provided
1229
+ else if (resultCallback && typeof resultCallback === 'function') {
1230
+ try {
1231
+ const result = resultCallback(true, { setLoading, close: closeConfirmation });
1232
+ if (result && typeof result.then === 'function') {
1233
+ if (!useLoading) {
1234
+ setLoading(true);
1235
+ }
1236
+ await result;
1237
+ if (toastElement && toastElement.parentNode) {
1238
+ setLoading(false);
1239
+ closeConfirmation();
1240
+ }
1241
+ } else {
1242
+ if (!useLoading) {
1243
+ closeConfirmation();
1244
+ }
1245
+ }
1246
+ } catch (error) {
1247
+ console.error('ToastifyPro: Error in confirmation callback:', error);
1248
+ setLoading(false);
1249
+ closeConfirmation();
1250
+ }
1251
+ } else {
1252
+ // No callback - just close
1253
+ closeConfirmation();
1254
+ }
1255
+ } else {
1256
+ // Cancel - no loading needed, check if not currently loading
1257
+ if (isLoading) return; // Don't allow cancel while loading
1258
+
1259
+ // Call onCancel if provided
1260
+ if (options.onCancel && typeof options.onCancel === 'function') {
1261
+ try {
1262
+ options.onCancel();
1263
+ } catch (error) {
1264
+ console.error('ToastifyPro: Error in onCancel callback:', error);
1265
+ }
1266
+ }
1267
+ // Call unified callback if provided
1268
+ if (resultCallback && typeof resultCallback === 'function') {
1269
+ try {
1270
+ resultCallback(false);
1271
+ } catch (error) {
1272
+ console.error('ToastifyPro: Error in confirmation callback:', error);
1273
+ }
1274
+ }
1275
+ closeConfirmation();
1276
+ }
1277
+ };
1278
+
1279
+ try {
1280
+ // Create confirmation toast element
1281
+ const toast = document.createElement("div");
1282
+ toast.className = `toastify-pro confirmation ${confirmOptions.theme}`;
1283
+
1284
+ // Store reference to toast element
1285
+ toastElement = toast;
1286
+
1287
+ // Apply custom colors if provided
1288
+ if (confirmOptions.primaryColor) {
1289
+ const primary = confirmOptions.primaryColor;
1290
+ const secondary = confirmOptions.secondaryColor;
1291
+
1292
+ if (secondary) {
1293
+ // Both colors provided - create gradient
1294
+ toast.style.background = `linear-gradient(135deg, ${primary} 0%, ${secondary} 100%)`;
1295
+ } else {
1296
+ // Only primary color - solid with slight transparency
1297
+ toast.style.background = primary;
1298
+ }
1299
+
1300
+ // Set text color based on background brightness
1301
+ toast.style.color = textColor;
1302
+
1303
+ // Adjust border color to match
1304
+ const borderOpacity = isLightColor(primary) ? '0.2' : '0.15';
1305
+ toast.style.borderColor = `rgba(255, 255, 255, ${borderOpacity})`;
1306
+ }
1307
+
1308
+ // Create close button for confirmation
1309
+ const closeBtn = document.createElement("span");
1310
+ closeBtn.className = "conf-close-btn";
1311
+ closeBtn.innerHTML = "×";
1312
+ closeBtn.setAttribute('aria-label', 'Cancel confirmation');
1313
+ closeBtn.onclick = () => {
1314
+ if (!isLoading) {
1315
+ handleConfirmation(false);
1316
+ }
1317
+ };
1318
+ if (confirmOptions.primaryColor) {
1319
+ closeBtn.style.color = textColor;
1320
+ }
1321
+ closeBtnElement = closeBtn;
1322
+ toast.appendChild(closeBtn);
1323
+
1324
+ // Create icon wrapper
1325
+ const iconWrapper = document.createElement("div");
1326
+ iconWrapper.className = "toast-icon";
1327
+ iconWrapper.innerHTML = this.getIconSVG('info'); // Default to info icon
1328
+ if (confirmOptions.primaryColor) {
1329
+ iconWrapper.style.color = textColor;
1330
+ }
1331
+ toast.appendChild(iconWrapper);
1332
+
1333
+ // Create content wrapper
1334
+ const contentWrapper = document.createElement("div");
1335
+ contentWrapper.className = "toast-content";
1336
+
1337
+ // Main message
1338
+ const messageElement = document.createElement("div");
1339
+ messageElement.className = "toast-message";
1340
+ messageElement.textContent = message.substring(0, this.defaultOptions.maxLength);
1341
+ if (confirmOptions.primaryColor) {
1342
+ messageElement.style.color = textColor;
1343
+ }
1344
+ contentWrapper.appendChild(messageElement);
1345
+
1346
+ // Optional description
1347
+ if (description) {
1348
+ const descriptionElement = document.createElement("div");
1349
+ descriptionElement.className = "toast-description";
1350
+ descriptionElement.textContent = description.substring(0, this.defaultOptions.maxLength * 2);
1351
+ if (confirmOptions.primaryColor) {
1352
+ descriptionElement.style.color = textColor;
1353
+ }
1354
+ contentWrapper.appendChild(descriptionElement);
1355
+ }
1356
+
1357
+ toast.appendChild(contentWrapper);
1358
+
1359
+ // Create action buttons container
1360
+ const actionsWrapper = document.createElement("div");
1361
+ actionsWrapper.className = "toast-actions";
1362
+
1363
+ // Cancel button
1364
+ const cancelBtn = document.createElement("button");
1365
+ cancelBtn.className = "toast-btn toast-btn-cancel";
1366
+ cancelBtn.textContent = confirmOptions.cancelText;
1367
+ cancelBtn.onclick = () => {
1368
+ if (!isLoading) {
1369
+ handleConfirmation(false);
1370
+ }
1371
+ };
1372
+
1373
+ // Store cancel button reference
1374
+ cancelBtnElement = cancelBtn;
1375
+
1376
+ // Style cancel button with custom colors
1377
+ if (confirmOptions.primaryColor) {
1378
+ const isLight = isLightColor(confirmOptions.primaryColor);
1379
+ cancelBtn.style.background = isLight ? 'rgba(15, 23, 42, 0.08)' : 'rgba(255, 255, 255, 0.1)';
1380
+ cancelBtn.style.color = textColor;
1381
+ cancelBtn.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.2)' : 'rgba(255, 255, 255, 0.25)';
1382
+ }
1383
+
1384
+ // Confirm button
1385
+ const confirmBtn = document.createElement("button");
1386
+ confirmBtn.className = `toast-btn toast-btn-confirm`;
1387
+
1388
+ // Create spinner element
1389
+ const spinner = document.createElement("span");
1390
+ spinner.className = "btn-spinner";
1391
+ confirmBtn.appendChild(spinner);
1392
+
1393
+ // Create text wrapper
1394
+ const textWrapper = document.createElement("span");
1395
+ textWrapper.className = "btn-text";
1396
+ textWrapper.textContent = confirmOptions.confirmText;
1397
+ confirmBtn.appendChild(textWrapper);
1398
+
1399
+ confirmBtn.onclick = () => {
1400
+ if (!isLoading) {
1401
+ handleConfirmation(true);
1402
+ }
1403
+ };
1404
+
1405
+ // Store reference for loading state control
1406
+ confirmBtnElement = confirmBtn;
1407
+
1408
+ // Style confirm button with custom colors
1409
+ if (confirmOptions.primaryColor) {
1410
+ const primary = confirmOptions.primaryColor;
1411
+ const secondary = confirmOptions.secondaryColor;
1412
+ const isLight = isLightColor(primary);
1413
+
1414
+ if (secondary) {
1415
+ // Gradient confirm button
1416
+ confirmBtn.style.background = `linear-gradient(135deg, ${primary} 0%, ${secondary} 100%)`;
1417
+ } else {
1418
+ // Solid color confirm button with enhanced styling
1419
+ confirmBtn.style.background = primary;
1420
+ }
1421
+
1422
+ // Determine button text color (always use contrasting color for readability)
1423
+ confirmBtn.style.color = isLight ? '#1e293b' : 'white';
1424
+ confirmBtn.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.3)' : 'rgba(255, 255, 255, 0.4)';
1425
+ }
1426
+
1427
+ actionsWrapper.appendChild(cancelBtn);
1428
+ actionsWrapper.appendChild(confirmBtn);
1429
+ toast.appendChild(actionsWrapper);
1430
+
1431
+ // Add toast to the specified container (not default container)
1432
+ confirmContainer.appendChild(toast);
1433
+
1434
+ // Create control object
1435
+ const controlObject = {
1436
+ element: toast,
1437
+ setLoading: setLoading,
1438
+ close: closeConfirmation
1439
+ };
1440
+
1441
+ // Store as global active confirmation (with control object)
1442
+ globalActiveConfirmation = controlObject;
1443
+
1444
+ // Apply initial loading state if provided (for React/Vue)
1445
+ if (confirmOptions.loading) {
1446
+ setLoading(true);
1447
+ }
1448
+
1449
+ // Entrance animation
1450
+ setTimeout(() => {
1451
+ toast.classList.add("show");
1452
+ const icon = toast.querySelector('.toast-icon');
1453
+ if (icon) {
1454
+ icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
1455
+ }
1456
+ }, 10);
1457
+
1458
+ // Return control object with toast element and control functions
1459
+ return controlObject;
1460
+ } catch (error) {
1461
+ console.error('ToastifyPro: Failed to create confirmation toast:', error);
1462
+ }
1463
+ }
1464
+
1465
+ /**
1466
+ * Alias for conf() method - shows a confirmation toast
1467
+ * @param {string} message - Main confirmation question
1468
+ * @param {string|Function|Object} descriptionOrCallback - Description text, callback function, or options object
1469
+ * @param {Function} callback - Callback function (if description provided)
1470
+ */
1471
+ confirm(message, descriptionOrCallback, callback) {
1472
+ return this.conf(message, descriptionOrCallback, callback);
1473
+ }
719
1474
  }
720
1475
 
721
1476
  /**