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