toastify-pro 1.5.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 +480 -26
- package/dist/toastify-pro.esm.js.map +1 -1
- package/dist/toastify-pro.umd.js +480 -26
- package/dist/toastify-pro.umd.js.map +1 -1
- package/dist/toastify-pro.umd.min.js +8 -3
- package/dist/toastify-pro.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/toastify-pro.js +480 -26
package/dist/toastify-pro.esm.js
CHANGED
|
@@ -17,8 +17,13 @@
|
|
|
17
17
|
* - Confirmation overlay with blur effect for focus
|
|
18
18
|
* - Center position support for enhanced focus
|
|
19
19
|
* - Independent positioning for confirmations
|
|
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
|
|
20
25
|
*
|
|
21
|
-
* @version 1.
|
|
26
|
+
* @version 1.6.0
|
|
22
27
|
* @author ToastifyPro Team
|
|
23
28
|
* @license MIT
|
|
24
29
|
*/
|
|
@@ -36,6 +41,10 @@ class ToastifyPro {
|
|
|
36
41
|
* @param {number} options.maxLength - Maximum message length
|
|
37
42
|
* @param {string} options.primaryColor - Primary color for custom() method
|
|
38
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')
|
|
39
48
|
*/
|
|
40
49
|
constructor(options = {}) {
|
|
41
50
|
// Validate options parameter
|
|
@@ -52,7 +61,14 @@ class ToastifyPro {
|
|
|
52
61
|
maxLength: options.maxLength || 100,
|
|
53
62
|
primaryColor: options.primaryColor || null, // Custom primary color for custom() method
|
|
54
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'
|
|
55
68
|
};
|
|
69
|
+
|
|
70
|
+
// Track active toasts for queue management
|
|
71
|
+
this.activeToasts = [];
|
|
56
72
|
|
|
57
73
|
// Validate position
|
|
58
74
|
const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center', 'center'];
|
|
@@ -85,6 +101,46 @@ class ToastifyPro {
|
|
|
85
101
|
|
|
86
102
|
// Inject styles once
|
|
87
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
|
+
});
|
|
88
144
|
}
|
|
89
145
|
|
|
90
146
|
/**
|
|
@@ -479,6 +535,7 @@ class ToastifyPro {
|
|
|
479
535
|
transition: all 0.2s ease;
|
|
480
536
|
flex-shrink: 0;
|
|
481
537
|
width: 32px;
|
|
538
|
+
border: none;
|
|
482
539
|
height: 32px;
|
|
483
540
|
display: flex;
|
|
484
541
|
align-items: center;
|
|
@@ -710,6 +767,9 @@ class ToastifyPro {
|
|
|
710
767
|
}
|
|
711
768
|
|
|
712
769
|
.toast-btn-confirm {
|
|
770
|
+
display: flex;
|
|
771
|
+
align-items: center;
|
|
772
|
+
justify-content: center;
|
|
713
773
|
color: white;
|
|
714
774
|
font-weight: 700;
|
|
715
775
|
border: 2px solid rgba(255, 255, 255, 0.4);
|
|
@@ -748,17 +808,20 @@ class ToastifyPro {
|
|
|
748
808
|
|
|
749
809
|
.toast-btn-confirm .btn-spinner {
|
|
750
810
|
display: none;
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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;
|
|
758
821
|
}
|
|
759
822
|
|
|
760
823
|
.toast-btn-confirm.loading .btn-spinner {
|
|
761
|
-
display: inline-
|
|
824
|
+
display: inline-flex;
|
|
762
825
|
}
|
|
763
826
|
|
|
764
827
|
.toast-btn-confirm.loading .btn-text {
|
|
@@ -848,6 +911,117 @@ class ToastifyPro {
|
|
|
848
911
|
.toastify-pro-overlay.show {
|
|
849
912
|
opacity: 1;
|
|
850
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
|
+
}
|
|
851
1025
|
`;
|
|
852
1026
|
document.head.appendChild(style);
|
|
853
1027
|
} catch (error) {
|
|
@@ -864,6 +1038,9 @@ class ToastifyPro {
|
|
|
864
1038
|
* @param {number} opts.timeout - Override default timeout
|
|
865
1039
|
* @param {boolean} opts.allowClose - Override close button setting
|
|
866
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')
|
|
867
1044
|
*/
|
|
868
1045
|
show(message, type = "dark", opts = {}) {
|
|
869
1046
|
// Input validation
|
|
@@ -893,10 +1070,30 @@ class ToastifyPro {
|
|
|
893
1070
|
const options = { ...this.defaultOptions, ...opts };
|
|
894
1071
|
|
|
895
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
|
+
|
|
896
1084
|
// Create toast element
|
|
897
1085
|
const toast = document.createElement("div");
|
|
898
1086
|
toast.className = `toastify-pro ${type}`;
|
|
899
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
|
+
|
|
900
1097
|
// Set duration for progress bar animation
|
|
901
1098
|
if (options.timeout > 0) {
|
|
902
1099
|
toast.style.setProperty('--duration', `${options.timeout}ms`);
|
|
@@ -905,6 +1102,7 @@ class ToastifyPro {
|
|
|
905
1102
|
// Create icon wrapper
|
|
906
1103
|
const iconWrapper = document.createElement("div");
|
|
907
1104
|
iconWrapper.className = "toast-icon";
|
|
1105
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
908
1106
|
iconWrapper.innerHTML = this.getIconSVG(type);
|
|
909
1107
|
toast.appendChild(iconWrapper);
|
|
910
1108
|
|
|
@@ -926,20 +1124,50 @@ class ToastifyPro {
|
|
|
926
1124
|
contentWrapper.appendChild(descriptionElement);
|
|
927
1125
|
}
|
|
928
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
|
+
|
|
929
1142
|
toast.appendChild(contentWrapper);
|
|
930
1143
|
|
|
931
1144
|
// Add close button if enabled
|
|
932
1145
|
if (options.allowClose) {
|
|
933
|
-
const closeBtn = document.createElement("
|
|
1146
|
+
const closeBtn = document.createElement("button");
|
|
934
1147
|
closeBtn.className = "close-btn";
|
|
935
1148
|
closeBtn.innerHTML = "×";
|
|
1149
|
+
closeBtn.setAttribute('type', 'button');
|
|
936
1150
|
closeBtn.setAttribute('aria-label', 'Close notification');
|
|
937
1151
|
closeBtn.onclick = () => this.removeToast(toast);
|
|
938
1152
|
toast.appendChild(closeBtn);
|
|
939
1153
|
}
|
|
940
1154
|
|
|
941
|
-
// Add toast to container
|
|
942
|
-
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);
|
|
943
1171
|
|
|
944
1172
|
// Apple AirDrop-style entrance animation
|
|
945
1173
|
setTimeout(() => {
|
|
@@ -951,16 +1179,90 @@ class ToastifyPro {
|
|
|
951
1179
|
}
|
|
952
1180
|
}, 10);
|
|
953
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
|
+
|
|
954
1211
|
// Auto-remove after timeout
|
|
955
1212
|
if (options.timeout > 0) {
|
|
956
|
-
|
|
1213
|
+
toastData.startTime = Date.now();
|
|
1214
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), options.timeout);
|
|
957
1215
|
}
|
|
958
1216
|
|
|
959
|
-
|
|
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
|
+
};
|
|
960
1223
|
} catch (error) {
|
|
961
1224
|
console.error('ToastifyPro: Failed to create toast:', error);
|
|
962
1225
|
}
|
|
963
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
|
+
}
|
|
964
1266
|
|
|
965
1267
|
/**
|
|
966
1268
|
* Removes a toast with position-aware car swipe animation
|
|
@@ -973,6 +1275,16 @@ class ToastifyPro {
|
|
|
973
1275
|
}
|
|
974
1276
|
|
|
975
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
|
+
|
|
976
1288
|
// Detect position to choose the right swipe direction
|
|
977
1289
|
const container = toast.parentNode;
|
|
978
1290
|
const position = container.className.split(' ')[1]; // get position class
|
|
@@ -1015,6 +1327,33 @@ class ToastifyPro {
|
|
|
1015
1327
|
}
|
|
1016
1328
|
}
|
|
1017
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
|
+
}
|
|
1018
1357
|
|
|
1019
1358
|
/**
|
|
1020
1359
|
* Shows a success toast notification
|
|
@@ -1189,6 +1528,14 @@ class ToastifyPro {
|
|
|
1189
1528
|
const toast = document.createElement("div");
|
|
1190
1529
|
toast.className = `toastify-pro custom${options.customTextLight ? ' light-text' : ''}`;
|
|
1191
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
|
+
|
|
1192
1539
|
// Apply custom gradient
|
|
1193
1540
|
if (options.customGradient) {
|
|
1194
1541
|
toast.style.background = options.customGradient;
|
|
@@ -1201,6 +1548,7 @@ class ToastifyPro {
|
|
|
1201
1548
|
// Create icon wrapper
|
|
1202
1549
|
const iconWrapper = document.createElement("div");
|
|
1203
1550
|
iconWrapper.className = "toast-icon";
|
|
1551
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
1204
1552
|
iconWrapper.innerHTML = this.getIconSVG('success'); // Use success icon for custom
|
|
1205
1553
|
toast.appendChild(iconWrapper);
|
|
1206
1554
|
|
|
@@ -1223,15 +1571,53 @@ class ToastifyPro {
|
|
|
1223
1571
|
toast.appendChild(contentWrapper);
|
|
1224
1572
|
|
|
1225
1573
|
if (options.allowClose) {
|
|
1226
|
-
const closeBtn = document.createElement("
|
|
1574
|
+
const closeBtn = document.createElement("button");
|
|
1227
1575
|
closeBtn.className = "close-btn";
|
|
1228
1576
|
closeBtn.innerHTML = "×";
|
|
1577
|
+
closeBtn.setAttribute('type', 'button');
|
|
1229
1578
|
closeBtn.setAttribute('aria-label', 'Close notification');
|
|
1230
1579
|
closeBtn.onclick = () => this.removeToast(toast);
|
|
1231
1580
|
toast.appendChild(closeBtn);
|
|
1232
1581
|
}
|
|
1233
1582
|
|
|
1234
|
-
|
|
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
|
+
}
|
|
1235
1621
|
|
|
1236
1622
|
setTimeout(() => {
|
|
1237
1623
|
toast.classList.add("show");
|
|
@@ -1242,10 +1628,15 @@ class ToastifyPro {
|
|
|
1242
1628
|
}, 10);
|
|
1243
1629
|
|
|
1244
1630
|
if (options.timeout > 0) {
|
|
1245
|
-
|
|
1631
|
+
toastData.startTime = Date.now();
|
|
1632
|
+
toastData.timeout = setTimeout(() => this.removeToast(toast), options.timeout);
|
|
1246
1633
|
}
|
|
1247
1634
|
|
|
1248
|
-
return
|
|
1635
|
+
return {
|
|
1636
|
+
element: toast,
|
|
1637
|
+
dismiss: () => this.removeToast(toast),
|
|
1638
|
+
update: (newMessage, newOpts) => this.updateToast(toast, newMessage, newOpts)
|
|
1639
|
+
};
|
|
1249
1640
|
} catch (error) {
|
|
1250
1641
|
console.error('ToastifyPro: Failed to create custom toast:', error);
|
|
1251
1642
|
}
|
|
@@ -1555,9 +1946,10 @@ class ToastifyPro {
|
|
|
1555
1946
|
}
|
|
1556
1947
|
|
|
1557
1948
|
// Create close button for confirmation
|
|
1558
|
-
const closeBtn = document.createElement("
|
|
1949
|
+
const closeBtn = document.createElement("button");
|
|
1559
1950
|
closeBtn.className = "conf-close-btn";
|
|
1560
1951
|
closeBtn.innerHTML = "×";
|
|
1952
|
+
closeBtn.setAttribute('type', 'button');
|
|
1561
1953
|
closeBtn.setAttribute('aria-label', 'Cancel confirmation');
|
|
1562
1954
|
closeBtn.onclick = () => {
|
|
1563
1955
|
if (!isLoading) {
|
|
@@ -1573,6 +1965,7 @@ class ToastifyPro {
|
|
|
1573
1965
|
// Create icon wrapper
|
|
1574
1966
|
const iconWrapper = document.createElement("div");
|
|
1575
1967
|
iconWrapper.className = "toast-icon";
|
|
1968
|
+
iconWrapper.setAttribute('aria-hidden', 'true');
|
|
1576
1969
|
iconWrapper.innerHTML = this.getIconSVG('info'); // Default to info icon
|
|
1577
1970
|
if (confirmOptions.primaryColor) {
|
|
1578
1971
|
iconWrapper.style.color = textColor;
|
|
@@ -1593,8 +1986,9 @@ class ToastifyPro {
|
|
|
1593
1986
|
contentWrapper.appendChild(messageElement);
|
|
1594
1987
|
|
|
1595
1988
|
// Optional description
|
|
1989
|
+
let descriptionElement = null;
|
|
1596
1990
|
if (description) {
|
|
1597
|
-
|
|
1991
|
+
descriptionElement = document.createElement("div");
|
|
1598
1992
|
descriptionElement.className = "toast-description";
|
|
1599
1993
|
descriptionElement.textContent = description.substring(0, this.defaultOptions.maxLength * 2);
|
|
1600
1994
|
if (confirmOptions.primaryColor) {
|
|
@@ -1612,6 +2006,7 @@ class ToastifyPro {
|
|
|
1612
2006
|
// Cancel button
|
|
1613
2007
|
const cancelBtn = document.createElement("button");
|
|
1614
2008
|
cancelBtn.className = "toast-btn toast-btn-cancel";
|
|
2009
|
+
cancelBtn.setAttribute('type', 'button');
|
|
1615
2010
|
cancelBtn.textContent = confirmOptions.cancelText;
|
|
1616
2011
|
cancelBtn.onclick = () => {
|
|
1617
2012
|
if (!isLoading) {
|
|
@@ -1633,18 +2028,25 @@ class ToastifyPro {
|
|
|
1633
2028
|
// Confirm button
|
|
1634
2029
|
const confirmBtn = document.createElement("button");
|
|
1635
2030
|
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
|
-
|
|
2031
|
+
confirmBtn.setAttribute('type', 'button');
|
|
2032
|
+
|
|
1642
2033
|
// Create text wrapper
|
|
1643
2034
|
const textWrapper = document.createElement("span");
|
|
1644
2035
|
textWrapper.className = "btn-text";
|
|
1645
2036
|
textWrapper.textContent = confirmOptions.confirmText;
|
|
1646
2037
|
confirmBtn.appendChild(textWrapper);
|
|
1647
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
|
+
|
|
1648
2050
|
confirmBtn.onclick = () => {
|
|
1649
2051
|
if (!isLoading) {
|
|
1650
2052
|
handleConfirmation(true);
|
|
@@ -1695,6 +2097,53 @@ class ToastifyPro {
|
|
|
1695
2097
|
setLoading(true);
|
|
1696
2098
|
}
|
|
1697
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
|
+
|
|
1698
2147
|
// Entrance animation
|
|
1699
2148
|
setTimeout(() => {
|
|
1700
2149
|
toast.classList.add("show");
|
|
@@ -1702,6 +2151,11 @@ class ToastifyPro {
|
|
|
1702
2151
|
if (icon) {
|
|
1703
2152
|
icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
|
1704
2153
|
}
|
|
2154
|
+
|
|
2155
|
+
// Focus the confirm button after animation
|
|
2156
|
+
setTimeout(() => {
|
|
2157
|
+
confirmBtn.focus();
|
|
2158
|
+
}, 100);
|
|
1705
2159
|
}, 10);
|
|
1706
2160
|
|
|
1707
2161
|
// Return control object with toast element and control functions
|