toastify-pro 1.4.0 → 1.6.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/dist/toastify-pro.esm.js +728 -25
- package/dist/toastify-pro.esm.js.map +1 -1
- package/dist/toastify-pro.umd.js +728 -25
- package/dist/toastify-pro.umd.js.map +1 -1
- package/dist/toastify-pro.umd.min.js +10 -6
- package/dist/toastify-pro.umd.min.js.map +1 -1
- package/package.json +2 -2
- package/src/toastify-pro.js +728 -25
package/dist/toastify-pro.esm.js
CHANGED
|
@@ -9,17 +9,21 @@
|
|
|
9
9
|
* - Position-aware car swipe exit animations
|
|
10
10
|
* - Description support for enhanced messaging
|
|
11
11
|
* - Six theme variants (success, error, info, warning, dark, light)
|
|
12
|
+
* - Custom color toasts with gradient support (custom method)
|
|
12
13
|
* - Progress bar with shimmer effects
|
|
13
14
|
* - Responsive design for mobile devices
|
|
14
15
|
* - Framework agnostic (works with React, Vue, Angular, etc.)
|
|
15
16
|
* - Confirmation dialogs with customizable buttons and callbacks
|
|
17
|
+
* - Confirmation overlay with blur effect for focus
|
|
16
18
|
* - Center position support for enhanced focus
|
|
17
19
|
* - Independent positioning for confirmations
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
20
|
+
* - Action buttons in toasts with customizable callbacks
|
|
21
|
+
* - Pause on hover functionality
|
|
22
|
+
* - Queue management (maxToasts, newestOnTop)
|
|
23
|
+
* - Full accessibility support (ARIA, keyboard navigation, reduced motion)
|
|
24
|
+
* - Focus management for confirmation dialogs
|
|
21
25
|
*
|
|
22
|
-
* @version 1.
|
|
26
|
+
* @version 1.6.0
|
|
23
27
|
* @author ToastifyPro Team
|
|
24
28
|
* @license MIT
|
|
25
29
|
*/
|
|
@@ -35,6 +39,12 @@ class ToastifyPro {
|
|
|
35
39
|
* @param {number} options.timeout - Auto-dismiss timeout in milliseconds (0 to disable)
|
|
36
40
|
* @param {boolean} options.allowClose - Whether to show close button
|
|
37
41
|
* @param {number} options.maxLength - Maximum message length
|
|
42
|
+
* @param {string} options.primaryColor - Primary color for custom() method
|
|
43
|
+
* @param {string} options.secondaryColor - Secondary color for gradient in custom() method
|
|
44
|
+
* @param {boolean} options.pauseOnHover - Pause timeout when hovering over toast (default: true)
|
|
45
|
+
* @param {number} options.maxToasts - Maximum number of visible toasts (0 for unlimited)
|
|
46
|
+
* @param {boolean} options.newestOnTop - Show newest toasts on top (default: true)
|
|
47
|
+
* @param {boolean} options.ariaLive - ARIA live region setting: 'polite' or 'assertive' (default: 'polite')
|
|
38
48
|
*/
|
|
39
49
|
constructor(options = {}) {
|
|
40
50
|
// Validate options parameter
|
|
@@ -49,7 +59,16 @@ class ToastifyPro {
|
|
|
49
59
|
timeout: options.timeout || 3000,
|
|
50
60
|
allowClose: options.allowClose !== false, // default true
|
|
51
61
|
maxLength: options.maxLength || 100,
|
|
62
|
+
primaryColor: options.primaryColor || null, // Custom primary color for custom() method
|
|
63
|
+
secondaryColor: options.secondaryColor || null, // Custom secondary color for gradient
|
|
64
|
+
pauseOnHover: options.pauseOnHover !== false, // default true - pause timeout on hover
|
|
65
|
+
maxToasts: options.maxToasts || 0, // 0 = unlimited
|
|
66
|
+
newestOnTop: options.newestOnTop !== false, // default true
|
|
67
|
+
ariaLive: options.ariaLive || 'polite', // 'polite' or 'assertive'
|
|
52
68
|
};
|
|
69
|
+
|
|
70
|
+
// Track active toasts for queue management
|
|
71
|
+
this.activeToasts = [];
|
|
53
72
|
|
|
54
73
|
// Validate position
|
|
55
74
|
const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center', 'center'];
|
|
@@ -82,6 +101,46 @@ class ToastifyPro {
|
|
|
82
101
|
|
|
83
102
|
// Inject styles once
|
|
84
103
|
this.injectStyles();
|
|
104
|
+
|
|
105
|
+
// Setup global keyboard event listener for accessibility
|
|
106
|
+
this.setupKeyboardNavigation();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sets up keyboard navigation for accessibility
|
|
111
|
+
* - Escape key dismisses the most recent toast or confirmation
|
|
112
|
+
* - Tab key cycles through focusable elements in confirmations
|
|
113
|
+
*/
|
|
114
|
+
setupKeyboardNavigation() {
|
|
115
|
+
// Only setup once globally
|
|
116
|
+
if (window._toastifyProKeyboardSetup) return;
|
|
117
|
+
window._toastifyProKeyboardSetup = true;
|
|
118
|
+
|
|
119
|
+
document.addEventListener('keydown', (e) => {
|
|
120
|
+
// Escape key - dismiss toast or confirmation
|
|
121
|
+
if (e.key === 'Escape') {
|
|
122
|
+
// First check for active confirmation
|
|
123
|
+
if (globalActiveConfirmation && globalActiveConfirmation.element) {
|
|
124
|
+
const loadingBtn = globalActiveConfirmation.element.querySelector('.toast-btn-confirm.loading');
|
|
125
|
+
if (!loadingBtn) {
|
|
126
|
+
globalActiveConfirmation.close();
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Otherwise dismiss the most recent toast
|
|
132
|
+
const containers = document.querySelectorAll('.toastify-pro-container');
|
|
133
|
+
containers.forEach(container => {
|
|
134
|
+
const toasts = container.querySelectorAll('.toastify-pro:not(.confirmation)');
|
|
135
|
+
if (toasts.length > 0) {
|
|
136
|
+
const lastToast = toasts[toasts.length - 1];
|
|
137
|
+
if (lastToast && lastToast._toastInstance) {
|
|
138
|
+
lastToast._toastInstance.removeToast(lastToast);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
});
|
|
85
144
|
}
|
|
86
145
|
|
|
87
146
|
/**
|
|
@@ -476,6 +535,7 @@ class ToastifyPro {
|
|
|
476
535
|
transition: all 0.2s ease;
|
|
477
536
|
flex-shrink: 0;
|
|
478
537
|
width: 32px;
|
|
538
|
+
border: none;
|
|
479
539
|
height: 32px;
|
|
480
540
|
display: flex;
|
|
481
541
|
align-items: center;
|
|
@@ -707,6 +767,9 @@ class ToastifyPro {
|
|
|
707
767
|
}
|
|
708
768
|
|
|
709
769
|
.toast-btn-confirm {
|
|
770
|
+
display: flex;
|
|
771
|
+
align-items: center;
|
|
772
|
+
justify-content: center;
|
|
710
773
|
color: white;
|
|
711
774
|
font-weight: 700;
|
|
712
775
|
border: 2px solid rgba(255, 255, 255, 0.4);
|
|
@@ -745,17 +808,20 @@ class ToastifyPro {
|
|
|
745
808
|
|
|
746
809
|
.toast-btn-confirm .btn-spinner {
|
|
747
810
|
display: none;
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
811
|
+
align-items: center;
|
|
812
|
+
justify-content: center;
|
|
813
|
+
margin-left: 8px;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.toast-btn-confirm .btn-spinner svg {
|
|
817
|
+
width: 25px;
|
|
818
|
+
height: 25px;
|
|
819
|
+
animation: spin 1s linear infinite;
|
|
820
|
+
color: currentColor;
|
|
755
821
|
}
|
|
756
822
|
|
|
757
823
|
.toast-btn-confirm.loading .btn-spinner {
|
|
758
|
-
display: inline-
|
|
824
|
+
display: inline-flex;
|
|
759
825
|
}
|
|
760
826
|
|
|
761
827
|
.toast-btn-confirm.loading .btn-text {
|
|
@@ -793,6 +859,169 @@ class ToastifyPro {
|
|
|
793
859
|
max-width: calc(100vw - 32px);
|
|
794
860
|
}
|
|
795
861
|
}
|
|
862
|
+
|
|
863
|
+
/* Custom toast type */
|
|
864
|
+
.toastify-pro.custom {
|
|
865
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.toastify-pro.custom.light-text {
|
|
869
|
+
color: #1e293b;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.toastify-pro.custom.light-text .toast-icon {
|
|
873
|
+
background: rgba(15, 23, 42, 0.1);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.toastify-pro.custom.light-text .close-btn {
|
|
877
|
+
background: rgba(15, 23, 42, 0.08);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.toastify-pro.custom.light-text .close-btn:hover {
|
|
881
|
+
background: rgba(15, 23, 42, 0.15);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
.toastify-pro.custom.light-text::before {
|
|
885
|
+
background: linear-gradient(90deg,
|
|
886
|
+
rgba(30, 41, 59, 0.8) 0%,
|
|
887
|
+
rgba(30, 41, 59, 0.4) 50%,
|
|
888
|
+
rgba(30, 41, 59, 0.8) 100%);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.toastify-pro.custom.light-text::after {
|
|
892
|
+
background: rgba(30, 41, 59, 0.6);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/* Confirmation Overlay */
|
|
896
|
+
.toastify-pro-overlay {
|
|
897
|
+
position: fixed;
|
|
898
|
+
top: 0;
|
|
899
|
+
left: 0;
|
|
900
|
+
right: 0;
|
|
901
|
+
bottom: 0;
|
|
902
|
+
background: rgba(0, 0, 0, 0.5);
|
|
903
|
+
backdrop-filter: blur(8px);
|
|
904
|
+
-webkit-backdrop-filter: blur(8px);
|
|
905
|
+
z-index: 9998;
|
|
906
|
+
opacity: 0;
|
|
907
|
+
transition: opacity 0.3s ease;
|
|
908
|
+
pointer-events: auto;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.toastify-pro-overlay.show {
|
|
912
|
+
opacity: 1;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/* Action Button Styles */
|
|
916
|
+
.toastify-pro .toast-action {
|
|
917
|
+
display: inline-flex;
|
|
918
|
+
align-items: center;
|
|
919
|
+
gap: 6px;
|
|
920
|
+
padding: 6px 12px;
|
|
921
|
+
margin-top: 8px;
|
|
922
|
+
border: none;
|
|
923
|
+
border-radius: 8px;
|
|
924
|
+
font-weight: 600;
|
|
925
|
+
font-size: 13px;
|
|
926
|
+
cursor: pointer;
|
|
927
|
+
transition: all 0.2s ease;
|
|
928
|
+
background: rgba(255, 255, 255, 0.2);
|
|
929
|
+
color: inherit;
|
|
930
|
+
backdrop-filter: blur(10px);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
.toastify-pro .toast-action:hover {
|
|
934
|
+
background: rgba(255, 255, 255, 0.3);
|
|
935
|
+
transform: translateY(-1px);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.toastify-pro .toast-action:active {
|
|
939
|
+
transform: translateY(0);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.toastify-pro.light .toast-action {
|
|
943
|
+
background: rgba(15, 23, 42, 0.1);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.toastify-pro.light .toast-action:hover {
|
|
947
|
+
background: rgba(15, 23, 42, 0.15);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/* Paused state - pause progress bar */
|
|
951
|
+
.toastify-pro.paused::after {
|
|
952
|
+
animation-play-state: paused;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* Focus styles for accessibility */
|
|
956
|
+
.toastify-pro .close-btn:focus,
|
|
957
|
+
.toastify-pro .toast-action:focus,
|
|
958
|
+
.toast-btn:focus {
|
|
959
|
+
outline: 1px solid rgba(255, 255, 255, 0.8);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.toastify-pro.light .close-btn:focus,
|
|
963
|
+
.toastify-pro.light .toast-action:focus {
|
|
964
|
+
outline-color: 1px solid rgba(15, 23, 42, 0.5);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/* Screen reader only class */
|
|
968
|
+
.sr-only {
|
|
969
|
+
position: absolute;
|
|
970
|
+
width: 1px;
|
|
971
|
+
height: 1px;
|
|
972
|
+
padding: 0;
|
|
973
|
+
margin: -1px;
|
|
974
|
+
overflow: hidden;
|
|
975
|
+
clip: rect(0, 0, 0, 0);
|
|
976
|
+
white-space: nowrap;
|
|
977
|
+
border: 0;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/* Reduced motion support */
|
|
981
|
+
@media (prefers-reduced-motion: reduce) {
|
|
982
|
+
.toastify-pro {
|
|
983
|
+
transition: opacity 0.3s ease;
|
|
984
|
+
transform: none !important;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.toastify-pro.show {
|
|
988
|
+
animation: none !important;
|
|
989
|
+
opacity: 1;
|
|
990
|
+
transform: none !important;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.toastify-pro .toast-icon {
|
|
994
|
+
animation: none !important;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.toastify-pro::before {
|
|
998
|
+
animation: none !important;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.toastify-pro::after {
|
|
1002
|
+
animation: progress var(--duration, 5s) linear !important;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.toastify-pro-overlay {
|
|
1006
|
+
transition: opacity 0.2s ease;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.toast-btn::after {
|
|
1010
|
+
display: none;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.toast-btn:hover {
|
|
1014
|
+
transform: none;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.toastify-pro.confirmation .conf-close-btn:hover {
|
|
1018
|
+
transform: scale(1.05);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.btn-spinner svg {
|
|
1022
|
+
animation: spin 1.5s linear infinite !important;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
796
1025
|
`;
|
|
797
1026
|
document.head.appendChild(style);
|
|
798
1027
|
} catch (error) {
|
|
@@ -809,6 +1038,9 @@ class ToastifyPro {
|
|
|
809
1038
|
* @param {number} opts.timeout - Override default timeout
|
|
810
1039
|
* @param {boolean} opts.allowClose - Override close button setting
|
|
811
1040
|
* @param {number} opts.maxLength - Override max message length
|
|
1041
|
+
* @param {Object} opts.action - Action button configuration { label, onClick }
|
|
1042
|
+
* @param {boolean} opts.pauseOnHover - Pause timeout on hover
|
|
1043
|
+
* @param {string} opts.ariaLive - ARIA live region type ('polite' or 'assertive')
|
|
812
1044
|
*/
|
|
813
1045
|
show(message, type = "dark", opts = {}) {
|
|
814
1046
|
// Input validation
|
|
@@ -838,10 +1070,30 @@ class ToastifyPro {
|
|
|
838
1070
|
const options = { ...this.defaultOptions, ...opts };
|
|
839
1071
|
|
|
840
1072
|
try {
|
|
1073
|
+
// Queue management - remove oldest toasts if limit exceeded
|
|
1074
|
+
if (options.maxToasts > 0 && this.activeToasts.length >= options.maxToasts) {
|
|
1075
|
+
const toastsToRemove = this.activeToasts.length - options.maxToasts + 1;
|
|
1076
|
+
for (let i = 0; i < toastsToRemove; i++) {
|
|
1077
|
+
const oldestToast = this.activeToasts.shift();
|
|
1078
|
+
if (oldestToast && oldestToast.element) {
|
|
1079
|
+
this.removeToast(oldestToast.element);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
841
1084
|
// Create toast element
|
|
842
1085
|
const toast = document.createElement("div");
|
|
843
1086
|
toast.className = `toastify-pro ${type}`;
|
|
844
1087
|
|
|
1088
|
+
// Store reference to this instance for keyboard navigation
|
|
1089
|
+
toast._toastInstance = this;
|
|
1090
|
+
|
|
1091
|
+
// ARIA accessibility attributes
|
|
1092
|
+
const ariaLive = type === 'error' || type === 'warning' ? 'assertive' : (options.ariaLive || 'polite');
|
|
1093
|
+
toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
|
|
1094
|
+
toast.setAttribute('aria-live', ariaLive);
|
|
1095
|
+
toast.setAttribute('aria-atomic', 'true');
|
|
1096
|
+
|
|
845
1097
|
// Set duration for progress bar animation
|
|
846
1098
|
if (options.timeout > 0) {
|
|
847
1099
|
toast.style.setProperty('--duration', `${options.timeout}ms`);
|
|
@@ -850,6 +1102,7 @@ class ToastifyPro {
|
|
|
850
1102
|
// Create icon wrapper
|
|
851
1103
|
const iconWrapper = document.createElement("div");
|
|
852
1104
|
iconWrapper.className = "toast-icon";
|
|
1105
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
853
1106
|
iconWrapper.innerHTML = this.getIconSVG(type);
|
|
854
1107
|
toast.appendChild(iconWrapper);
|
|
855
1108
|
|
|
@@ -871,20 +1124,50 @@ class ToastifyPro {
|
|
|
871
1124
|
contentWrapper.appendChild(descriptionElement);
|
|
872
1125
|
}
|
|
873
1126
|
|
|
1127
|
+
// Action button support
|
|
1128
|
+
if (options.action && typeof options.action === 'object') {
|
|
1129
|
+
const actionBtn = document.createElement("button");
|
|
1130
|
+
actionBtn.className = "toast-action";
|
|
1131
|
+
actionBtn.textContent = options.action.label || 'Action';
|
|
1132
|
+
actionBtn.setAttribute('type', 'button');
|
|
1133
|
+
if (typeof options.action.onClick === 'function') {
|
|
1134
|
+
actionBtn.onclick = (e) => {
|
|
1135
|
+
e.stopPropagation();
|
|
1136
|
+
options.action.onClick({ close: () => this.removeToast(toast), event: e });
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
contentWrapper.appendChild(actionBtn);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
874
1142
|
toast.appendChild(contentWrapper);
|
|
875
1143
|
|
|
876
1144
|
// Add close button if enabled
|
|
877
1145
|
if (options.allowClose) {
|
|
878
|
-
const closeBtn = document.createElement("
|
|
1146
|
+
const closeBtn = document.createElement("button");
|
|
879
1147
|
closeBtn.className = "close-btn";
|
|
880
1148
|
closeBtn.innerHTML = "×";
|
|
1149
|
+
closeBtn.setAttribute('type', 'button');
|
|
881
1150
|
closeBtn.setAttribute('aria-label', 'Close notification');
|
|
882
1151
|
closeBtn.onclick = () => this.removeToast(toast);
|
|
883
1152
|
toast.appendChild(closeBtn);
|
|
884
1153
|
}
|
|
885
1154
|
|
|
886
|
-
// Add toast to container
|
|
887
|
-
this.container.
|
|
1155
|
+
// Add toast to container (respect newestOnTop setting)
|
|
1156
|
+
if (options.newestOnTop && this.container.firstChild) {
|
|
1157
|
+
this.container.insertBefore(toast, this.container.firstChild);
|
|
1158
|
+
} else {
|
|
1159
|
+
this.container.appendChild(toast);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Track toast for queue management
|
|
1163
|
+
const toastData = {
|
|
1164
|
+
element: toast,
|
|
1165
|
+
timeout: null,
|
|
1166
|
+
remainingTime: options.timeout,
|
|
1167
|
+
startTime: null,
|
|
1168
|
+
isPaused: false
|
|
1169
|
+
};
|
|
1170
|
+
this.activeToasts.push(toastData);
|
|
888
1171
|
|
|
889
1172
|
// Apple AirDrop-style entrance animation
|
|
890
1173
|
setTimeout(() => {
|
|
@@ -896,16 +1179,90 @@ class ToastifyPro {
|
|
|
896
1179
|
}
|
|
897
1180
|
}, 10);
|
|
898
1181
|
|
|
1182
|
+
// Pause on hover functionality
|
|
1183
|
+
if (options.pauseOnHover && options.timeout > 0) {
|
|
1184
|
+
toast.addEventListener('mouseenter', () => {
|
|
1185
|
+
if (toastData.timeout) {
|
|
1186
|
+
clearTimeout(toastData.timeout);
|
|
1187
|
+
toastData.isPaused = true;
|
|
1188
|
+
toastData.remainingTime -= (Date.now() - toastData.startTime);
|
|
1189
|
+
toast.classList.add('paused');
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
toast.addEventListener('mouseleave', () => {
|
|
1194
|
+
if (toastData.isPaused && toastData.remainingTime > 0) {
|
|
1195
|
+
toastData.isPaused = false;
|
|
1196
|
+
toastData.startTime = Date.now();
|
|
1197
|
+
toast.classList.remove('paused');
|
|
1198
|
+
// Update CSS variable for remaining progress
|
|
1199
|
+
toast.style.setProperty('--duration', `${toastData.remainingTime}ms`);
|
|
1200
|
+
// Restart the progress animation
|
|
1201
|
+
const afterElement = toast.querySelector('::after');
|
|
1202
|
+
toast.style.animation = 'none';
|
|
1203
|
+
void toast.offsetHeight; // Force reflow
|
|
1204
|
+
toast.style.animation = '';
|
|
1205
|
+
|
|
1206
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), toastData.remainingTime);
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
|
|
899
1211
|
// Auto-remove after timeout
|
|
900
1212
|
if (options.timeout > 0) {
|
|
901
|
-
|
|
1213
|
+
toastData.startTime = Date.now();
|
|
1214
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), options.timeout);
|
|
902
1215
|
}
|
|
903
1216
|
|
|
904
|
-
|
|
1217
|
+
// Return toast control object
|
|
1218
|
+
return {
|
|
1219
|
+
element: toast,
|
|
1220
|
+
dismiss: () => this.removeToast(toast),
|
|
1221
|
+
update: (newMessage, newOpts) => this.updateToast(toast, newMessage, newOpts)
|
|
1222
|
+
};
|
|
905
1223
|
} catch (error) {
|
|
906
1224
|
console.error('ToastifyPro: Failed to create toast:', error);
|
|
907
1225
|
}
|
|
908
1226
|
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Updates an existing toast's content
|
|
1230
|
+
* @param {HTMLElement} toast - Toast element to update
|
|
1231
|
+
* @param {string} message - New message text
|
|
1232
|
+
* @param {Object} opts - Options to update
|
|
1233
|
+
*/
|
|
1234
|
+
updateToast(toast, message, opts = {}) {
|
|
1235
|
+
if (!toast || !toast.parentNode) return;
|
|
1236
|
+
|
|
1237
|
+
const messageEl = toast.querySelector('.toast-message');
|
|
1238
|
+
const descEl = toast.querySelector('.toast-description');
|
|
1239
|
+
|
|
1240
|
+
if (message && messageEl) {
|
|
1241
|
+
messageEl.textContent = message;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (opts.description && descEl) {
|
|
1245
|
+
descEl.textContent = opts.description;
|
|
1246
|
+
} else if (opts.description) {
|
|
1247
|
+
const descriptionElement = document.createElement("div");
|
|
1248
|
+
descriptionElement.className = "toast-description";
|
|
1249
|
+
descriptionElement.textContent = opts.description;
|
|
1250
|
+
toast.querySelector('.toast-content')?.appendChild(descriptionElement);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Update type/style if provided
|
|
1254
|
+
if (opts.type) {
|
|
1255
|
+
const validTypes = ['success', 'error', 'info', 'warning', 'dark', 'light'];
|
|
1256
|
+
if (validTypes.includes(opts.type)) {
|
|
1257
|
+
validTypes.forEach(t => toast.classList.remove(t));
|
|
1258
|
+
toast.classList.add(opts.type);
|
|
1259
|
+
const iconWrapper = toast.querySelector('.toast-icon');
|
|
1260
|
+
if (iconWrapper) {
|
|
1261
|
+
iconWrapper.innerHTML = this.getIconSVG(opts.type);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
909
1266
|
|
|
910
1267
|
/**
|
|
911
1268
|
* Removes a toast with position-aware car swipe animation
|
|
@@ -918,6 +1275,16 @@ class ToastifyPro {
|
|
|
918
1275
|
}
|
|
919
1276
|
|
|
920
1277
|
try {
|
|
1278
|
+
// Remove from active toasts tracking
|
|
1279
|
+
const toastIndex = this.activeToasts.findIndex(t => t.element === toast);
|
|
1280
|
+
if (toastIndex > -1) {
|
|
1281
|
+
const toastData = this.activeToasts[toastIndex];
|
|
1282
|
+
if (toastData.timeout) {
|
|
1283
|
+
clearTimeout(toastData.timeout);
|
|
1284
|
+
}
|
|
1285
|
+
this.activeToasts.splice(toastIndex, 1);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
921
1288
|
// Detect position to choose the right swipe direction
|
|
922
1289
|
const container = toast.parentNode;
|
|
923
1290
|
const position = container.className.split(' ')[1]; // get position class
|
|
@@ -960,6 +1327,33 @@ class ToastifyPro {
|
|
|
960
1327
|
}
|
|
961
1328
|
}
|
|
962
1329
|
}
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* Dismisses all active toasts
|
|
1333
|
+
* @param {string} type - Optional: only dismiss toasts of this type
|
|
1334
|
+
*/
|
|
1335
|
+
dismissAll(type = null) {
|
|
1336
|
+
const toastsCopy = [...this.activeToasts];
|
|
1337
|
+
toastsCopy.forEach(toastData => {
|
|
1338
|
+
if (toastData.element) {
|
|
1339
|
+
if (type) {
|
|
1340
|
+
if (toastData.element.classList.contains(type)) {
|
|
1341
|
+
this.removeToast(toastData.element);
|
|
1342
|
+
}
|
|
1343
|
+
} else {
|
|
1344
|
+
this.removeToast(toastData.element);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* Gets the count of active toasts
|
|
1352
|
+
* @returns {number} Number of active toasts
|
|
1353
|
+
*/
|
|
1354
|
+
getActiveCount() {
|
|
1355
|
+
return this.activeToasts.length;
|
|
1356
|
+
}
|
|
963
1357
|
|
|
964
1358
|
/**
|
|
965
1359
|
* Shows a success toast notification
|
|
@@ -1034,6 +1428,220 @@ class ToastifyPro {
|
|
|
1034
1428
|
this.show(msg, "light", opts);
|
|
1035
1429
|
}
|
|
1036
1430
|
|
|
1431
|
+
/**
|
|
1432
|
+
* Shows a custom-colored toast notification with gradient support
|
|
1433
|
+
* @param {string} msg - Main message
|
|
1434
|
+
* @param {string|Object} opts - Description string or options object
|
|
1435
|
+
* @param {string} opts.primaryColor - Primary color for the toast
|
|
1436
|
+
* @param {string} opts.secondaryColor - Secondary color for gradient (optional)
|
|
1437
|
+
*/
|
|
1438
|
+
custom(msg, opts) {
|
|
1439
|
+
if (typeof opts === 'string') {
|
|
1440
|
+
opts = { description: opts };
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
opts = opts || {};
|
|
1444
|
+
|
|
1445
|
+
// Get colors from options or use default options
|
|
1446
|
+
const primaryColor = opts.primaryColor || this.defaultOptions.primaryColor;
|
|
1447
|
+
const secondaryColor = opts.secondaryColor || this.defaultOptions.secondaryColor;
|
|
1448
|
+
|
|
1449
|
+
// If no custom colors provided, fallback to success style
|
|
1450
|
+
if (!primaryColor) {
|
|
1451
|
+
return this.success(msg, opts);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// Helper function to determine if a color is light
|
|
1455
|
+
const isLightColor = (color) => {
|
|
1456
|
+
if (!color) return false;
|
|
1457
|
+
const hex = color.replace('#', '');
|
|
1458
|
+
const r = parseInt(hex.substr(0, 2), 16);
|
|
1459
|
+
const g = parseInt(hex.substr(2, 2), 16);
|
|
1460
|
+
const b = parseInt(hex.substr(4, 2), 16);
|
|
1461
|
+
const brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
|
1462
|
+
return brightness > 155;
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// Helper function to lighten or darken a color
|
|
1466
|
+
const adjustColor = (color, percent) => {
|
|
1467
|
+
const hex = color.replace('#', '');
|
|
1468
|
+
const r = parseInt(hex.substr(0, 2), 16);
|
|
1469
|
+
const g = parseInt(hex.substr(2, 2), 16);
|
|
1470
|
+
const b = parseInt(hex.substr(4, 2), 16);
|
|
1471
|
+
|
|
1472
|
+
const adjust = (c) => {
|
|
1473
|
+
const adjusted = Math.round(c + (percent / 100) * (percent > 0 ? (255 - c) : c));
|
|
1474
|
+
return Math.max(0, Math.min(255, adjusted));
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
const newR = adjust(r).toString(16).padStart(2, '0');
|
|
1478
|
+
const newG = adjust(g).toString(16).padStart(2, '0');
|
|
1479
|
+
const newB = adjust(b).toString(16).padStart(2, '0');
|
|
1480
|
+
|
|
1481
|
+
return `#${newR}${newG}${newB}`;
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
// Determine gradient colors
|
|
1485
|
+
let gradientStart = primaryColor;
|
|
1486
|
+
let gradientEnd;
|
|
1487
|
+
|
|
1488
|
+
if (secondaryColor) {
|
|
1489
|
+
// Both colors provided
|
|
1490
|
+
gradientEnd = secondaryColor;
|
|
1491
|
+
} else {
|
|
1492
|
+
// Only primary color - create gradient with lighter/darker shade
|
|
1493
|
+
const isLight = isLightColor(primaryColor);
|
|
1494
|
+
gradientEnd = isLight ? adjustColor(primaryColor, -25) : adjustColor(primaryColor, 25);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// Determine text color
|
|
1498
|
+
const needsLightText = isLightColor(primaryColor);
|
|
1499
|
+
|
|
1500
|
+
// Create custom options
|
|
1501
|
+
const customOpts = {
|
|
1502
|
+
...opts,
|
|
1503
|
+
customGradient: `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)`,
|
|
1504
|
+
customTextLight: needsLightText
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
this.showCustom(msg, customOpts);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/**
|
|
1511
|
+
* Internal method to show a custom-styled toast
|
|
1512
|
+
* @param {string} message - Main message text
|
|
1513
|
+
* @param {Object} opts - Options including customGradient and customTextLight
|
|
1514
|
+
*/
|
|
1515
|
+
showCustom(message, opts = {}) {
|
|
1516
|
+
if (typeof message !== 'string') {
|
|
1517
|
+
message = String(message);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
if (!message.trim()) {
|
|
1521
|
+
console.warn('ToastifyPro: Empty message provided.');
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
const options = { ...this.defaultOptions, ...opts };
|
|
1526
|
+
|
|
1527
|
+
try {
|
|
1528
|
+
const toast = document.createElement("div");
|
|
1529
|
+
toast.className = `toastify-pro custom${options.customTextLight ? ' light-text' : ''}`;
|
|
1530
|
+
|
|
1531
|
+
// Store reference to this instance
|
|
1532
|
+
toast._toastInstance = this;
|
|
1533
|
+
|
|
1534
|
+
// ARIA accessibility attributes
|
|
1535
|
+
toast.setAttribute('role', 'status');
|
|
1536
|
+
toast.setAttribute('aria-live', options.ariaLive || 'polite');
|
|
1537
|
+
toast.setAttribute('aria-atomic', 'true');
|
|
1538
|
+
|
|
1539
|
+
// Apply custom gradient
|
|
1540
|
+
if (options.customGradient) {
|
|
1541
|
+
toast.style.background = options.customGradient;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (options.timeout > 0) {
|
|
1545
|
+
toast.style.setProperty('--duration', `${options.timeout}ms`);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Create icon wrapper
|
|
1549
|
+
const iconWrapper = document.createElement("div");
|
|
1550
|
+
iconWrapper.className = "toast-icon";
|
|
1551
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
1552
|
+
iconWrapper.innerHTML = this.getIconSVG('success'); // Use success icon for custom
|
|
1553
|
+
toast.appendChild(iconWrapper);
|
|
1554
|
+
|
|
1555
|
+
// Create content wrapper
|
|
1556
|
+
const contentWrapper = document.createElement("div");
|
|
1557
|
+
contentWrapper.className = "toast-content";
|
|
1558
|
+
|
|
1559
|
+
const messageElement = document.createElement("div");
|
|
1560
|
+
messageElement.className = "toast-message";
|
|
1561
|
+
messageElement.textContent = message.substring(0, options.maxLength);
|
|
1562
|
+
contentWrapper.appendChild(messageElement);
|
|
1563
|
+
|
|
1564
|
+
if (options.description && typeof options.description === 'string') {
|
|
1565
|
+
const descriptionElement = document.createElement("div");
|
|
1566
|
+
descriptionElement.className = "toast-description";
|
|
1567
|
+
descriptionElement.textContent = options.description.substring(0, options.maxLength * 2);
|
|
1568
|
+
contentWrapper.appendChild(descriptionElement);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
toast.appendChild(contentWrapper);
|
|
1572
|
+
|
|
1573
|
+
if (options.allowClose) {
|
|
1574
|
+
const closeBtn = document.createElement("button");
|
|
1575
|
+
closeBtn.className = "close-btn";
|
|
1576
|
+
closeBtn.innerHTML = "×";
|
|
1577
|
+
closeBtn.setAttribute('type', 'button');
|
|
1578
|
+
closeBtn.setAttribute('aria-label', 'Close notification');
|
|
1579
|
+
closeBtn.onclick = () => this.removeToast(toast);
|
|
1580
|
+
toast.appendChild(closeBtn);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// Add toast to container (respect newestOnTop setting)
|
|
1584
|
+
if (options.newestOnTop && this.container.firstChild) {
|
|
1585
|
+
this.container.insertBefore(toast, this.container.firstChild);
|
|
1586
|
+
} else {
|
|
1587
|
+
this.container.appendChild(toast);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// Track toast for queue management
|
|
1591
|
+
const toastData = {
|
|
1592
|
+
element: toast,
|
|
1593
|
+
timeout: null,
|
|
1594
|
+
remainingTime: options.timeout,
|
|
1595
|
+
startTime: null,
|
|
1596
|
+
isPaused: false
|
|
1597
|
+
};
|
|
1598
|
+
this.activeToasts.push(toastData);
|
|
1599
|
+
|
|
1600
|
+
// Pause on hover functionality
|
|
1601
|
+
if (options.pauseOnHover && options.timeout > 0) {
|
|
1602
|
+
toast.addEventListener('mouseenter', () => {
|
|
1603
|
+
if (toastData.timeout) {
|
|
1604
|
+
clearTimeout(toastData.timeout);
|
|
1605
|
+
toastData.isPaused = true;
|
|
1606
|
+
toastData.remainingTime -= (Date.now() - toastData.startTime);
|
|
1607
|
+
toast.classList.add('paused');
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
toast.addEventListener('mouseleave', () => {
|
|
1612
|
+
if (toastData.isPaused && toastData.remainingTime > 0) {
|
|
1613
|
+
toastData.isPaused = false;
|
|
1614
|
+
toastData.startTime = Date.now();
|
|
1615
|
+
toast.classList.remove('paused');
|
|
1616
|
+
toast.style.setProperty('--duration', `${toastData.remainingTime}ms`);
|
|
1617
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), toastData.remainingTime);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
setTimeout(() => {
|
|
1623
|
+
toast.classList.add("show");
|
|
1624
|
+
const icon = toast.querySelector('.toast-icon');
|
|
1625
|
+
if (icon) {
|
|
1626
|
+
icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
|
1627
|
+
}
|
|
1628
|
+
}, 10);
|
|
1629
|
+
|
|
1630
|
+
if (options.timeout > 0) {
|
|
1631
|
+
toastData.startTime = Date.now();
|
|
1632
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), options.timeout);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
return {
|
|
1636
|
+
element: toast,
|
|
1637
|
+
dismiss: () => this.removeToast(toast),
|
|
1638
|
+
update: (newMessage, newOpts) => this.updateToast(toast, newMessage, newOpts)
|
|
1639
|
+
};
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
console.error('ToastifyPro: Failed to create custom toast:', error);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1037
1645
|
/**
|
|
1038
1646
|
* Shows a confirmation toast with confirm/cancel buttons
|
|
1039
1647
|
* @param {string} message - Main confirmation question
|
|
@@ -1161,6 +1769,34 @@ class ToastifyPro {
|
|
|
1161
1769
|
let isLoading = false;
|
|
1162
1770
|
let useLoading = false; // Track if user wants loading behavior
|
|
1163
1771
|
let toastElement = null; // Reference to toast element
|
|
1772
|
+
let overlayElement = null; // Reference to overlay element
|
|
1773
|
+
|
|
1774
|
+
// Create overlay for confirmation
|
|
1775
|
+
const createOverlay = () => {
|
|
1776
|
+
overlayElement = document.createElement("div");
|
|
1777
|
+
overlayElement.className = "toastify-pro-overlay";
|
|
1778
|
+
document.body.appendChild(overlayElement);
|
|
1779
|
+
|
|
1780
|
+
// Trigger show animation
|
|
1781
|
+
setTimeout(() => {
|
|
1782
|
+
overlayElement.classList.add("show");
|
|
1783
|
+
}, 10);
|
|
1784
|
+
|
|
1785
|
+
return overlayElement;
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1788
|
+
// Remove overlay
|
|
1789
|
+
const removeOverlay = () => {
|
|
1790
|
+
if (overlayElement && overlayElement.parentNode) {
|
|
1791
|
+
overlayElement.classList.remove("show");
|
|
1792
|
+
setTimeout(() => {
|
|
1793
|
+
if (overlayElement && overlayElement.parentNode) {
|
|
1794
|
+
overlayElement.remove();
|
|
1795
|
+
}
|
|
1796
|
+
overlayElement = null;
|
|
1797
|
+
}, 300);
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1164
1800
|
|
|
1165
1801
|
const setLoading = (loading) => {
|
|
1166
1802
|
useLoading = true; // User is manually controlling loading
|
|
@@ -1190,6 +1826,7 @@ class ToastifyPro {
|
|
|
1190
1826
|
const closeConfirmation = () => {
|
|
1191
1827
|
if (toastElement && toastElement.parentNode) {
|
|
1192
1828
|
globalActiveConfirmation = null;
|
|
1829
|
+
removeOverlay(); // Remove the overlay when closing
|
|
1193
1830
|
this.removeToast(toastElement);
|
|
1194
1831
|
}
|
|
1195
1832
|
};
|
|
@@ -1277,6 +1914,9 @@ class ToastifyPro {
|
|
|
1277
1914
|
};
|
|
1278
1915
|
|
|
1279
1916
|
try {
|
|
1917
|
+
// Create overlay first
|
|
1918
|
+
createOverlay();
|
|
1919
|
+
|
|
1280
1920
|
// Create confirmation toast element
|
|
1281
1921
|
const toast = document.createElement("div");
|
|
1282
1922
|
toast.className = `toastify-pro confirmation ${confirmOptions.theme}`;
|
|
@@ -1306,9 +1946,10 @@ class ToastifyPro {
|
|
|
1306
1946
|
}
|
|
1307
1947
|
|
|
1308
1948
|
// Create close button for confirmation
|
|
1309
|
-
const closeBtn = document.createElement("
|
|
1949
|
+
const closeBtn = document.createElement("button");
|
|
1310
1950
|
closeBtn.className = "conf-close-btn";
|
|
1311
1951
|
closeBtn.innerHTML = "×";
|
|
1952
|
+
closeBtn.setAttribute('type', 'button');
|
|
1312
1953
|
closeBtn.setAttribute('aria-label', 'Cancel confirmation');
|
|
1313
1954
|
closeBtn.onclick = () => {
|
|
1314
1955
|
if (!isLoading) {
|
|
@@ -1324,6 +1965,7 @@ class ToastifyPro {
|
|
|
1324
1965
|
// Create icon wrapper
|
|
1325
1966
|
const iconWrapper = document.createElement("div");
|
|
1326
1967
|
iconWrapper.className = "toast-icon";
|
|
1968
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
1327
1969
|
iconWrapper.innerHTML = this.getIconSVG('info'); // Default to info icon
|
|
1328
1970
|
if (confirmOptions.primaryColor) {
|
|
1329
1971
|
iconWrapper.style.color = textColor;
|
|
@@ -1344,8 +1986,9 @@ class ToastifyPro {
|
|
|
1344
1986
|
contentWrapper.appendChild(messageElement);
|
|
1345
1987
|
|
|
1346
1988
|
// Optional description
|
|
1989
|
+
let descriptionElement = null;
|
|
1347
1990
|
if (description) {
|
|
1348
|
-
|
|
1991
|
+
descriptionElement = document.createElement("div");
|
|
1349
1992
|
descriptionElement.className = "toast-description";
|
|
1350
1993
|
descriptionElement.textContent = description.substring(0, this.defaultOptions.maxLength * 2);
|
|
1351
1994
|
if (confirmOptions.primaryColor) {
|
|
@@ -1363,6 +2006,7 @@ class ToastifyPro {
|
|
|
1363
2006
|
// Cancel button
|
|
1364
2007
|
const cancelBtn = document.createElement("button");
|
|
1365
2008
|
cancelBtn.className = "toast-btn toast-btn-cancel";
|
|
2009
|
+
cancelBtn.setAttribute('type', 'button');
|
|
1366
2010
|
cancelBtn.textContent = confirmOptions.cancelText;
|
|
1367
2011
|
cancelBtn.onclick = () => {
|
|
1368
2012
|
if (!isLoading) {
|
|
@@ -1384,18 +2028,25 @@ class ToastifyPro {
|
|
|
1384
2028
|
// Confirm button
|
|
1385
2029
|
const confirmBtn = document.createElement("button");
|
|
1386
2030
|
confirmBtn.className = `toast-btn toast-btn-confirm`;
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
const spinner = document.createElement("span");
|
|
1390
|
-
spinner.className = "btn-spinner";
|
|
1391
|
-
confirmBtn.appendChild(spinner);
|
|
1392
|
-
|
|
2031
|
+
confirmBtn.setAttribute('type', 'button');
|
|
2032
|
+
|
|
1393
2033
|
// Create text wrapper
|
|
1394
2034
|
const textWrapper = document.createElement("span");
|
|
1395
2035
|
textWrapper.className = "btn-text";
|
|
1396
2036
|
textWrapper.textContent = confirmOptions.confirmText;
|
|
1397
2037
|
confirmBtn.appendChild(textWrapper);
|
|
1398
2038
|
|
|
2039
|
+
// Create spinner element with custom SVG
|
|
2040
|
+
const spinner = document.createElement("span");
|
|
2041
|
+
spinner.className = "btn-spinner";
|
|
2042
|
+
spinner.innerHTML = `
|
|
2043
|
+
<svg width="25" height="25" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2044
|
+
<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" />
|
|
2045
|
+
<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" />
|
|
2046
|
+
</svg>
|
|
2047
|
+
`;
|
|
2048
|
+
confirmBtn.appendChild(spinner);
|
|
2049
|
+
|
|
1399
2050
|
confirmBtn.onclick = () => {
|
|
1400
2051
|
if (!isLoading) {
|
|
1401
2052
|
handleConfirmation(true);
|
|
@@ -1446,6 +2097,53 @@ class ToastifyPro {
|
|
|
1446
2097
|
setLoading(true);
|
|
1447
2098
|
}
|
|
1448
2099
|
|
|
2100
|
+
// ARIA accessibility for confirmation dialog
|
|
2101
|
+
toast.setAttribute('role', 'alertdialog');
|
|
2102
|
+
toast.setAttribute('aria-modal', 'true');
|
|
2103
|
+
toast.setAttribute('aria-labelledby', 'toast-conf-title');
|
|
2104
|
+
if (description) {
|
|
2105
|
+
toast.setAttribute('aria-describedby', 'toast-conf-desc');
|
|
2106
|
+
}
|
|
2107
|
+
messageElement.id = 'toast-conf-title';
|
|
2108
|
+
if (description && descriptionElement) {
|
|
2109
|
+
descriptionElement.id = 'toast-conf-desc';
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// Store previously focused element for restoration
|
|
2113
|
+
const previouslyFocused = document.activeElement;
|
|
2114
|
+
|
|
2115
|
+
// Focus trap for confirmation dialog
|
|
2116
|
+
const focusableElements = [cancelBtn, confirmBtn, closeBtn].filter(Boolean);
|
|
2117
|
+
let currentFocusIndex = 0;
|
|
2118
|
+
|
|
2119
|
+
const handleTabKey = (e) => {
|
|
2120
|
+
if (e.key === 'Tab' && toastElement && toastElement.parentNode) {
|
|
2121
|
+
e.preventDefault();
|
|
2122
|
+
if (e.shiftKey) {
|
|
2123
|
+
currentFocusIndex = (currentFocusIndex - 1 + focusableElements.length) % focusableElements.length;
|
|
2124
|
+
} else {
|
|
2125
|
+
currentFocusIndex = (currentFocusIndex + 1) % focusableElements.length;
|
|
2126
|
+
}
|
|
2127
|
+
focusableElements[currentFocusIndex]?.focus();
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
|
|
2131
|
+
document.addEventListener('keydown', handleTabKey);
|
|
2132
|
+
|
|
2133
|
+
// Store cleanup function
|
|
2134
|
+
const originalClose = closeConfirmation;
|
|
2135
|
+
const cleanupAndClose = () => {
|
|
2136
|
+
document.removeEventListener('keydown', handleTabKey);
|
|
2137
|
+
// Restore focus to previously focused element
|
|
2138
|
+
if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
|
|
2139
|
+
setTimeout(() => previouslyFocused.focus(), 100);
|
|
2140
|
+
}
|
|
2141
|
+
originalClose();
|
|
2142
|
+
};
|
|
2143
|
+
|
|
2144
|
+
// Update control object with enhanced close
|
|
2145
|
+
controlObject.close = cleanupAndClose;
|
|
2146
|
+
|
|
1449
2147
|
// Entrance animation
|
|
1450
2148
|
setTimeout(() => {
|
|
1451
2149
|
toast.classList.add("show");
|
|
@@ -1453,6 +2151,11 @@ class ToastifyPro {
|
|
|
1453
2151
|
if (icon) {
|
|
1454
2152
|
icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
|
1455
2153
|
}
|
|
2154
|
+
|
|
2155
|
+
// Focus the confirm button after animation
|
|
2156
|
+
setTimeout(() => {
|
|
2157
|
+
confirmBtn.focus();
|
|
2158
|
+
}, 100);
|
|
1456
2159
|
}, 10);
|
|
1457
2160
|
|
|
1458
2161
|
// Return control object with toast element and control functions
|