toastify-pro 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@
14
14
  * - Responsive design for mobile devices
15
15
  * - Framework agnostic (works with React, Vue, Angular, etc.)
16
16
  * - Confirmation dialogs with customizable buttons and callbacks
17
+ * - Input prompts with validation and async support
17
18
  * - Confirmation overlay with blur effect for focus
18
19
  * - Center position support for enhanced focus
19
20
  * - Independent positioning for confirmations
@@ -21,9 +22,10 @@
21
22
  * - Pause on hover functionality
22
23
  * - Queue management (maxToasts, newestOnTop)
23
24
  * - Full accessibility support (ARIA, keyboard navigation, reduced motion)
24
- * - Focus management for confirmation dialogs
25
+ * - Focus management for confirmation and input dialogs
26
+ * - Improved dismiss handling (no hover interference)
25
27
  *
26
- * @version 1.6.0
28
+ * @version 1.7.0
27
29
  * @author ToastifyPro Team
28
30
  * @license MIT
29
31
  */
@@ -264,19 +266,19 @@ class ToastifyPro {
264
266
  @keyframes airdropPop {
265
267
  0% {
266
268
  opacity: 0;
267
- transform: scale(0.3) rotateY(-20deg);
269
+ transform: scale(0.5) rotateY(-15deg) translateY(20px);
268
270
  }
269
- 30% {
270
- opacity: 0.8;
271
- transform: scale(1.1) rotateY(10deg);
271
+ 50% {
272
+ opacity: 0.95;
273
+ transform: scale(1.03) rotateY(5deg) translateY(-3px);
272
274
  }
273
- 60% {
275
+ 75% {
274
276
  opacity: 1;
275
- transform: scale(0.98) rotateY(-3deg);
277
+ transform: scale(0.99) rotateY(-1deg) translateY(1px);
276
278
  }
277
279
  100% {
278
280
  opacity: 1;
279
- transform: scale(1) rotateY(0deg);
281
+ transform: scale(1) rotateY(0deg) translateY(0);
280
282
  }
281
283
  }
282
284
 
@@ -285,13 +287,13 @@ class ToastifyPro {
285
287
  opacity: 1;
286
288
  transform: scale(1) translateY(0);
287
289
  }
288
- 15% {
290
+ 12% {
289
291
  opacity: 1;
290
- transform: scale(1.02) translateY(-8px);
292
+ transform: scale(1.015) translateY(-6px);
291
293
  }
292
294
  100% {
293
295
  opacity: 0;
294
- transform: scale(0.8) translateY(200px);
296
+ transform: scale(0.85) translateY(150px);
295
297
  }
296
298
  }
297
299
 
@@ -300,13 +302,13 @@ class ToastifyPro {
300
302
  opacity: 1;
301
303
  transform: scale(1) translateY(0);
302
304
  }
303
- 15% {
305
+ 12% {
304
306
  opacity: 1;
305
- transform: scale(1.02) translateY(8px);
307
+ transform: scale(1.015) translateY(6px);
306
308
  }
307
309
  100% {
308
310
  opacity: 0;
309
- transform: scale(0.8) translateY(-200px);
311
+ transform: scale(0.85) translateY(-150px);
310
312
  }
311
313
  }
312
314
 
@@ -315,13 +317,13 @@ class ToastifyPro {
315
317
  opacity: 1;
316
318
  transform: scale(1) translateX(0);
317
319
  }
318
- 15% {
320
+ 12% {
319
321
  opacity: 1;
320
- transform: scale(1.02) translateX(8px);
322
+ transform: scale(1.015) translateX(6px);
321
323
  }
322
324
  100% {
323
325
  opacity: 0;
324
- transform: scale(0.8) translateX(-300px);
326
+ transform: scale(0.85) translateX(-250px);
325
327
  }
326
328
  }
327
329
 
@@ -330,13 +332,13 @@ class ToastifyPro {
330
332
  opacity: 1;
331
333
  transform: scale(1) translateX(0);
332
334
  }
333
- 15% {
335
+ 12% {
334
336
  opacity: 1;
335
- transform: scale(1.02) translateX(-8px);
337
+ transform: scale(1.015) translateX(-6px);
336
338
  }
337
339
  100% {
338
340
  opacity: 0;
339
- transform: scale(0.8) translateX(300px);
341
+ transform: scale(0.85) translateX(250px);
340
342
  }
341
343
  }
342
344
 
@@ -345,13 +347,13 @@ class ToastifyPro {
345
347
  opacity: 1;
346
348
  transform: scale(1) translateY(0);
347
349
  }
348
- 15% {
350
+ 12% {
349
351
  opacity: 1;
350
- transform: scale(1.02) translateY(-5px);
352
+ transform: scale(1.015) translateY(-4px);
351
353
  }
352
354
  100% {
353
355
  opacity: 0;
354
- transform: scale(0.6) translateY(150px);
356
+ transform: scale(0.7) translateY(120px);
355
357
  }
356
358
  }
357
359
 
@@ -388,7 +390,7 @@ class ToastifyPro {
388
390
  .toastify-pro.show {
389
391
  opacity: 1;
390
392
  transform: scale(1);
391
- animation: airdropPop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
393
+ animation: airdropPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
392
394
  }
393
395
 
394
396
  .toastify-pro.success {
@@ -467,9 +469,9 @@ class ToastifyPro {
467
469
  }
468
470
 
469
471
  @keyframes iconBounce {
470
- 0% { transform: scale(0.2) rotate(-15deg); }
471
- 40% { transform: scale(1.2) rotate(8deg); }
472
- 70% { transform: scale(0.95) rotate(-3deg); }
472
+ 0% { transform: scale(0.3) rotate(-10deg); opacity: 0; }
473
+ 50% { transform: scale(1.1) rotate(5deg); opacity: 1; }
474
+ 70% { transform: scale(0.97) rotate(-2deg); }
473
475
  100% { transform: scale(1) rotate(0deg); }
474
476
  }
475
477
 
@@ -478,12 +480,12 @@ class ToastifyPro {
478
480
  transform: scale(1) rotate(0deg);
479
481
  opacity: 1;
480
482
  }
481
- 20% {
482
- transform: scale(1.1) rotate(-10deg);
483
- opacity: 0.9;
483
+ 25% {
484
+ transform: scale(1.08) rotate(-8deg);
485
+ opacity: 0.85;
484
486
  }
485
487
  100% {
486
- transform: scale(0.3) rotate(180deg);
488
+ transform: scale(0.4) rotate(120deg);
487
489
  opacity: 0;
488
490
  }
489
491
  }
@@ -492,6 +494,11 @@ class ToastifyPro {
492
494
  0% { transform: rotate(0deg); }
493
495
  100% { transform: rotate(360deg); }
494
496
  }
497
+
498
+ @keyframes spinFast {
499
+ 0% { transform: rotate(0deg); }
500
+ 100% { transform: rotate(360deg); }
501
+ }
495
502
 
496
503
  .toastify-pro .toast-icon svg {
497
504
  width: 18px;
@@ -816,7 +823,7 @@ class ToastifyPro {
816
823
  .toast-btn-confirm .btn-spinner svg {
817
824
  width: 25px;
818
825
  height: 25px;
819
- animation: spin 1s linear infinite;
826
+ animation: spinFast 0.5s linear infinite;
820
827
  color: currentColor;
821
828
  }
822
829
 
@@ -952,6 +959,11 @@ class ToastifyPro {
952
959
  animation-play-state: paused;
953
960
  }
954
961
 
962
+ /* Progress restart - used after hover to restart progress bar only */
963
+ .toastify-pro.progress-restart::after {
964
+ animation: none;
965
+ }
966
+
955
967
  /* Focus styles for accessibility */
956
968
  .toastify-pro .close-btn:focus,
957
969
  .toastify-pro .toast-action:focus,
@@ -977,6 +989,240 @@ class ToastifyPro {
977
989
  border: 0;
978
990
  }
979
991
 
992
+ /* ===== INPUT TOAST STYLES ===== */
993
+ .toastify-pro.input-toast {
994
+ min-width: 340px;
995
+ max-width: 450px;
996
+ padding: 24px 24px 20px;
997
+ flex-direction: column;
998
+ align-items: stretch;
999
+ gap: 16px;
1000
+ }
1001
+
1002
+ .toastify-pro.input-toast .toast-input-wrapper {
1003
+ display: flex;
1004
+ flex-direction: column;
1005
+ gap: 16px;
1006
+ width: 100%;
1007
+ }
1008
+
1009
+ .toastify-pro.input-toast .toast-input-header {
1010
+ display: flex;
1011
+ align-items: flex-start;
1012
+ gap: 12px;
1013
+ }
1014
+
1015
+ .toastify-pro.input-toast .toast-input {
1016
+ width: 100%;
1017
+ padding: 12px 16px;
1018
+ border: 1.5px solid rgba(255, 255, 255, 0.2);
1019
+ border-radius: 12px;
1020
+ background: rgba(255, 255, 255, 0.08);
1021
+ backdrop-filter: blur(12px);
1022
+ color: inherit;
1023
+ font-family: inherit;
1024
+ font-size: 14px;
1025
+ font-weight: 450;
1026
+ outline: none;
1027
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
1028
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
1029
+ }
1030
+
1031
+ .toastify-pro.input-toast .toast-input::placeholder {
1032
+ color: rgba(255, 255, 255, 0.5);
1033
+ font-weight: 400;
1034
+ }
1035
+
1036
+ .toastify-pro.input-toast .toast-input:focus {
1037
+ border-color: rgba(255, 255, 255, 0.45);
1038
+ background: rgba(255, 255, 255, 0.12);
1039
+ box-shadow:
1040
+ inset 0 1px 2px rgba(0, 0, 0, 0.1),
1041
+ 0 0 0 3px rgba(255, 255, 255, 0.08);
1042
+ }
1043
+
1044
+ .toastify-pro.input-toast .toast-input:hover:not(:focus) {
1045
+ border-color: rgba(255, 255, 255, 0.3);
1046
+ background: rgba(255, 255, 255, 0.1);
1047
+ }
1048
+
1049
+ .toastify-pro.input-toast.light .toast-input {
1050
+ border-color: rgba(15, 23, 42, 0.15);
1051
+ background: rgba(15, 23, 42, 0.04);
1052
+ box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.06);
1053
+ }
1054
+
1055
+ .toastify-pro.input-toast.light .toast-input::placeholder {
1056
+ color: rgba(15, 23, 42, 0.45);
1057
+ }
1058
+
1059
+ .toastify-pro.input-toast.light .toast-input:focus {
1060
+ border-color: rgba(15, 23, 42, 0.35);
1061
+ background: rgba(15, 23, 42, 0.06);
1062
+ box-shadow:
1063
+ inset 0 1px 2px rgba(15, 23, 42, 0.06),
1064
+ 0 0 0 3px rgba(15, 23, 42, 0.06);
1065
+ }
1066
+
1067
+ .toastify-pro.input-toast.light .toast-input:hover:not(:focus) {
1068
+ border-color: rgba(15, 23, 42, 0.25);
1069
+ background: rgba(15, 23, 42, 0.06);
1070
+ }
1071
+
1072
+ .toastify-pro.input-toast .toast-input-actions {
1073
+ display: flex;
1074
+ gap: 10px;
1075
+ justify-content: flex-end;
1076
+ }
1077
+
1078
+ .toastify-pro.input-toast .input-btn {
1079
+ padding: 10px 20px;
1080
+ border-radius: 10px;
1081
+ font-size: 14px;
1082
+ font-weight: 550;
1083
+ cursor: pointer;
1084
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
1085
+ border: 1.5px solid transparent;
1086
+ position: relative;
1087
+ overflow: hidden;
1088
+ }
1089
+
1090
+ .toastify-pro.input-toast .input-btn-cancel {
1091
+ background: rgba(255, 255, 255, 0.1);
1092
+ color: rgba(255, 255, 255, 0.9);
1093
+ border-color: rgba(255, 255, 255, 0.2);
1094
+ }
1095
+
1096
+ .toastify-pro.input-toast .input-btn-cancel:hover {
1097
+ background: rgba(255, 255, 255, 0.18);
1098
+ border-color: rgba(255, 255, 255, 0.3);
1099
+ }
1100
+
1101
+ .toastify-pro.input-toast .input-btn-submit {
1102
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%);
1103
+ color: #1e293b;
1104
+ border-color: rgba(255, 255, 255, 0.4);
1105
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
1106
+ }
1107
+
1108
+ .toastify-pro.input-toast .input-btn-submit:hover {
1109
+ background: linear-gradient(135deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.95) 100%);
1110
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
1111
+ transform: translateY(-1px);
1112
+ }
1113
+
1114
+ .toastify-pro.input-toast .input-btn-submit:active {
1115
+ transform: translateY(0);
1116
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
1117
+ }
1118
+
1119
+ .toastify-pro.input-toast.light .input-btn-cancel {
1120
+ background: rgba(15, 23, 42, 0.06);
1121
+ color: rgba(15, 23, 42, 0.85);
1122
+ border-color: rgba(15, 23, 42, 0.15);
1123
+ }
1124
+
1125
+ .toastify-pro.input-toast.light .input-btn-cancel:hover {
1126
+ background: rgba(15, 23, 42, 0.1);
1127
+ border-color: rgba(15, 23, 42, 0.25);
1128
+ }
1129
+
1130
+ .toastify-pro.input-toast.light .input-btn-submit {
1131
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
1132
+ color: white;
1133
+ border-color: rgba(15, 23, 42, 0.3);
1134
+ }
1135
+
1136
+ .toastify-pro.input-toast.light .input-btn-submit:hover {
1137
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
1138
+ box-shadow: 0 4px 16px rgba(15, 23, 42, 0.25);
1139
+ }
1140
+
1141
+ /* Input toast loading state */
1142
+ .toastify-pro.input-toast .input-btn-submit.loading {
1143
+ opacity: 0.7;
1144
+ cursor: not-allowed;
1145
+ pointer-events: none;
1146
+ }
1147
+
1148
+ .toastify-pro.input-toast .input-btn-submit .btn-spinner {
1149
+ display: none;
1150
+ align-items: center;
1151
+ justify-content: center;
1152
+ margin-left: 6px;
1153
+ }
1154
+
1155
+ .toastify-pro.input-toast .input-btn-submit .btn-spinner svg {
1156
+ width: 16px;
1157
+ height: 16px;
1158
+ animation: spinFast 0.5s linear infinite;
1159
+ }
1160
+
1161
+ .toastify-pro.input-toast .input-btn-submit.loading .btn-spinner {
1162
+ display: inline-flex;
1163
+ }
1164
+
1165
+ .toastify-pro.input-toast .input-btn-submit.loading .btn-text {
1166
+ opacity: 0.7;
1167
+ }
1168
+
1169
+ /* Input validation error state */
1170
+ .toastify-pro.input-toast .toast-input.error {
1171
+ border-color: rgba(239, 68, 68, 0.6);
1172
+ background: rgba(239, 68, 68, 0.08);
1173
+ animation: inputShake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97);
1174
+ }
1175
+
1176
+ .toastify-pro.input-toast .toast-input-error {
1177
+ color: #f87171;
1178
+ font-size: 12px;
1179
+ font-weight: 450;
1180
+ margin-top: -8px;
1181
+ opacity: 0;
1182
+ transform: translateY(-4px);
1183
+ transition: all 0.2s ease;
1184
+ }
1185
+
1186
+ .toastify-pro.input-toast .toast-input-error.visible {
1187
+ opacity: 1;
1188
+ transform: translateY(0);
1189
+ }
1190
+
1191
+ .toastify-pro.input-toast.light .toast-input.error {
1192
+ border-color: rgba(239, 68, 68, 0.5);
1193
+ background: rgba(239, 68, 68, 0.06);
1194
+ }
1195
+
1196
+ .toastify-pro.input-toast.light .toast-input-error {
1197
+ color: #dc2626;
1198
+ }
1199
+
1200
+ @keyframes inputShake {
1201
+ 0%, 100% { transform: translateX(0); }
1202
+ 20%, 60% { transform: translateX(-6px); }
1203
+ 40%, 80% { transform: translateX(6px); }
1204
+ }
1205
+
1206
+ /* Hide progress bar for input toasts */
1207
+ .toastify-pro.input-toast::after {
1208
+ display: none;
1209
+ }
1210
+
1211
+ @media (max-width: 640px) {
1212
+ .toastify-pro.input-toast {
1213
+ min-width: 280px;
1214
+ max-width: calc(100vw - 32px);
1215
+ }
1216
+
1217
+ .toastify-pro.input-toast .toast-input-actions {
1218
+ flex-direction: column;
1219
+ }
1220
+
1221
+ .toastify-pro.input-toast .input-btn {
1222
+ width: 100%;
1223
+ }
1224
+ }
1225
+
980
1226
  /* Reduced motion support */
981
1227
  @media (prefers-reduced-motion: reduce) {
982
1228
  .toastify-pro {
@@ -1019,7 +1265,7 @@ class ToastifyPro {
1019
1265
  }
1020
1266
 
1021
1267
  .btn-spinner svg {
1022
- animation: spin 1.5s linear infinite !important;
1268
+ animation: spinFast 0.5s linear infinite !important;
1023
1269
  }
1024
1270
  }
1025
1271
  `;
@@ -1165,7 +1411,8 @@ class ToastifyPro {
1165
1411
  timeout: null,
1166
1412
  remainingTime: options.timeout,
1167
1413
  startTime: null,
1168
- isPaused: false
1414
+ isPaused: false,
1415
+ isRemoving: false // Flag to prevent hover interference during removal
1169
1416
  };
1170
1417
  this.activeToasts.push(toastData);
1171
1418
 
@@ -1182,6 +1429,8 @@ class ToastifyPro {
1182
1429
  // Pause on hover functionality
1183
1430
  if (options.pauseOnHover && options.timeout > 0) {
1184
1431
  toast.addEventListener('mouseenter', () => {
1432
+ // Don't pause if toast is being removed
1433
+ if (toastData.isRemoving) return;
1185
1434
  if (toastData.timeout) {
1186
1435
  clearTimeout(toastData.timeout);
1187
1436
  toastData.isPaused = true;
@@ -1191,17 +1440,19 @@ class ToastifyPro {
1191
1440
  });
1192
1441
 
1193
1442
  toast.addEventListener('mouseleave', () => {
1443
+ // Don't restart timer if toast is being removed
1444
+ if (toastData.isRemoving) return;
1194
1445
  if (toastData.isPaused && toastData.remainingTime > 0) {
1195
1446
  toastData.isPaused = false;
1196
1447
  toastData.startTime = Date.now();
1197
1448
  toast.classList.remove('paused');
1198
1449
  // Update CSS variable for remaining progress
1199
1450
  toast.style.setProperty('--duration', `${toastData.remainingTime}ms`);
1200
- // Restart the progress animation
1201
- const afterElement = toast.querySelector('::after');
1202
- toast.style.animation = 'none';
1451
+ // Restart the progress bar animation only (not the main toast animation)
1452
+ // Using class toggle to reset pseudo-element animation without affecting main element
1453
+ toast.classList.add('progress-restart');
1203
1454
  void toast.offsetHeight; // Force reflow
1204
- toast.style.animation = '';
1455
+ toast.classList.remove('progress-restart');
1205
1456
 
1206
1457
  toastData.timeout = setTimeout(() => this.removeToast(toast), toastData.remainingTime);
1207
1458
  }
@@ -1275,16 +1526,25 @@ class ToastifyPro {
1275
1526
  }
1276
1527
 
1277
1528
  try {
1278
- // Remove from active toasts tracking
1529
+ // Remove from active toasts tracking and set removal flag
1279
1530
  const toastIndex = this.activeToasts.findIndex(t => t.element === toast);
1280
1531
  if (toastIndex > -1) {
1281
1532
  const toastData = this.activeToasts[toastIndex];
1533
+ // Prevent hover events from interfering during removal
1534
+ toastData.isRemoving = true;
1282
1535
  if (toastData.timeout) {
1283
1536
  clearTimeout(toastData.timeout);
1284
1537
  }
1285
1538
  this.activeToasts.splice(toastIndex, 1);
1286
1539
  }
1287
1540
 
1541
+ // Mark the toast element as removing to prevent double-removal
1542
+ if (toast.dataset.removing === 'true') return;
1543
+ toast.dataset.removing = 'true';
1544
+
1545
+ // Disable pointer events during exit animation to prevent hover issues
1546
+ toast.style.pointerEvents = 'none';
1547
+
1288
1548
  // Detect position to choose the right swipe direction
1289
1549
  const container = toast.parentNode;
1290
1550
  const position = container.className.split(' ')[1]; // get position class
@@ -1305,12 +1565,12 @@ class ToastifyPro {
1305
1565
  }
1306
1566
 
1307
1567
  // Apply fast car swipe animation with improved easing
1308
- toast.style.animation = `${swipeAnimation} 0.4s cubic-bezier(0.4, 0.0, 1, 1) forwards`;
1568
+ toast.style.animation = `${swipeAnimation} 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards`;
1309
1569
 
1310
1570
  // Add spinning icon animation for extra polish
1311
1571
  const icon = toast.querySelector('.toast-icon');
1312
1572
  if (icon) {
1313
- icon.style.animation = 'iconCarExit 0.4s cubic-bezier(0.4, 0.0, 1, 1) forwards';
1573
+ icon.style.animation = 'iconCarExit 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards';
1314
1574
  }
1315
1575
 
1316
1576
  // Remove element after animation completes
@@ -1318,7 +1578,7 @@ class ToastifyPro {
1318
1578
  if (toast.parentNode) {
1319
1579
  toast.remove();
1320
1580
  }
1321
- }, 400);
1581
+ }, 350);
1322
1582
  } catch (error) {
1323
1583
  console.error('ToastifyPro: Error removing toast:', error);
1324
1584
  // Fallback: remove immediately if animation fails
@@ -2174,6 +2434,527 @@ class ToastifyPro {
2174
2434
  confirm(message, descriptionOrCallback, callback) {
2175
2435
  return this.conf(message, descriptionOrCallback, callback);
2176
2436
  }
2437
+
2438
+ /**
2439
+ * Shows an input prompt toast with a text field
2440
+ * @param {string} message - Main prompt question
2441
+ * @param {string|Function|Object} descriptionOrCallback - Description text, callback function, or options object
2442
+ * @param {Function} callback - Callback function (if description provided)
2443
+ * @returns {Object} Control object with element, close, setValue, getValue methods
2444
+ *
2445
+ * Options object:
2446
+ * - description: {string} Optional description text
2447
+ * - placeholder: {string} Input placeholder text (default: 'Enter your response...')
2448
+ * - submitText: {string} Submit button text (default: 'Submit')
2449
+ * - cancelText: {string} Cancel button text (default: 'Cancel')
2450
+ * - defaultValue: {string} Default input value
2451
+ * - required: {boolean} Whether input is required (default: true)
2452
+ * - type: {string} Input type: 'text', 'email', 'number', 'password', 'url' (default: 'text')
2453
+ * - validate: {Function} Custom validation function (receives value, returns true or error string)
2454
+ * - onSubmit: {Function} Called when user submits (receives value, control object)
2455
+ * - onCancel: {Function} Called when user cancels
2456
+ * - theme: {string} Toast theme: 'dark' or 'light' (default: 'dark')
2457
+ * - position: {string} Override default position
2458
+ * - overlay: {boolean} Show overlay behind toast (default: true)
2459
+ * - primaryColor: {string} Custom primary color
2460
+ * - secondaryColor: {string} Custom secondary color for gradient
2461
+ *
2462
+ * @example
2463
+ * // Simple usage
2464
+ * toast.input('What is your name?', (value) => console.log(value));
2465
+ *
2466
+ * // With description
2467
+ * toast.input('Enter your email', 'We will send you updates', (value) => {...});
2468
+ *
2469
+ * // Full options
2470
+ * toast.input('Enter password', {
2471
+ * type: 'password',
2472
+ * placeholder: 'Minimum 8 characters',
2473
+ * validate: (val) => val.length >= 8 || 'Password must be at least 8 characters',
2474
+ * onSubmit: (value) => { ... },
2475
+ * theme: 'light'
2476
+ * });
2477
+ */
2478
+ input(message, descriptionOrCallback, callback) {
2479
+ try {
2480
+ // Parse arguments like confirm method
2481
+ let description = '';
2482
+ let options = {};
2483
+ let resultCallback = null;
2484
+
2485
+ if (typeof descriptionOrCallback === 'string') {
2486
+ description = descriptionOrCallback;
2487
+ if (typeof callback === 'function') {
2488
+ resultCallback = callback;
2489
+ } else if (typeof callback === 'object') {
2490
+ options = callback || {};
2491
+ resultCallback = options.onSubmit || null;
2492
+ }
2493
+ } else if (typeof descriptionOrCallback === 'function') {
2494
+ resultCallback = descriptionOrCallback;
2495
+ } else if (typeof descriptionOrCallback === 'object') {
2496
+ options = descriptionOrCallback || {};
2497
+ description = options.description || '';
2498
+ resultCallback = options.onSubmit || null;
2499
+ }
2500
+
2501
+ // Default options
2502
+ const inputOptions = {
2503
+ placeholder: options.placeholder || 'Enter your response...',
2504
+ submitText: options.submitText || 'Submit',
2505
+ cancelText: options.cancelText || 'Cancel',
2506
+ defaultValue: options.defaultValue || '',
2507
+ required: options.required !== false, // default true
2508
+ type: options.type || 'text',
2509
+ validate: options.validate || null,
2510
+ onCancel: options.onCancel || null,
2511
+ theme: options.theme || 'dark',
2512
+ position: options.position || this.defaultOptions.position,
2513
+ overlay: options.overlay !== false, // default true
2514
+ primaryColor: options.primaryColor || null,
2515
+ secondaryColor: options.secondaryColor || null,
2516
+ };
2517
+
2518
+ // Validate input type
2519
+ const validTypes = ['text', 'email', 'number', 'password', 'url', 'tel'];
2520
+ if (!validTypes.includes(inputOptions.type)) {
2521
+ inputOptions.type = 'text';
2522
+ }
2523
+
2524
+ // Create or get container for the specified position
2525
+ let inputContainer;
2526
+ const positionClass = inputOptions.position.replace(' ', '-');
2527
+ const existingContainer = document.querySelector(
2528
+ `.toastify-pro-container.${positionClass}`
2529
+ );
2530
+
2531
+ if (existingContainer) {
2532
+ inputContainer = existingContainer;
2533
+ } else {
2534
+ inputContainer = document.createElement("div");
2535
+ inputContainer.className = `toastify-pro-container ${positionClass}`;
2536
+ document.body.appendChild(inputContainer);
2537
+ }
2538
+
2539
+ // Create overlay if enabled
2540
+ let overlay = null;
2541
+ if (inputOptions.overlay) {
2542
+ overlay = document.createElement("div");
2543
+ overlay.className = "toastify-pro-overlay";
2544
+ document.body.appendChild(overlay);
2545
+ setTimeout(() => overlay.classList.add("visible"), 10);
2546
+ }
2547
+
2548
+ // Create input toast element
2549
+ const toast = document.createElement("div");
2550
+ toast.className = `toastify-pro input-toast ${inputOptions.theme}`;
2551
+ toast.setAttribute('role', 'dialog');
2552
+ toast.setAttribute('aria-modal', 'true');
2553
+ toast.setAttribute('aria-labelledby', 'toast-input-title');
2554
+
2555
+ // Track state
2556
+ let isLoading = false;
2557
+ let isClosed = false;
2558
+ let inputElement = null;
2559
+ let submitBtnElement = null;
2560
+ let cancelBtnElement = null;
2561
+ let errorElement = null;
2562
+
2563
+ // Helper: Check if a color is light or dark
2564
+ const isLightColor = (color) => {
2565
+ if (!color) return false;
2566
+ const hex = color.replace('#', '');
2567
+ if (hex.length === 3) {
2568
+ const r = parseInt(hex[0] + hex[0], 16);
2569
+ const g = parseInt(hex[1] + hex[1], 16);
2570
+ const b = parseInt(hex[2] + hex[2], 16);
2571
+ return (r * 299 + g * 587 + b * 114) / 1000 > 128;
2572
+ }
2573
+ const r = parseInt(hex.substr(0, 2), 16);
2574
+ const g = parseInt(hex.substr(2, 2), 16);
2575
+ const b = parseInt(hex.substr(4, 2), 16);
2576
+ return (r * 299 + g * 587 + b * 114) / 1000 > 128;
2577
+ };
2578
+
2579
+ // Apply custom colors
2580
+ let textColor = inputOptions.theme === 'light' ? '#1e293b' : 'white';
2581
+ if (inputOptions.primaryColor) {
2582
+ const primary = inputOptions.primaryColor;
2583
+ const secondary = inputOptions.secondaryColor;
2584
+
2585
+ if (secondary) {
2586
+ toast.style.background = `linear-gradient(135deg, ${primary} 0%, ${secondary} 100%)`;
2587
+ } else {
2588
+ toast.style.background = `linear-gradient(135deg, ${primary} 0%, ${primary}dd 100%)`;
2589
+ }
2590
+
2591
+ textColor = isLightColor(primary) ? '#1e293b' : 'white';
2592
+ toast.style.color = textColor;
2593
+
2594
+ const borderOpacity = isLightColor(primary) ? '0.2' : '0.15';
2595
+ toast.style.borderColor = `rgba(255, 255, 255, ${borderOpacity})`;
2596
+ }
2597
+
2598
+ // Set loading state
2599
+ const setLoading = (loading) => {
2600
+ isLoading = loading;
2601
+ if (submitBtnElement) {
2602
+ if (loading) {
2603
+ submitBtnElement.classList.add('loading');
2604
+ } else {
2605
+ submitBtnElement.classList.remove('loading');
2606
+ }
2607
+ }
2608
+ if (cancelBtnElement) {
2609
+ cancelBtnElement.disabled = loading;
2610
+ cancelBtnElement.style.opacity = loading ? '0.5' : '1';
2611
+ cancelBtnElement.style.cursor = loading ? 'not-allowed' : 'pointer';
2612
+ }
2613
+ if (inputElement) {
2614
+ inputElement.disabled = loading;
2615
+ inputElement.style.opacity = loading ? '0.6' : '1';
2616
+ }
2617
+ };
2618
+
2619
+ // Close the input toast
2620
+ const closeInput = () => {
2621
+ if (isClosed) return;
2622
+ isClosed = true;
2623
+
2624
+ // Remove overlay
2625
+ if (overlay) {
2626
+ overlay.classList.remove("visible");
2627
+ setTimeout(() => overlay.remove(), 300);
2628
+ }
2629
+
2630
+ // Exit animation
2631
+ toast.style.pointerEvents = 'none';
2632
+ toast.style.animation = 'carSwipeCenter 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards';
2633
+
2634
+ const icon = toast.querySelector('.toast-icon');
2635
+ if (icon) {
2636
+ icon.style.animation = 'iconCarExit 0.35s cubic-bezier(0.55, 0.0, 0.85, 0.36) forwards';
2637
+ }
2638
+
2639
+ setTimeout(() => {
2640
+ if (toast.parentNode) {
2641
+ toast.remove();
2642
+ }
2643
+ }, 350);
2644
+
2645
+ // Restore focus
2646
+ if (previouslyFocused && typeof previouslyFocused.focus === 'function') {
2647
+ setTimeout(() => previouslyFocused.focus(), 100);
2648
+ }
2649
+
2650
+ // Cleanup keyboard handler
2651
+ document.removeEventListener('keydown', handleKeyDown);
2652
+ };
2653
+
2654
+ // Validate input
2655
+ const validateInput = (value) => {
2656
+ // Required check
2657
+ if (inputOptions.required && !value.trim()) {
2658
+ return 'This field is required';
2659
+ }
2660
+
2661
+ // Type-specific validation
2662
+ if (value.trim()) {
2663
+ if (inputOptions.type === 'email') {
2664
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2665
+ if (!emailRegex.test(value)) {
2666
+ return 'Please enter a valid email address';
2667
+ }
2668
+ } else if (inputOptions.type === 'url') {
2669
+ try {
2670
+ new URL(value);
2671
+ } catch {
2672
+ return 'Please enter a valid URL';
2673
+ }
2674
+ } else if (inputOptions.type === 'number') {
2675
+ if (isNaN(Number(value))) {
2676
+ return 'Please enter a valid number';
2677
+ }
2678
+ }
2679
+ }
2680
+
2681
+ // Custom validation
2682
+ if (inputOptions.validate) {
2683
+ const customResult = inputOptions.validate(value);
2684
+ if (customResult !== true && customResult) {
2685
+ return customResult;
2686
+ }
2687
+ }
2688
+
2689
+ return null; // Valid
2690
+ };
2691
+
2692
+ // Show error message
2693
+ const showError = (message) => {
2694
+ if (errorElement) {
2695
+ errorElement.textContent = message;
2696
+ errorElement.classList.add('visible');
2697
+ inputElement.classList.add('error');
2698
+ }
2699
+ };
2700
+
2701
+ // Clear error message
2702
+ const clearError = () => {
2703
+ if (errorElement) {
2704
+ errorElement.classList.remove('visible');
2705
+ inputElement.classList.remove('error');
2706
+ }
2707
+ };
2708
+
2709
+ // Handle submit
2710
+ const handleSubmit = async () => {
2711
+ if (isLoading || isClosed) return;
2712
+
2713
+ const value = inputElement.value;
2714
+ const error = validateInput(value);
2715
+
2716
+ if (error) {
2717
+ showError(error);
2718
+ inputElement.focus();
2719
+ return;
2720
+ }
2721
+
2722
+ clearError();
2723
+
2724
+ if (resultCallback) {
2725
+ const result = resultCallback(value, { setLoading, close: closeInput, setValue: (v) => inputElement.value = v });
2726
+
2727
+ // Handle async callbacks
2728
+ if (result && typeof result.then === 'function') {
2729
+ setLoading(true);
2730
+ try {
2731
+ await result;
2732
+ if (!isClosed) {
2733
+ closeInput();
2734
+ }
2735
+ } catch (err) {
2736
+ setLoading(false);
2737
+ if (err && typeof err === 'string') {
2738
+ showError(err);
2739
+ } else if (err && err.message) {
2740
+ showError(err.message);
2741
+ }
2742
+ }
2743
+ } else if (!isLoading) {
2744
+ closeInput();
2745
+ }
2746
+ } else {
2747
+ closeInput();
2748
+ }
2749
+ };
2750
+
2751
+ // Handle cancel
2752
+ const handleCancel = () => {
2753
+ if (isLoading || isClosed) return;
2754
+
2755
+ if (inputOptions.onCancel) {
2756
+ inputOptions.onCancel();
2757
+ }
2758
+ closeInput();
2759
+ };
2760
+
2761
+ // Store previously focused element
2762
+ const previouslyFocused = document.activeElement;
2763
+
2764
+ // Keyboard handler
2765
+ const handleKeyDown = (e) => {
2766
+ if (isClosed) return;
2767
+
2768
+ if (e.key === 'Escape' && !isLoading) {
2769
+ handleCancel();
2770
+ } else if (e.key === 'Enter' && e.target === inputElement && !isLoading) {
2771
+ e.preventDefault();
2772
+ handleSubmit();
2773
+ }
2774
+ };
2775
+
2776
+ document.addEventListener('keydown', handleKeyDown);
2777
+
2778
+ // Build toast content
2779
+ // Header with icon and content
2780
+ const headerWrapper = document.createElement("div");
2781
+ headerWrapper.className = "toast-input-header";
2782
+
2783
+ // Icon
2784
+ const iconWrapper = document.createElement("div");
2785
+ iconWrapper.className = "toast-icon";
2786
+ iconWrapper.setAttribute('aria-hidden', 'true');
2787
+ iconWrapper.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2788
+ <path d="M11 4H4C3.46957 4 2.96086 4.21071 2.58579 4.58579C2.21071 4.96086 2 5.46957 2 6V20C2 20.5304 2.21071 21.0391 2.58579 21.4142C2.96086 21.7893 3.46957 22 4 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2789
+ <path d="M18.5 2.50001C18.8978 2.10219 19.4374 1.87869 20 1.87869C20.5626 1.87869 21.1022 2.10219 21.5 2.50001C21.8978 2.89784 22.1213 3.43739 22.1213 4.00001C22.1213 4.56262 21.8978 5.10219 21.5 5.50001L12 15L8 16L9 12L18.5 2.50001Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
2790
+ </svg>`;
2791
+ if (inputOptions.primaryColor) {
2792
+ iconWrapper.style.color = textColor;
2793
+ }
2794
+ headerWrapper.appendChild(iconWrapper);
2795
+
2796
+ // Content wrapper
2797
+ const contentWrapper = document.createElement("div");
2798
+ contentWrapper.className = "toast-content";
2799
+
2800
+ // Message
2801
+ const messageElement = document.createElement("div");
2802
+ messageElement.className = "toast-message";
2803
+ messageElement.id = "toast-input-title";
2804
+ messageElement.textContent = message.substring(0, this.defaultOptions.maxLength);
2805
+ if (inputOptions.primaryColor) {
2806
+ messageElement.style.color = textColor;
2807
+ }
2808
+ contentWrapper.appendChild(messageElement);
2809
+
2810
+ // Description
2811
+ if (description) {
2812
+ const descriptionElement = document.createElement("div");
2813
+ descriptionElement.className = "toast-description";
2814
+ descriptionElement.id = "toast-input-desc";
2815
+ descriptionElement.textContent = description.substring(0, this.defaultOptions.maxLength * 2);
2816
+ if (inputOptions.primaryColor) {
2817
+ descriptionElement.style.color = textColor;
2818
+ }
2819
+ contentWrapper.appendChild(descriptionElement);
2820
+ toast.setAttribute('aria-describedby', 'toast-input-desc');
2821
+ }
2822
+
2823
+ headerWrapper.appendChild(contentWrapper);
2824
+
2825
+ // Input wrapper
2826
+ const inputWrapper = document.createElement("div");
2827
+ inputWrapper.className = "toast-input-wrapper";
2828
+ inputWrapper.appendChild(headerWrapper);
2829
+
2830
+ // Input field
2831
+ inputElement = document.createElement("input");
2832
+ inputElement.className = "toast-input";
2833
+ inputElement.type = inputOptions.type;
2834
+ inputElement.placeholder = inputOptions.placeholder;
2835
+ inputElement.value = inputOptions.defaultValue;
2836
+ inputElement.setAttribute('aria-label', message);
2837
+
2838
+ if (inputOptions.primaryColor) {
2839
+ const isLight = isLightColor(inputOptions.primaryColor);
2840
+ inputElement.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.2)' : 'rgba(255, 255, 255, 0.2)';
2841
+ inputElement.style.background = isLight ? 'rgba(15, 23, 42, 0.06)' : 'rgba(255, 255, 255, 0.08)';
2842
+ inputElement.style.color = textColor;
2843
+ }
2844
+
2845
+ // Clear error on input
2846
+ inputElement.addEventListener('input', () => {
2847
+ if (errorElement && errorElement.classList.contains('visible')) {
2848
+ clearError();
2849
+ }
2850
+ });
2851
+
2852
+ inputWrapper.appendChild(inputElement);
2853
+
2854
+ // Error message element
2855
+ errorElement = document.createElement("div");
2856
+ errorElement.className = "toast-input-error";
2857
+ errorElement.setAttribute('role', 'alert');
2858
+ inputWrapper.appendChild(errorElement);
2859
+
2860
+ // Actions wrapper
2861
+ const actionsWrapper = document.createElement("div");
2862
+ actionsWrapper.className = "toast-input-actions";
2863
+
2864
+ // Cancel button
2865
+ cancelBtnElement = document.createElement("button");
2866
+ cancelBtnElement.className = "input-btn input-btn-cancel";
2867
+ cancelBtnElement.type = "button";
2868
+ cancelBtnElement.textContent = inputOptions.cancelText;
2869
+ cancelBtnElement.onclick = handleCancel;
2870
+
2871
+ if (inputOptions.primaryColor) {
2872
+ const isLight = isLightColor(inputOptions.primaryColor);
2873
+ cancelBtnElement.style.background = isLight ? 'rgba(15, 23, 42, 0.08)' : 'rgba(255, 255, 255, 0.1)';
2874
+ cancelBtnElement.style.color = textColor;
2875
+ cancelBtnElement.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.2)' : 'rgba(255, 255, 255, 0.2)';
2876
+ }
2877
+
2878
+ // Submit button
2879
+ submitBtnElement = document.createElement("button");
2880
+ submitBtnElement.className = "input-btn input-btn-submit";
2881
+ submitBtnElement.type = "button";
2882
+
2883
+ const textWrapper = document.createElement("span");
2884
+ textWrapper.className = "btn-text";
2885
+ textWrapper.textContent = inputOptions.submitText;
2886
+ submitBtnElement.appendChild(textWrapper);
2887
+
2888
+ // Spinner
2889
+ const spinner = document.createElement("span");
2890
+ spinner.className = "btn-spinner";
2891
+ spinner.innerHTML = `<svg width="16" height="16" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2892
+ <path d="M9.5 2.9375V5.5625M9.5 13.4375V16.0625M2.9375 9.5H5.5625M13.4375 9.5H16.0625" stroke="currentColor" stroke-width="1.875" stroke-linecap="round" />
2893
+ <path d="M4.86011 4.85961L6.71627 6.71577M12.2847 12.2842L14.1409 14.1404M4.86011 14.1404L6.71627 12.2842M12.2847 6.71577L14.1409 4.85961" stroke="currentColor" stroke-width="1.875" stroke-linecap="round" />
2894
+ </svg>`;
2895
+ submitBtnElement.appendChild(spinner);
2896
+
2897
+ submitBtnElement.onclick = handleSubmit;
2898
+
2899
+ if (inputOptions.primaryColor) {
2900
+ const primary = inputOptions.primaryColor;
2901
+ const isLight = isLightColor(primary);
2902
+ submitBtnElement.style.background = isLight
2903
+ ? 'linear-gradient(135deg, #1e293b 0%, #334155 100%)'
2904
+ : 'linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%)';
2905
+ submitBtnElement.style.color = isLight ? 'white' : '#1e293b';
2906
+ submitBtnElement.style.borderColor = isLight ? 'rgba(15, 23, 42, 0.3)' : 'rgba(255, 255, 255, 0.4)';
2907
+ }
2908
+
2909
+ actionsWrapper.appendChild(cancelBtnElement);
2910
+ actionsWrapper.appendChild(submitBtnElement);
2911
+ inputWrapper.appendChild(actionsWrapper);
2912
+
2913
+ toast.appendChild(inputWrapper);
2914
+ inputContainer.appendChild(toast);
2915
+
2916
+ // Entrance animation
2917
+ setTimeout(() => {
2918
+ toast.classList.add("show");
2919
+ const icon = toast.querySelector('.toast-icon');
2920
+ if (icon) {
2921
+ icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
2922
+ }
2923
+
2924
+ // Focus input after animation
2925
+ setTimeout(() => {
2926
+ inputElement.focus();
2927
+ // Select default value if present
2928
+ if (inputOptions.defaultValue) {
2929
+ inputElement.select();
2930
+ }
2931
+ }, 150);
2932
+ }, 10);
2933
+
2934
+ // Return control object
2935
+ return {
2936
+ element: toast,
2937
+ close: closeInput,
2938
+ setLoading,
2939
+ getValue: () => inputElement.value,
2940
+ setValue: (value) => { inputElement.value = value; },
2941
+ setError: showError,
2942
+ clearError
2943
+ };
2944
+ } catch (error) {
2945
+ console.error('ToastifyPro: Failed to create input toast:', error);
2946
+ }
2947
+ }
2948
+
2949
+ /**
2950
+ * Alias for input() method - shows an input prompt
2951
+ * @param {string} message - Main prompt question
2952
+ * @param {string|Function|Object} descriptionOrCallback - Description text, callback function, or options object
2953
+ * @param {Function} callback - Callback function (if description provided)
2954
+ */
2955
+ prompt(message, descriptionOrCallback, callback) {
2956
+ return this.input(message, descriptionOrCallback, callback);
2957
+ }
2177
2958
  }
2178
2959
 
2179
2960
  /**