toastify-pro 1.0.3 → 1.2.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.
@@ -1,5 +1,39 @@
1
+ /**
2
+ * ToastifyPro - Modern Toast Notification Library
3
+ * A beautiful, customizable toast notification library with glassmorphism design,
4
+ * Apple AirDrop-style animations, and car swipe exit effects.
5
+ *
6
+ * Features:
7
+ * - Glassmorphism design with backdrop-filter effects
8
+ * - Apple AirDrop-style entrance animations
9
+ * - Position-aware car swipe exit animations
10
+ * - Description support for enhanced messaging
11
+ * - Six theme variants (success, error, info, warning, dark, light)
12
+ * - Progress bar with shimmer effects
13
+ * - Responsive design for mobile devices
14
+ * - Framework agnostic (works with React, Vue, Angular, etc.)
15
+ *
16
+ * @version 1.2.0
17
+ * @author ToastifyPro Team
18
+ * @license MIT
19
+ */
1
20
  class ToastifyPro {
21
+ /**
22
+ * Creates a new ToastifyPro instance
23
+ * @param {Object} options - Configuration options
24
+ * @param {string} options.position - Toast position (top-left, top-right, bottom-left, bottom-right, top-center, bottom-center)
25
+ * @param {number} options.timeout - Auto-dismiss timeout in milliseconds (0 to disable)
26
+ * @param {boolean} options.allowClose - Whether to show close button
27
+ * @param {number} options.maxLength - Maximum message length
28
+ */
2
29
  constructor(options = {}) {
30
+ // Validate options parameter
31
+ if (typeof options !== 'object' || options === null) {
32
+ console.warn('ToastifyPro: Invalid options parameter. Using defaults.');
33
+ options = {};
34
+ }
35
+
36
+ // Merge with default options
3
37
  this.defaultOptions = {
4
38
  position: options.position || "bottom-center", // top-left, top-right, bottom-left, bottom-right, top-center, bottom-center
5
39
  timeout: options.timeout || 3000,
@@ -7,151 +41,707 @@ class ToastifyPro {
7
41
  maxLength: options.maxLength || 100,
8
42
  };
9
43
 
10
- // create container only once
44
+ // Validate position
45
+ const validPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center'];
46
+ if (!validPositions.includes(this.defaultOptions.position)) {
47
+ console.warn(`ToastifyPro: Invalid position "${this.defaultOptions.position}". Using "bottom-center".`);
48
+ this.defaultOptions.position = "bottom-center";
49
+ }
50
+
51
+ // Check if we're in a browser environment
52
+ if (typeof document === 'undefined') {
53
+ throw new Error('ToastifyPro: This library requires a DOM environment (browser).');
54
+ }
55
+
56
+ // Create or reuse container for this position
11
57
  const existing = document.querySelector(
12
58
  `.toastify-pro-container.${this.defaultOptions.position}`
13
59
  );
60
+
14
61
  if (existing) {
15
62
  this.container = existing;
16
63
  } else {
17
- this.container = document.createElement("div");
18
- this.container.className = `toastify-pro-container ${this.defaultOptions.position}`;
19
- document.body.appendChild(this.container);
64
+ try {
65
+ this.container = document.createElement("div");
66
+ this.container.className = `toastify-pro-container ${this.defaultOptions.position}`;
67
+ document.body.appendChild(this.container);
68
+ } catch (error) {
69
+ throw new Error('ToastifyPro: Failed to create container element. DOM may not be ready.');
70
+ }
20
71
  }
21
72
 
73
+ // Inject styles once
22
74
  this.injectStyles();
23
75
  }
24
76
 
77
+ /**
78
+ * Returns the SVG icon for a given toast type
79
+ * @param {string} type - Toast type (success, error, info, warning, dark, light)
80
+ * @returns {string} SVG icon markup
81
+ */
82
+ getIconSVG(type) {
83
+ // Validate type parameter
84
+ if (typeof type !== 'string') {
85
+ console.warn('ToastifyPro: Invalid icon type. Using default info icon.');
86
+ type = 'info';
87
+ }
88
+
89
+ const icons = {
90
+ success: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
91
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="currentColor"/>
92
+ </svg>`,
93
+ error: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
94
+ <path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z" fill="currentColor"/>
95
+ </svg>`,
96
+ info: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
97
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z" fill="currentColor"/>
98
+ </svg>`,
99
+ warning: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
100
+ <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" fill="currentColor"/>
101
+ </svg>`,
102
+ dark: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
103
+ <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="currentColor"/>
104
+ </svg>`,
105
+ light: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
106
+ <path d="M9 11H7v6h2v-6zm4 0h-2v6h2v-6zm4 0h-2v6h2v-6zm2.5-9H19V1h-2v1H7V1H5v1H4.5C3.11 2 2 3.11 2 4.5v14C2 19.89 3.11 21 4.5 21h15c1.39 0 2.5-1.11 2.5-2.5v-14C22 3.11 20.89 2 19.5 2zm0 16h-15v-11h15v11z" fill="currentColor"/>
107
+ </svg>`
108
+ };
109
+
110
+ return icons[type] || icons.info;
111
+ }
112
+
113
+ /**
114
+ * Injects the CSS styles into the document head
115
+ * Styles include glassmorphism design, animations, and responsive layout
116
+ */
25
117
  injectStyles() {
26
- if (document.getElementById("toastify-pro-styles")) return; // load once
27
- const style = document.createElement("style");
28
- style.id = "toastify-pro-styles";
29
- style.textContent = `
118
+ // Prevent duplicate style injection
119
+ if (document.getElementById("toastify-pro-styles")) return;
120
+
121
+ try {
122
+ const style = document.createElement("style");
123
+ style.id = "toastify-pro-styles";
124
+ style.textContent = `
125
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
126
+
30
127
  .toastify-pro-container {
31
128
  position: fixed;
32
129
  z-index: 9999;
33
130
  display: flex;
34
131
  flex-direction: column;
35
- gap: 10px;
132
+ gap: 16px;
36
133
  pointer-events: none;
37
134
  }
38
- .toastify-pro-container.top-left { top: 20px; left: 20px; align-items: flex-start; }
39
- .toastify-pro-container.top-right { top: 20px; right: 20px; align-items: flex-end; }
40
- .toastify-pro-container.bottom-left { bottom: 20px; left: 20px; align-items: flex-start; }
41
- .toastify-pro-container.bottom-right { bottom: 20px; right: 20px; align-items: flex-end; }
42
- .toastify-pro-container.top-center { top: 20px; left: 50%; transform: translateX(-50%); }
43
- .toastify-pro-container.bottom-center { bottom: 150px; left: 50%; transform: translateX(-50%); }
135
+ .toastify-pro-container.top-left { top: 50px; left: 24px; align-items: flex-start; }
136
+ .toastify-pro-container.top-right { top: 50px; right: 24px; align-items: flex-end; }
137
+ .toastify-pro-container.bottom-left { bottom: 50px; left: 24px; align-items: flex-start; }
138
+ .toastify-pro-container.bottom-right { bottom: 50px; right: 24px; align-items: flex-end; }
139
+ .toastify-pro-container.top-center { top: 50px; left: 50%; transform: translateX(-50%); }
140
+ .toastify-pro-container.bottom-center { bottom: 50px; left: 50%; transform: translateX(-50%); }
44
141
 
45
142
  .toastify-pro {
46
- min-width: 220px;
47
- max-width: 320px;
48
- padding: 12px 18px;
49
- border-radius: 20px;
50
- font-size: 14px;
51
- font-family: sans-serif;
143
+ min-width: 280px;
144
+ max-width: 400px;
145
+ padding: 20px 24px;
146
+ border-radius: 16px;
147
+ font-size: 15px;
148
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
149
+ font-weight: 500;
52
150
  color: white;
53
151
  opacity: 0;
54
- transform: translateY(20px);
55
- transition: all 0.3s ease;
152
+ transform: scale(0.3);
153
+ transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
56
154
  pointer-events: auto;
57
155
  position: relative;
58
156
  display: flex;
59
157
  align-items: center;
60
- justify-content: space-between;
61
- gap: 12px;
158
+ gap: 16px;
159
+ backdrop-filter: blur(20px);
160
+ border: 1px solid rgba(255, 255, 255, 0.1);
161
+ box-shadow:
162
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
163
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04),
164
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
165
+ overflow: hidden;
166
+ }
167
+
168
+ .toastify-pro::before {
169
+ content: '';
170
+ position: absolute;
171
+ top: 0;
172
+ left: 0;
173
+ right: 0;
174
+ height: 3px;
175
+ background: linear-gradient(90deg,
176
+ rgba(255, 255, 255, 0.8) 0%,
177
+ rgba(255, 255, 255, 0.4) 50%,
178
+ rgba(255, 255, 255, 0.8) 100%);
179
+ animation: shimmer 2s infinite;
180
+ transition: opacity 0.8s ease;
181
+ }
182
+
183
+ .toastify-pro::after {
184
+ content: '';
185
+ position: absolute;
186
+ bottom: 0;
187
+ left: 0;
188
+ height: 3px;
189
+ background: rgba(255, 255, 255, 0.6);
190
+ animation: progress var(--duration, 5s) linear;
191
+ border-radius: 0 0 16px 16px;
192
+ }
193
+
194
+ @keyframes airdropPop {
195
+ 0% {
196
+ opacity: 0;
197
+ transform: scale(0.3) rotateY(-20deg);
198
+ }
199
+ 30% {
200
+ opacity: 0.8;
201
+ transform: scale(1.1) rotateY(10deg);
202
+ }
203
+ 60% {
204
+ opacity: 1;
205
+ transform: scale(0.98) rotateY(-3deg);
206
+ }
207
+ 100% {
208
+ opacity: 1;
209
+ transform: scale(1) rotateY(0deg);
210
+ }
211
+ }
212
+
213
+ @keyframes carSwipeBottom {
214
+ 0% {
215
+ opacity: 1;
216
+ transform: scale(1) translateY(0);
217
+ }
218
+ 15% {
219
+ opacity: 1;
220
+ transform: scale(1.02) translateY(-8px);
221
+ }
222
+ 100% {
223
+ opacity: 0;
224
+ transform: scale(0.8) translateY(200px);
225
+ }
226
+ }
227
+
228
+ @keyframes carSwipeTop {
229
+ 0% {
230
+ opacity: 1;
231
+ transform: scale(1) translateY(0);
232
+ }
233
+ 15% {
234
+ opacity: 1;
235
+ transform: scale(1.02) translateY(8px);
236
+ }
237
+ 100% {
238
+ opacity: 0;
239
+ transform: scale(0.8) translateY(-200px);
240
+ }
241
+ }
242
+
243
+ @keyframes carSwipeLeft {
244
+ 0% {
245
+ opacity: 1;
246
+ transform: scale(1) translateX(0);
247
+ }
248
+ 15% {
249
+ opacity: 1;
250
+ transform: scale(1.02) translateX(8px);
251
+ }
252
+ 100% {
253
+ opacity: 0;
254
+ transform: scale(0.8) translateX(-300px);
255
+ }
256
+ }
257
+
258
+ @keyframes carSwipeRight {
259
+ 0% {
260
+ opacity: 1;
261
+ transform: scale(1) translateX(0);
262
+ }
263
+ 15% {
264
+ opacity: 1;
265
+ transform: scale(1.02) translateX(-8px);
266
+ }
267
+ 100% {
268
+ opacity: 0;
269
+ transform: scale(0.8) translateX(300px);
270
+ }
271
+ }
272
+
273
+ @keyframes carSwipeCenter {
274
+ 0% {
275
+ opacity: 1;
276
+ transform: scale(1) translateY(0);
277
+ }
278
+ 15% {
279
+ opacity: 1;
280
+ transform: scale(1.02) translateY(-5px);
281
+ }
282
+ 100% {
283
+ opacity: 0;
284
+ transform: scale(0.6) translateY(150px);
285
+ }
286
+ }
287
+
288
+ @keyframes progress {
289
+ 0% { width: 100%; }
290
+ 100% { width: 0%; }
291
+ }
292
+
293
+ @keyframes shimmer {
294
+ 0% { transform: translateX(-100%); }
295
+ 100% { transform: translateX(100%); }
296
+ }
297
+
298
+ .toastify-pro.show {
299
+ opacity: 1;
300
+ transform: scale(1);
301
+ animation: airdropPop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
302
+ }
303
+
304
+ .toastify-pro.success {
305
+ background: linear-gradient(135deg,
306
+ rgba(34, 197, 94, 0.9) 0%,
307
+ rgba(21, 128, 61, 0.9) 100%);
308
+ border-color: rgba(34, 197, 94, 0.3);
309
+ }
310
+
311
+ .toastify-pro.error {
312
+ background: linear-gradient(135deg,
313
+ rgba(239, 68, 68, 0.9) 0%,
314
+ rgba(185, 28, 28, 0.9) 100%);
315
+ border-color: rgba(239, 68, 68, 0.3);
316
+ }
317
+
318
+ .toastify-pro.info {
319
+ background: linear-gradient(135deg,
320
+ rgba(59, 130, 246, 0.9) 0%,
321
+ rgba(29, 78, 216, 0.9) 100%);
322
+ border-color: rgba(59, 130, 246, 0.3);
323
+ }
324
+
325
+ .toastify-pro.warning {
326
+ background: linear-gradient(135deg,
327
+ rgba(245, 158, 11, 0.9) 0%,
328
+ rgba(217, 119, 6, 0.9) 100%);
329
+ border-color: rgba(245, 158, 11, 0.3);
330
+ }
331
+
332
+ .toastify-pro.dark {
333
+ background: linear-gradient(135deg,
334
+ rgba(15, 23, 42, 0.95) 0%,
335
+ rgba(30, 41, 59, 0.95) 100%);
336
+ border-color: rgba(148, 163, 184, 0.2);
337
+ }
338
+
339
+ .toastify-pro.light {
340
+ background: linear-gradient(135deg,
341
+ rgba(255, 255, 255, 0.95) 0%,
342
+ rgba(248, 250, 252, 0.95) 100%);
343
+ color: #1e293b;
344
+ border-color: rgba(226, 232, 240, 0.8);
345
+ box-shadow:
346
+ 0 20px 25px -5px rgba(0, 0, 0, 0.08),
347
+ 0 10px 10px -5px rgba(0, 0, 0, 0.03);
348
+ }
349
+
350
+ .toastify-pro.light::before {
351
+ background: linear-gradient(90deg,
352
+ rgba(30, 41, 59, 0.8) 0%,
353
+ rgba(30, 41, 59, 0.4) 50%,
354
+ rgba(30, 41, 59, 0.8) 100%);
355
+ }
356
+
357
+ .toastify-pro.light::after {
358
+ background: rgba(30, 41, 59, 0.6);
359
+ }
360
+
361
+ .toastify-pro .toast-icon {
362
+ flex-shrink: 0;
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ width: 28px;
367
+ height: 28px;
368
+ border-radius: 50%;
369
+ background: rgba(255, 255, 255, 0.2);
370
+ backdrop-filter: blur(10px);
371
+ animation: iconPulse 2s infinite;
372
+ }
373
+
374
+ @keyframes iconPulse {
375
+ 0%, 100% { transform: scale(1); }
376
+ 50% { transform: scale(1.05); }
377
+ }
378
+
379
+ @keyframes iconBounce {
380
+ 0% { transform: scale(0.2) rotate(-15deg); }
381
+ 40% { transform: scale(1.2) rotate(8deg); }
382
+ 70% { transform: scale(0.95) rotate(-3deg); }
383
+ 100% { transform: scale(1) rotate(0deg); }
384
+ }
385
+
386
+ @keyframes iconCarExit {
387
+ 0% {
388
+ transform: scale(1) rotate(0deg);
389
+ opacity: 1;
390
+ }
391
+ 20% {
392
+ transform: scale(1.1) rotate(-10deg);
393
+ opacity: 0.9;
394
+ }
395
+ 100% {
396
+ transform: scale(0.3) rotate(180deg);
397
+ opacity: 0;
398
+ }
399
+ }
400
+
401
+ .toastify-pro .toast-icon svg {
402
+ width: 18px;
403
+ height: 18px;
404
+ color: inherit;
405
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
406
+ }
407
+
408
+ .toastify-pro.light .toast-icon {
409
+ background: rgba(15, 23, 42, 0.1);
62
410
  }
63
- .toastify-pro.show { opacity: 1; transform: translateY(0); }
64
- .toastify-pro.success { background: rgba(76, 175, 80, 0.9); }
65
- .toastify-pro.error { background: rgba(244, 67, 54, 0.9); }
66
- .toastify-pro.info { background: rgba(33, 150, 243, 0.9); }
67
- .toastify-pro.warning { background: rgba(255, 152, 0, 0.9); }
68
- .toastify-pro.dark { background: rgba(0,0,0,0.85); }
69
- .toastify-pro.light { background: rgba(255,255,255,0.9); color: #000; }
70
411
 
71
412
  .toastify-pro .toast-content {
72
413
  flex: 1;
73
- padding-right: 8px;
414
+ line-height: 1.5;
415
+ font-weight: 500;
416
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
417
+ }
418
+
419
+ .toastify-pro .toast-message {
420
+ font-size: 15px;
421
+ font-weight: 500;
422
+ margin-bottom: 0;
423
+ }
424
+
425
+ .toastify-pro .toast-description {
426
+ font-size: 13px;
427
+ font-weight: 400;
428
+ opacity: 0.85;
429
+ margin-top: 4px;
74
430
  line-height: 1.4;
75
431
  }
76
432
 
77
433
  .toastify-pro .close-btn {
78
434
  cursor: pointer;
79
- font-size: 18px;
435
+ font-size: 20px;
80
436
  color: inherit;
81
- opacity: 0.8;
82
- padding: 2px 4px;
83
- border-radius: 3px;
84
- transition: opacity 0.2s ease, background-color 0.2s ease;
437
+ opacity: 0.7;
438
+ padding: 8px;
439
+ border-radius: 50%;
440
+ transition: all 0.2s ease;
85
441
  flex-shrink: 0;
86
- min-width: 20px;
87
- text-align: center;
442
+ width: 32px;
443
+ height: 32px;
444
+ display: flex;
445
+ align-items: center;
446
+ justify-content: center;
447
+ background: rgba(255, 255, 255, 0.1);
448
+ backdrop-filter: blur(10px);
449
+ font-weight: 300;
88
450
  line-height: 1;
89
451
  }
452
+
90
453
  .toastify-pro .close-btn:hover {
91
454
  opacity: 1;
92
- background-color: rgba(0,0,0,0.1);
455
+ background: rgba(255, 255, 255, 0.2);
456
+ transform: scale(1.1);
457
+ }
458
+
459
+ .toastify-pro.light .close-btn {
460
+ background: rgba(15, 23, 42, 0.08);
93
461
  }
462
+
94
463
  .toastify-pro.light .close-btn:hover {
95
- background-color: rgba(0,0,0,0.05);
464
+ background: rgba(15, 23, 42, 0.15);
465
+ }
466
+
467
+ @media (max-width: 640px) {
468
+ .toastify-pro {
469
+ min-width: 260px;
470
+ max-width: calc(100vw - 48px);
471
+ margin: 0 8px;
472
+ }
473
+
474
+ .toastify-pro-container.top-left,
475
+ .toastify-pro-container.bottom-left { left: 16px; }
476
+ .toastify-pro-container.top-right,
477
+ .toastify-pro-container.bottom-right { right: 16px; }
96
478
  }
97
479
  `;
98
- document.head.appendChild(style);
480
+ document.head.appendChild(style);
481
+ } catch (error) {
482
+ console.error('ToastifyPro: Failed to inject styles:', error);
483
+ }
99
484
  }
100
485
 
486
+ /**
487
+ * Creates and displays a toast notification
488
+ * @param {string} message - Main message text
489
+ * @param {string} type - Toast type (success, error, info, warning, dark, light)
490
+ * @param {Object} opts - Additional options
491
+ * @param {string} opts.description - Optional description text
492
+ * @param {number} opts.timeout - Override default timeout
493
+ * @param {boolean} opts.allowClose - Override close button setting
494
+ * @param {number} opts.maxLength - Override max message length
495
+ */
101
496
  show(message, type = "dark", opts = {}) {
102
- const options = { ...this.defaultOptions, ...opts };
497
+ // Input validation
498
+ if (typeof message !== 'string') {
499
+ console.warn('ToastifyPro: Message must be a string. Converting to string.');
500
+ message = String(message);
501
+ }
103
502
 
104
- const toast = document.createElement("div");
105
- toast.className = `toastify-pro ${type}`;
503
+ if (!message.trim()) {
504
+ console.warn('ToastifyPro: Empty message provided.');
505
+ return;
506
+ }
106
507
 
107
- // Create content wrapper for the message
108
- const contentWrapper = document.createElement("div");
109
- contentWrapper.className = "toast-content";
110
- contentWrapper.textContent = message.substring(0, options.maxLength);
111
- toast.appendChild(contentWrapper);
508
+ // Validate type
509
+ const validTypes = ['success', 'error', 'info', 'warning', 'dark', 'light'];
510
+ if (!validTypes.includes(type)) {
511
+ console.warn(`ToastifyPro: Invalid type "${type}". Using "dark".`);
512
+ type = 'dark';
513
+ }
112
514
 
113
- if (options.allowClose) {
114
- const closeBtn = document.createElement("span");
115
- closeBtn.className = "close-btn";
116
- closeBtn.innerHTML = "&times;";
117
- closeBtn.onclick = () => this.removeToast(toast);
118
- toast.appendChild(closeBtn);
515
+ // Validate and merge options
516
+ if (typeof opts !== 'object' || opts === null) {
517
+ console.warn('ToastifyPro: Invalid options parameter. Using defaults.');
518
+ opts = {};
119
519
  }
120
520
 
121
- this.container.appendChild(toast);
521
+ const options = { ...this.defaultOptions, ...opts };
522
+
523
+ try {
524
+ // Create toast element
525
+ const toast = document.createElement("div");
526
+ toast.className = `toastify-pro ${type}`;
527
+
528
+ // Set duration for progress bar animation
529
+ if (options.timeout > 0) {
530
+ toast.style.setProperty('--duration', `${options.timeout}ms`);
531
+ }
532
+
533
+ // Create icon wrapper
534
+ const iconWrapper = document.createElement("div");
535
+ iconWrapper.className = "toast-icon";
536
+ iconWrapper.innerHTML = this.getIconSVG(type);
537
+ toast.appendChild(iconWrapper);
122
538
 
123
- // show animation
124
- setTimeout(() => toast.classList.add("show"), 50);
539
+ // Create content wrapper for the message and description
540
+ const contentWrapper = document.createElement("div");
541
+ contentWrapper.className = "toast-content";
542
+
543
+ // Main message
544
+ const messageElement = document.createElement("div");
545
+ messageElement.className = "toast-message";
546
+ messageElement.textContent = message.substring(0, options.maxLength);
547
+ contentWrapper.appendChild(messageElement);
548
+
549
+ // Optional description (if provided)
550
+ if (options.description && typeof options.description === 'string') {
551
+ const descriptionElement = document.createElement("div");
552
+ descriptionElement.className = "toast-description";
553
+ descriptionElement.textContent = options.description.substring(0, options.maxLength * 2);
554
+ contentWrapper.appendChild(descriptionElement);
555
+ }
556
+
557
+ toast.appendChild(contentWrapper);
125
558
 
126
- // auto remove
127
- if (options.timeout > 0) {
128
- setTimeout(() => this.removeToast(toast), options.timeout);
559
+ // Add close button if enabled
560
+ if (options.allowClose) {
561
+ const closeBtn = document.createElement("span");
562
+ closeBtn.className = "close-btn";
563
+ closeBtn.innerHTML = "&times;";
564
+ closeBtn.setAttribute('aria-label', 'Close notification');
565
+ closeBtn.onclick = () => this.removeToast(toast);
566
+ toast.appendChild(closeBtn);
567
+ }
568
+
569
+ // Add toast to container
570
+ this.container.appendChild(toast);
571
+
572
+ // Apple AirDrop-style entrance animation
573
+ setTimeout(() => {
574
+ toast.classList.add("show");
575
+ // Add icon bounce effect with Apple-style timing
576
+ const icon = toast.querySelector('.toast-icon');
577
+ if (icon) {
578
+ icon.style.animation = 'iconBounce 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
579
+ }
580
+ }, 10);
581
+
582
+ // Auto-remove after timeout
583
+ if (options.timeout > 0) {
584
+ setTimeout(() => this.removeToast(toast), options.timeout);
585
+ }
586
+
587
+ return toast; // Return element for potential future manipulation
588
+ } catch (error) {
589
+ console.error('ToastifyPro: Failed to create toast:', error);
129
590
  }
130
591
  }
131
592
 
593
+ /**
594
+ * Removes a toast with position-aware car swipe animation
595
+ * @param {HTMLElement} toast - Toast element to remove
596
+ */
132
597
  removeToast(toast) {
133
- toast.classList.remove("show");
134
- setTimeout(() => toast.remove(), 300);
598
+ if (!toast || !toast.parentNode) {
599
+ console.warn('ToastifyPro: Invalid toast element for removal.');
600
+ return;
601
+ }
602
+
603
+ try {
604
+ // Detect position to choose the right swipe direction
605
+ const container = toast.parentNode;
606
+ const position = container.className.split(' ')[1]; // get position class
607
+
608
+ let swipeAnimation = 'carSwipeBottom'; // default fallback
609
+
610
+ // Choose animation based on position - car swipes away from screen edge
611
+ if (position.includes('bottom')) {
612
+ swipeAnimation = 'carSwipeBottom'; // swipe down off screen
613
+ } else if (position.includes('top')) {
614
+ swipeAnimation = 'carSwipeTop'; // swipe up off screen
615
+ } else if (position.includes('left')) {
616
+ swipeAnimation = 'carSwipeLeft'; // swipe left off screen
617
+ } else if (position.includes('right')) {
618
+ swipeAnimation = 'carSwipeRight'; // swipe right off screen
619
+ } else if (position.includes('center')) {
620
+ swipeAnimation = 'carSwipeCenter'; // swipe down for center
621
+ }
622
+
623
+ // Apply fast car swipe animation with improved easing
624
+ toast.style.animation = `${swipeAnimation} 0.4s cubic-bezier(0.4, 0.0, 1, 1) forwards`;
625
+
626
+ // Add spinning icon animation for extra polish
627
+ const icon = toast.querySelector('.toast-icon');
628
+ if (icon) {
629
+ icon.style.animation = 'iconCarExit 0.4s cubic-bezier(0.4, 0.0, 1, 1) forwards';
630
+ }
631
+
632
+ // Remove element after animation completes
633
+ setTimeout(() => {
634
+ if (toast.parentNode) {
635
+ toast.remove();
636
+ }
637
+ }, 400);
638
+ } catch (error) {
639
+ console.error('ToastifyPro: Error removing toast:', error);
640
+ // Fallback: remove immediately if animation fails
641
+ if (toast.parentNode) {
642
+ toast.remove();
643
+ }
644
+ }
135
645
  }
136
646
 
647
+ /**
648
+ * Shows a success toast notification
649
+ * @param {string} msg - Main message
650
+ * @param {string|Object} opts - Description string or options object
651
+ */
137
652
  success(msg, opts) {
653
+ // Handle both (message) and (message, description) formats
654
+ if (typeof opts === 'string') {
655
+ opts = { description: opts };
656
+ }
138
657
  this.show(msg, "success", opts);
139
658
  }
659
+
660
+ /**
661
+ * Shows an error toast notification
662
+ * @param {string} msg - Main message
663
+ * @param {string|Object} opts - Description string or options object
664
+ */
140
665
  error(msg, opts) {
666
+ if (typeof opts === 'string') {
667
+ opts = { description: opts };
668
+ }
141
669
  this.show(msg, "error", opts);
142
670
  }
671
+
672
+ /**
673
+ * Shows an info toast notification
674
+ * @param {string} msg - Main message
675
+ * @param {string|Object} opts - Description string or options object
676
+ */
143
677
  info(msg, opts) {
678
+ if (typeof opts === 'string') {
679
+ opts = { description: opts };
680
+ }
144
681
  this.show(msg, "info", opts);
145
682
  }
683
+
684
+ /**
685
+ * Shows a warning toast notification
686
+ * @param {string} msg - Main message
687
+ * @param {string|Object} opts - Description string or options object
688
+ */
146
689
  warning(msg, opts) {
690
+ if (typeof opts === 'string') {
691
+ opts = { description: opts };
692
+ }
147
693
  this.show(msg, "warning", opts);
148
694
  }
695
+
696
+ /**
697
+ * Shows a dark-themed toast notification
698
+ * @param {string} msg - Main message
699
+ * @param {string|Object} opts - Description string or options object
700
+ */
149
701
  dark(msg, opts) {
702
+ if (typeof opts === 'string') {
703
+ opts = { description: opts };
704
+ }
150
705
  this.show(msg, "dark", opts);
151
706
  }
707
+
708
+ /**
709
+ * Shows a light-themed toast notification
710
+ * @param {string} msg - Main message
711
+ * @param {string|Object} opts - Description string or options object
712
+ */
152
713
  light(msg, opts) {
714
+ if (typeof opts === 'string') {
715
+ opts = { description: opts };
716
+ }
153
717
  this.show(msg, "light", opts);
154
718
  }
155
719
  }
156
720
 
157
- export default ToastifyPro;
721
+ /**
722
+ * Export for different environments
723
+ * Supports CommonJS (Node.js), AMD, and browser globals
724
+ */
725
+
726
+ // CommonJS export (Node.js/npm)
727
+ if (typeof module !== 'undefined' && module.exports) {
728
+ module.exports = ToastifyPro;
729
+ }
730
+
731
+ // ES6 module export
732
+ if (typeof exports !== 'undefined') {
733
+ exports.ToastifyPro = ToastifyPro;
734
+ exports.default = ToastifyPro;
735
+ }
736
+
737
+ // AMD export (RequireJS)
738
+ if (typeof define === 'function' && define.amd) {
739
+ define(function() {
740
+ return ToastifyPro;
741
+ });
742
+ }
743
+
744
+ // Browser global
745
+ if (typeof window !== 'undefined') {
746
+ window.ToastifyPro = ToastifyPro;
747
+ }