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