@tsirosgeorge/toastnotification 5.5.0 → 5.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +34 -0
- package/assets/css/toast.css +49 -1
- package/assets/css/toast.min.css +1 -1
- package/package.json +2 -2
- package/toast.d.ts +29 -0
- package/toast.js +172 -58
- package/toast.min.js +1 -1
- package/toast.module.js +172 -58
package/Readme.md
CHANGED
|
@@ -103,6 +103,37 @@ toast('Are you sure?', {
|
|
|
103
103
|
});
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
### 🔄 Wrapping an async action
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
const order = await toast.promise(saveOrder(cart), {
|
|
110
|
+
loading: 'Saving your order…',
|
|
111
|
+
success: (order) => `Order #${order.id} saved.`,
|
|
112
|
+
error: (err) => `Could not save: ${err.message}`,
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
One loading toast becomes the success or error message. It resolves with the promise's
|
|
117
|
+
value and re-throws its rejection, so the caller still handles the failure.
|
|
118
|
+
|
|
119
|
+
### ↩️ Action button
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
toast.success('Item removed from your cart.', {
|
|
123
|
+
duration: 8000,
|
|
124
|
+
showProgress: true,
|
|
125
|
+
action: { text: 'Undo', onClick: () => restoreItem() },
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ⚙️ Site-wide defaults
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
toast.defaults = { position: 'bottom-right', showProgress: true, duration: 4000 };
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Applied to every toast unless the individual call overrides them.
|
|
136
|
+
|
|
106
137
|
### ⌨️ Keyboard and screen readers
|
|
107
138
|
|
|
108
139
|
Confirm dialogs take focus when they open, keep Tab inside themselves while they are
|
|
@@ -138,6 +169,9 @@ t.update('Done!', { type: 'success', duration: 2000 });
|
|
|
138
169
|
| `duration` | `number` | `3000` | Duration in milliseconds before the toast automatically dismisses. |
|
|
139
170
|
| `icon` | `string` or `null` | `null` | Optional custom icon displayed as text (e.g., emoji) before the toast message. If not set, a default GIF icon is used based on the `type`. |
|
|
140
171
|
| `showLoader` | `boolean` | `false` | Whether to show a loader/progress bar animation on the toast during its visible duration. |
|
|
172
|
+
| `pauseOnHover` | `boolean` | `true` | Freeze the countdown while the pointer or keyboard focus is on the toast. |
|
|
173
|
+
| `showProgress` | `boolean` | `false` | Thin bar counting the remaining time down. Needs `duration > 0`; pauses with `pauseOnHover`. |
|
|
174
|
+
| `action` | `object` or `null` | `null` | `{ text, onClick }` renders a button inside the toast, e.g. Undo. Clicking it runs `onClick` and closes the toast. |
|
|
141
175
|
| `allowHtml` | `boolean` | `true` | `message` is written as HTML. Pass `false` to render it as plain text — do that for anything user-supplied, or you have an XSS hole. |
|
|
142
176
|
| `closeOnEscape` | `boolean` | `true` | Confirm dialogs only. Escape cancels the dialog. |
|
|
143
177
|
| `onClick` | `function` or `null` | `null` | Callback function executed when the toast is clicked. |
|
package/assets/css/toast.css
CHANGED
|
@@ -343,6 +343,8 @@
|
|
|
343
343
|
.ts-toast-overlay.bottom-center { align-items: flex-end; justify-content: center; padding: 1rem; }
|
|
344
344
|
.ts-toast-overlay.bottom-right { align-items: flex-end; justify-content: flex-end; padding: 1rem; }
|
|
345
345
|
.ts-toast-overlay.center { align-items: center; justify-content: center; padding: 1rem; }
|
|
346
|
+
/* A dialog opening over another must not dim the page a second time. */
|
|
347
|
+
.ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }
|
|
346
348
|
|
|
347
349
|
/* Tiny utility: flex icon container */
|
|
348
350
|
/* Scoped utility to avoid conflicts */
|
|
@@ -351,4 +353,50 @@
|
|
|
351
353
|
/* Prevent scroll behind confirm overlay */
|
|
352
354
|
body.ts-toast-no-scroll {
|
|
353
355
|
overflow: hidden;
|
|
354
|
-
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* Progress bar counting down the remaining time */
|
|
359
|
+
@keyframes ts-toast-progress {
|
|
360
|
+
from { transform: scaleX(1); }
|
|
361
|
+
to { transform: scaleX(0); }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.ts-toast-container .ts-toast-progress {
|
|
365
|
+
position: absolute;
|
|
366
|
+
left: 0;
|
|
367
|
+
right: 0;
|
|
368
|
+
bottom: 0;
|
|
369
|
+
height: 3px;
|
|
370
|
+
transform-origin: left center;
|
|
371
|
+
border-radius: 0 0 8px 8px;
|
|
372
|
+
background: var(--toast-progress-color, currentColor);
|
|
373
|
+
opacity: 0.35;
|
|
374
|
+
pointer-events: none;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.ts-toast-container .ts-toast-success .ts-toast-progress { background: var(--toast-success-color, #17a35c); }
|
|
378
|
+
.ts-toast-container .ts-toast-error .ts-toast-progress { background: var(--toast-error-color, #ef4444); }
|
|
379
|
+
.ts-toast-container .ts-toast-warning .ts-toast-progress { background: var(--toast-warning-color, #f0a020); }
|
|
380
|
+
.ts-toast-container .ts-toast-info .ts-toast-progress { background: var(--toast-info-color, #3b82f6); }
|
|
381
|
+
|
|
382
|
+
/* Action button inside a toast (e.g. Undo) */
|
|
383
|
+
.ts-toast-container .ts-toast-action {
|
|
384
|
+
/* Toasts lay out row-reverse, so the lowest order sits at the right-hand end:
|
|
385
|
+
icon, message, then the action button. */
|
|
386
|
+
order: -1;
|
|
387
|
+
flex: none;
|
|
388
|
+
appearance: none;
|
|
389
|
+
border: 0;
|
|
390
|
+
background: transparent;
|
|
391
|
+
color: var(--toast-action-color, #3b82f6);
|
|
392
|
+
font: inherit;
|
|
393
|
+
font-weight: 600;
|
|
394
|
+
padding: 4px 8px;
|
|
395
|
+
margin-left: 4px;
|
|
396
|
+
border-radius: 6px;
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.ts-toast-container .ts-toast-action:hover {
|
|
401
|
+
background: var(--toast-action-hover, rgba(59, 130, 246, 0.12));
|
|
402
|
+
}
|
package/assets/css/toast.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.ts-toast-container{position:fixed;z-index:9999;display:flex;flex-direction:column;gap:10px;pointer-events:none;width:fit-content;max-width:calc(100% - 2rem)}.ts-toast-container>:not(:last-child){margin-bottom:0!important}.ts-toast-container.top-right{top:1rem;right:1rem;align-items:flex-end}.ts-toast-container.top-left{top:1rem;left:1rem;align-items:flex-start}.ts-toast-container.top-center{top:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.bottom-right{bottom:1rem;right:1rem;align-items:flex-end}.ts-toast-container.bottom-left{bottom:1rem;left:1rem;align-items:flex-start}.ts-toast-container.bottom-center{bottom:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.center{top:50%;left:50%;transform:translate(-50%,-50%);align-items:center}.ts-toast-container .ts-toast{display:flex;align-items:center;gap:.75rem;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);padding:.75rem 1rem;border-radius:8px;border:1px solid var(--toast-border,#e5e7eb);box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1));font-family:sans-serif;font-size:.95rem;pointer-events:all;position:relative;opacity:0;transform:translateY(20px);transition:opacity .5s ease,transform .5s ease;width:auto!important}.ts-toast.ts-toast-confirm{display:flex;align-items:center;justify-content:center;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);border:1px solid var(--toast-border,#e5e7eb);border-radius:12px;padding:16px 20px;box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1));transition:opacity .5s ease,transform .5s ease}.ts-toast.ts-toast-confirm .ts-toast-content{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center}.ts-toast.ts-toast-confirm .ts-toast-body{text-align:center}.ts-toast-actions{display:flex;gap:10px;justify-content:center;margin-top:12px}.ts-toast-input{width:100%;padding:8px 12px;border:1px solid var(--toast-border,#e5e7eb);border-radius:8px;font-family:inherit;font-size:.95rem;margin-top:12px;background:var(--toast-bg,#fff);color:var(--toast-color,#000);outline:0;transition:border-color .2s ease}.ts-toast-input:focus{border-color:#3b82f6}.ts-toast-container .ts-toast.ts-toast-show{opacity:1;transform:translateY(0)}@keyframes ts-toast-slide-left{from{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-right{from{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-top{from{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes ts-toast-slide-bottom{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes ts-toast-zoom-in{from{transform:scale(0)}to{transform:scale(1)}}@keyframes ts-toast-zoom-out{from{transform:scale(1)}to{transform:scale(0)}}@keyframes ts-toast-flip{from{transform:rotateY(90deg)}to{transform:rotateY(0)}}.ts-toast-container .ts-toast-icon{font-size:1.2rem}.ts-toast-container .ts-toast-success .ts-toast-icon{color:var(--toast-success-color,#66ee78)}.ts-toast-container .ts-toast-error .ts-toast-icon{color:var(--toast-error-color,#ef4444)}.ts-toast-container .ts-toast-loader{width:20px;height:20px;border:3px solid #f3f3f3;border-top:3px solid var(--toast-loader-color,#66ee78);border-radius:50%;animation:ts-toast-spin 1s linear infinite}@keyframes ts-toast-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.ts-toast-container .ts-toast-loader.done{animation:none;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast-icon{font-size:1.2rem;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast.ts-toast-show .ts-toast-icon{opacity:1}@keyframes progress-bar{0%{width:0%}100%{width:100%}}@keyframes ts-toast-slide-top-reverse{from{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes ts-toast-slide-bottom-reverse{from{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes ts-toast-slide-left-reverse{from{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes ts-toast-slide-right-reverse{from{transform:translateX(0)}to{transform:translateX(100%)}}.ts-toast.ts-toast-slide-out{opacity:0}.ts-toast-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:2147483646}.ts-toast-overlay.top-left{align-items:flex-start;justify-content:flex-start;padding:1rem}.ts-toast-overlay.top-center{align-items:flex-start;justify-content:center;padding:1rem}.ts-toast-overlay.top-right{align-items:flex-start;justify-content:flex-end;padding:1rem}.ts-toast-overlay.bottom-left{align-items:flex-end;justify-content:flex-start;padding:1rem}.ts-toast-overlay.bottom-center{align-items:flex-end;justify-content:center;padding:1rem}.ts-toast-overlay.bottom-right{align-items:flex-end;justify-content:flex-end;padding:1rem}.ts-toast-overlay.center{align-items:center;justify-content:center;padding:1rem}.ts-toast-d-flex{display:inline-flex;align-items:center}body.ts-toast-no-scroll{overflow:hidden}
|
|
1
|
+
.ts-toast-container{position:fixed;z-index:9999;display:flex;flex-direction:column;gap:10px;pointer-events:none;width:fit-content;max-width:calc(100% - 2rem)}.ts-toast-container>:not(:last-child){margin-bottom:0!important}.ts-toast-container.top-right{top:1rem;right:1rem;align-items:flex-end}.ts-toast-container.top-left{top:1rem;left:1rem;align-items:flex-start}.ts-toast-container.top-center{top:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.bottom-right{bottom:1rem;right:1rem;align-items:flex-end}.ts-toast-container.bottom-left{bottom:1rem;left:1rem;align-items:flex-start}.ts-toast-container.bottom-center{bottom:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.center{top:50%;left:50%;transform:translate(-50%,-50%);align-items:center}.ts-toast-container .ts-toast{display:flex;align-items:center;gap:.75rem;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);padding:.75rem 1rem;border-radius:8px;border:1px solid var(--toast-border,#e5e7eb);box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1));font-family:sans-serif;font-size:.95rem;pointer-events:all;position:relative;opacity:0;transform:translateY(20px);transition:opacity .5s ease,transform .5s ease;width:auto!important}.ts-toast.ts-toast-confirm{display:flex;align-items:center;justify-content:center;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);border:1px solid var(--toast-border,#e5e7eb);border-radius:12px;padding:16px 20px;box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1));transition:opacity .5s ease,transform .5s ease}.ts-toast.ts-toast-confirm .ts-toast-content{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center}.ts-toast.ts-toast-confirm .ts-toast-body{text-align:center}.ts-toast-actions{display:flex;gap:10px;justify-content:center;margin-top:12px}.ts-toast-input{width:100%;padding:8px 12px;border:1px solid var(--toast-border,#e5e7eb);border-radius:8px;font-family:inherit;font-size:.95rem;margin-top:12px;background:var(--toast-bg,#fff);color:var(--toast-color,#000);outline:0;transition:border-color .2s ease}.ts-toast-input:focus{border-color:#3b82f6}.ts-toast-container .ts-toast.ts-toast-show{opacity:1;transform:translateY(0)}@keyframes ts-toast-slide-left{from{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-right{from{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-top{from{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes ts-toast-slide-bottom{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes ts-toast-zoom-in{from{transform:scale(0)}to{transform:scale(1)}}@keyframes ts-toast-zoom-out{from{transform:scale(1)}to{transform:scale(0)}}@keyframes ts-toast-flip{from{transform:rotateY(90deg)}to{transform:rotateY(0)}}.ts-toast-container .ts-toast-icon{font-size:1.2rem}.ts-toast-container .ts-toast-success .ts-toast-icon{color:var(--toast-success-color,#66ee78)}.ts-toast-container .ts-toast-error .ts-toast-icon{color:var(--toast-error-color,#ef4444)}.ts-toast-container .ts-toast-loader{width:20px;height:20px;border:3px solid #f3f3f3;border-top:3px solid var(--toast-loader-color,#66ee78);border-radius:50%;animation:ts-toast-spin 1s linear infinite}@keyframes ts-toast-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.ts-toast-container .ts-toast-loader.done{animation:none;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast-icon{font-size:1.2rem;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast.ts-toast-show .ts-toast-icon{opacity:1}@keyframes progress-bar{0%{width:0%}100%{width:100%}}@keyframes ts-toast-slide-top-reverse{from{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes ts-toast-slide-bottom-reverse{from{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes ts-toast-slide-left-reverse{from{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes ts-toast-slide-right-reverse{from{transform:translateX(0)}to{transform:translateX(100%)}}.ts-toast.ts-toast-slide-out{opacity:0}.ts-toast-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:2147483646}.ts-toast-overlay.top-left{align-items:flex-start;justify-content:flex-start;padding:1rem}.ts-toast-overlay.top-center{align-items:flex-start;justify-content:center;padding:1rem}.ts-toast-overlay.top-right{align-items:flex-start;justify-content:flex-end;padding:1rem}.ts-toast-overlay.bottom-left{align-items:flex-end;justify-content:flex-start;padding:1rem}.ts-toast-overlay.bottom-center{align-items:flex-end;justify-content:center;padding:1rem}.ts-toast-overlay.bottom-right{align-items:flex-end;justify-content:flex-end;padding:1rem}.ts-toast-overlay.center{align-items:center;justify-content:center;padding:1rem}.ts-toast-overlay.ts-toast-overlay-stacked{background:0 0}.ts-toast-d-flex{display:inline-flex;align-items:center}body.ts-toast-no-scroll{overflow:hidden}@keyframes ts-toast-progress{from{transform:scaleX(1)}to{transform:scaleX(0)}}.ts-toast-container .ts-toast-progress{position:absolute;left:0;right:0;bottom:0;height:3px;transform-origin:left center;border-radius:0 0 8px 8px;background:var(--toast-progress-color,currentColor);opacity:.35;pointer-events:none}.ts-toast-container .ts-toast-success .ts-toast-progress{background:var(--toast-success-color,#17a35c)}.ts-toast-container .ts-toast-error .ts-toast-progress{background:var(--toast-error-color,#ef4444)}.ts-toast-container .ts-toast-warning .ts-toast-progress{background:var(--toast-warning-color,#f0a020)}.ts-toast-container .ts-toast-info .ts-toast-progress{background:var(--toast-info-color,#3b82f6)}.ts-toast-container .ts-toast-action{order:-1;flex:none;appearance:none;border:0;background:0 0;color:var(--toast-action-color,#3b82f6);font:inherit;font-weight:600;padding:4px 8px;margin-left:4px;border-radius:6px;cursor:pointer}.ts-toast-container .ts-toast-action:hover{background:var(--toast-action-hover,rgba(59,130,246,.12))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsirosgeorge/toastnotification",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.1",
|
|
4
4
|
"description": "a toast notification plugin",
|
|
5
5
|
"main": "toast.min.js",
|
|
6
6
|
"module": "toast.module.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"build:css": "cleancss -o assets/css/toast.min.css assets/css/toast.css",
|
|
22
22
|
"build:js": "terser toast.js --compress --mangle --output toast.min.js",
|
|
23
23
|
"build": "npm run sync:version && npm run build:module && npm run build:css && npm run build:js",
|
|
24
|
-
"version": "npm run build && git add -A",
|
|
24
|
+
"version": "node scripts/stamp-changelog.mjs && npm run build && git add -A",
|
|
25
25
|
"prepublishOnly": "npm run build",
|
|
26
26
|
"release": "npm version ${V:-patch} && git push origin main --follow-tags"
|
|
27
27
|
},
|
package/toast.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface ToastOptions {
|
|
|
31
31
|
/** Emoji or text used instead of the bundled animated icon. */
|
|
32
32
|
icon?: string | null;
|
|
33
33
|
showLoader?: boolean;
|
|
34
|
+
/** Freeze the countdown while the pointer or keyboard focus is on the toast. */
|
|
35
|
+
pauseOnHover?: boolean;
|
|
36
|
+
/** Thin bar counting the remaining time down. Needs `duration > 0`. */
|
|
37
|
+
showProgress?: boolean;
|
|
38
|
+
/** Renders a button inside the toast, e.g. Undo. */
|
|
39
|
+
action?: ToastAction | null;
|
|
34
40
|
/**
|
|
35
41
|
* `message` is written as HTML by default, for backwards compatibility.
|
|
36
42
|
* Pass `false` to render it as plain text — do that for anything user-supplied.
|
|
@@ -66,6 +72,21 @@ export interface ConfirmOptions extends ToastOptions {
|
|
|
66
72
|
onResult?: (result: ConfirmResult, el: ToastElement) => void;
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
export interface ToastAction {
|
|
76
|
+
/** Button label. */
|
|
77
|
+
text: string;
|
|
78
|
+
/** Runs on click; the toast closes afterwards. */
|
|
79
|
+
onClick?: (el: ToastElement) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface PromiseMessages<T = unknown> {
|
|
83
|
+
loading?: string;
|
|
84
|
+
/** A function receives the resolved value. */
|
|
85
|
+
success?: string | ((value: T) => string);
|
|
86
|
+
/** A function receives the rejection reason. */
|
|
87
|
+
error?: string | ((err: unknown) => string);
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
export interface LoadingHandle {
|
|
70
91
|
update(message: string, options?: ToastOptions): void;
|
|
71
92
|
close(): void;
|
|
@@ -87,6 +108,14 @@ export interface Toast {
|
|
|
87
108
|
confirm(message: string, options?: ConfirmOptions): Promise<ConfirmResult>;
|
|
88
109
|
/** Dismiss every toast on screen; open confirm dialogs settle as a cancel. */
|
|
89
110
|
dismissAll(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Shows a loading toast that becomes the success or error message when
|
|
113
|
+
* `promise` settles. Resolves with the promise's value, and re-throws its
|
|
114
|
+
* rejection so the caller still handles the failure.
|
|
115
|
+
*/
|
|
116
|
+
promise<T>(promise: Promise<T>, messages?: PromiseMessages<T>, options?: ToastOptions): Promise<T>;
|
|
117
|
+
/** Options applied to every toast unless the call overrides them. */
|
|
118
|
+
defaults: ToastOptions;
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
declare const toast: Toast;
|
package/toast.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Single source of truth for the CDN this build points at.
|
|
4
4
|
// `npm run sync:version` rewrites it from package.json, so it can never go stale.
|
|
5
|
-
const TS_TOAST_VERSION = "5.
|
|
5
|
+
const TS_TOAST_VERSION = "5.6.1";
|
|
6
6
|
// Point this at your own copy of assets/ to self-host the CSS and icons
|
|
7
7
|
// (useful offline, behind a strict CSP, or when you don't want a CDN dependency):
|
|
8
8
|
// window.TS_TOAST_ASSET_BASE = '/vendor/toastnotification';
|
|
@@ -14,6 +14,7 @@ const TS_TOAST_CDN = (typeof window !== 'undefined' && window.TS_TOAST_ASSET_BAS
|
|
|
14
14
|
// group, so only the last dialog to close may release it.
|
|
15
15
|
let tsToastOpenModals = 0;
|
|
16
16
|
let tsToastPrevOverflow = '';
|
|
17
|
+
let tsToastPrevPaddingRight = '';
|
|
17
18
|
|
|
18
19
|
const tsToastReducedMotion = () =>
|
|
19
20
|
typeof window !== 'undefined' &&
|
|
@@ -52,6 +53,11 @@ let tsToastIdCounter = 0;
|
|
|
52
53
|
.ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
|
|
53
54
|
.ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }
|
|
54
55
|
.ts-toast-overlay.center { align-items: center; justify-content: center; }
|
|
56
|
+
.ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }
|
|
57
|
+
@keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }
|
|
58
|
+
.ts-toast .ts-toast-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; transform-origin: left center; border-radius: 0 0 8px 8px; background: currentColor; opacity: 0.35; pointer-events: none; }
|
|
59
|
+
.ts-toast .ts-toast-action { order: -1; flex: none; appearance: none; border: 0; background: transparent; color: #3b82f6; font: inherit; font-weight: 600; padding: 4px 8px; margin-left: 4px; border-radius: 6px; cursor: pointer; }
|
|
60
|
+
.ts-toast .ts-toast-action:hover { background: rgba(59,130,246,0.12); }
|
|
55
61
|
.ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }
|
|
56
62
|
.ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }
|
|
57
63
|
.ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
|
|
@@ -75,6 +81,9 @@ let tsToastIdCounter = 0;
|
|
|
75
81
|
})();
|
|
76
82
|
|
|
77
83
|
const toast = function (message, options = {}) {
|
|
84
|
+
// Site-wide defaults, so a page can turn on things like showProgress once
|
|
85
|
+
// instead of repeating them at every call site.
|
|
86
|
+
options = { ...(toast.defaults || {}), ...options };
|
|
78
87
|
const {
|
|
79
88
|
position = 'top-right',
|
|
80
89
|
animation = 'slide-right', // Default fallback animation
|
|
@@ -103,6 +112,12 @@ const toast = function (message, options = {}) {
|
|
|
103
112
|
useOverlay = true,
|
|
104
113
|
closeOnOverlayClick = true,
|
|
105
114
|
showClose = false,
|
|
115
|
+
// Freeze the countdown while the pointer or keyboard focus is on the toast
|
|
116
|
+
pauseOnHover = true,
|
|
117
|
+
// Thin bar counting the remaining time down
|
|
118
|
+
showProgress = false,
|
|
119
|
+
// { text, onClick } renders a button inside the toast, e.g. Undo
|
|
120
|
+
action = null,
|
|
106
121
|
// Escape cancels a confirm dialog
|
|
107
122
|
closeOnEscape = true,
|
|
108
123
|
// `message` is written as HTML for backwards compatibility. Pass false to
|
|
@@ -329,6 +344,31 @@ const toast = function (message, options = {}) {
|
|
|
329
344
|
confirmBtn.addEventListener('click', (e) => { e.stopPropagation(); resolveAndClose(true); });
|
|
330
345
|
}
|
|
331
346
|
|
|
347
|
+
// Action button (e.g. Undo). Clicking it runs the callback and closes the toast.
|
|
348
|
+
if (!isConfirm && action && typeof action === 'object' && action.text) {
|
|
349
|
+
const actionBtn = document.createElement('button');
|
|
350
|
+
actionBtn.className = 'ts-toast-action';
|
|
351
|
+
actionBtn.type = 'button';
|
|
352
|
+
actionBtn.textContent = action.text;
|
|
353
|
+
actionBtn.addEventListener('click', (e) => {
|
|
354
|
+
e.stopPropagation(); // do not also trigger dismissOnClick
|
|
355
|
+
if (typeof action.onClick === 'function') action.onClick(toastElement);
|
|
356
|
+
toastElement._dismiss();
|
|
357
|
+
});
|
|
358
|
+
toastElement.appendChild(actionBtn);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Progress bar. The width is driven by a CSS animation whose duration is the
|
|
362
|
+
// toast's own, so pausing it is one property and never drifts from the timer.
|
|
363
|
+
let progressBar = null;
|
|
364
|
+
if (!isConfirm && showProgress && duration > 0 && !reducedMotion) {
|
|
365
|
+
progressBar = document.createElement('div');
|
|
366
|
+
progressBar.className = 'ts-toast-progress';
|
|
367
|
+
progressBar.style.animation = `ts-toast-progress ${duration}ms linear forwards`;
|
|
368
|
+
progressBar.style.animationPlayState = 'paused';
|
|
369
|
+
toastElement.appendChild(progressBar);
|
|
370
|
+
}
|
|
371
|
+
|
|
332
372
|
// Loader Element
|
|
333
373
|
let loader = null;
|
|
334
374
|
if (showLoader) {
|
|
@@ -339,11 +379,15 @@ const toast = function (message, options = {}) {
|
|
|
339
379
|
|
|
340
380
|
// Container/Overlay
|
|
341
381
|
let overlay = null;
|
|
382
|
+
let containerEl = null;
|
|
342
383
|
if (isConfirm && useOverlay) {
|
|
343
384
|
overlay = document.createElement('div');
|
|
344
385
|
// A modal centres by default. The `position` default of 'top-right' is meant
|
|
345
386
|
// for toasts; applying it here parked the dialog in a corner of the backdrop.
|
|
346
|
-
|
|
387
|
+
// Each backdrop paints its own 50% black, so a second dialog opening over a
|
|
388
|
+
// first turned the page almost fully dark. Only the bottom one dims.
|
|
389
|
+
const stacked = tsToastOpenModals > 0 ? ' ts-toast-overlay-stacked' : '';
|
|
390
|
+
overlay.className = 'ts-toast-overlay' + stacked + (options.position ? ` ${position}` : '');
|
|
347
391
|
document.body.appendChild(overlay);
|
|
348
392
|
overlay.appendChild(toastElement);
|
|
349
393
|
if (showClose) {
|
|
@@ -387,6 +431,7 @@ const toast = function (message, options = {}) {
|
|
|
387
431
|
container.setAttribute('aria-relevant', 'additions');
|
|
388
432
|
}
|
|
389
433
|
container.appendChild(toastElement);
|
|
434
|
+
containerEl = container;
|
|
390
435
|
}
|
|
391
436
|
|
|
392
437
|
if (isConfirm) {
|
|
@@ -399,6 +444,15 @@ const toast = function (message, options = {}) {
|
|
|
399
444
|
tsToastOpenModals += 1;
|
|
400
445
|
if (tsToastOpenModals === 1) {
|
|
401
446
|
tsToastPrevOverflow = document.body.style.overflow;
|
|
447
|
+
tsToastPrevPaddingRight = document.body.style.paddingRight;
|
|
448
|
+
// Hiding the scrollbar makes the page wider, which shifts the whole
|
|
449
|
+
// layout sideways as the dialog opens. Pad by the scrollbar's width to
|
|
450
|
+
// hold it still. A no-op where scrollbars are overlays, as on macOS.
|
|
451
|
+
const scrollbar = window.innerWidth - document.documentElement.clientWidth;
|
|
452
|
+
if (scrollbar > 0) {
|
|
453
|
+
const current = parseFloat(window.getComputedStyle(document.body).paddingRight) || 0;
|
|
454
|
+
document.body.style.paddingRight = `${current + scrollbar}px`;
|
|
455
|
+
}
|
|
402
456
|
document.body.style.overflow = 'hidden';
|
|
403
457
|
}
|
|
404
458
|
|
|
@@ -441,7 +495,10 @@ const toast = function (message, options = {}) {
|
|
|
441
495
|
releaseModal = () => {
|
|
442
496
|
document.removeEventListener('keydown', onKeydown, true);
|
|
443
497
|
tsToastOpenModals = Math.max(0, tsToastOpenModals - 1);
|
|
444
|
-
if (tsToastOpenModals === 0)
|
|
498
|
+
if (tsToastOpenModals === 0) {
|
|
499
|
+
document.body.style.overflow = tsToastPrevOverflow;
|
|
500
|
+
document.body.style.paddingRight = tsToastPrevPaddingRight;
|
|
501
|
+
}
|
|
445
502
|
// Hand the keyboard back to whatever opened the dialog.
|
|
446
503
|
if (previouslyFocused && typeof previouslyFocused.focus === 'function' &&
|
|
447
504
|
document.contains(previouslyFocused)) {
|
|
@@ -463,6 +520,10 @@ const toast = function (message, options = {}) {
|
|
|
463
520
|
toastElement.classList.add('ts-toast-show');
|
|
464
521
|
}, 100);
|
|
465
522
|
|
|
523
|
+
// The loader always ran for 2s, so with a shorter duration the toast was gone
|
|
524
|
+
// before the icon it reveals ever appeared.
|
|
525
|
+
const loaderMs = duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000;
|
|
526
|
+
|
|
466
527
|
// Handle Loader and Icon
|
|
467
528
|
if (showLoader && loader) {
|
|
468
529
|
setTimeout(() => {
|
|
@@ -474,7 +535,7 @@ const toast = function (message, options = {}) {
|
|
|
474
535
|
if (isConfirm && contentRow) contentRow.appendChild(iconElement);
|
|
475
536
|
else toastElement.appendChild(iconElement); // Add icon only if not present
|
|
476
537
|
}
|
|
477
|
-
},
|
|
538
|
+
}, loaderMs);
|
|
478
539
|
}
|
|
479
540
|
if (!showLoader) {
|
|
480
541
|
// For confirm, icon already added above inside contentRow; avoid moving it
|
|
@@ -483,26 +544,70 @@ const toast = function (message, options = {}) {
|
|
|
483
544
|
}
|
|
484
545
|
}
|
|
485
546
|
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
547
|
+
// Dismissal state lives on the element so toast.update() can rebind the callback
|
|
548
|
+
// and reuse this exact removal path instead of keeping its own copy of it.
|
|
549
|
+
toastElement._onDismiss = typeof onDismiss === 'function' ? onDismiss : null;
|
|
550
|
+
|
|
551
|
+
// A plain setTimeout cannot be paused, so the countdown is tracked by hand:
|
|
552
|
+
// `remaining` is what is left, and hovering banks it.
|
|
553
|
+
let remaining = duration;
|
|
554
|
+
let timerId = null;
|
|
555
|
+
let startedAt = 0;
|
|
556
|
+
|
|
557
|
+
const stopTimer = () => {
|
|
558
|
+
if (timerId) { clearTimeout(timerId); timerId = null; }
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const startTimer = () => {
|
|
562
|
+
if (isConfirm || remaining <= 0 || timerId) return;
|
|
563
|
+
startedAt = Date.now();
|
|
564
|
+
timerId = setTimeout(() => { timerId = null; toastElement._dismiss(); }, remaining);
|
|
565
|
+
if (progressBar) progressBar.style.animationPlayState = 'running';
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const pauseTimer = () => {
|
|
569
|
+
if (!timerId) return;
|
|
570
|
+
clearTimeout(timerId);
|
|
571
|
+
timerId = null;
|
|
572
|
+
remaining -= Date.now() - startedAt;
|
|
573
|
+
if (progressBar) progressBar.style.animationPlayState = 'paused';
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
toastElement._dismiss = () => {
|
|
577
|
+
if (toastElement._removing) return; // never run the exit twice
|
|
578
|
+
toastElement._removing = true;
|
|
579
|
+
stopTimer();
|
|
580
|
+
removeWithAnimation(toastElement, () => {
|
|
581
|
+
if (toastElement._onDismiss) toastElement._onDismiss(toastElement);
|
|
582
|
+
// Containers used to pile up in the DOM, one per position, forever.
|
|
583
|
+
if (containerEl && !containerEl.children.length) containerEl.remove();
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// Lets toast.update() re-arm the countdown without reaching into internals.
|
|
588
|
+
toastElement._setDuration = (ms) => {
|
|
589
|
+
stopTimer();
|
|
590
|
+
remaining = ms;
|
|
591
|
+
startTimer();
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
startTimer();
|
|
595
|
+
|
|
596
|
+
if (!isConfirm && pauseOnHover) {
|
|
597
|
+
// Give the reader a chance to finish the sentence.
|
|
598
|
+
toastElement.addEventListener('mouseenter', pauseTimer);
|
|
599
|
+
toastElement.addEventListener('mouseleave', startTimer);
|
|
600
|
+
toastElement.addEventListener('focusin', pauseTimer);
|
|
601
|
+
toastElement.addEventListener('focusout', startTimer);
|
|
494
602
|
}
|
|
495
603
|
|
|
496
604
|
// Add event listener for closing the toast when clicked (disabled in confirm mode)
|
|
497
605
|
if (!isConfirm && dismissOnClick) {
|
|
498
606
|
toastElement.addEventListener('click', () => {
|
|
499
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove); // Clear the auto-remove timeout
|
|
500
607
|
// onClick belongs to the click, not to the end of the exit animation,
|
|
501
608
|
// which is where it used to fire half a second late.
|
|
502
609
|
if (onClick && typeof onClick === 'function') onClick(toastElement);
|
|
503
|
-
|
|
504
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
505
|
-
});
|
|
610
|
+
toastElement._dismiss();
|
|
506
611
|
});
|
|
507
612
|
}
|
|
508
613
|
|
|
@@ -525,23 +630,15 @@ const toast = function (message, options = {}) {
|
|
|
525
630
|
const dy = Math.abs(touchStartY - e.changedTouches[0].screenY);
|
|
526
631
|
// Only a mostly-horizontal swipe dismisses, so scrolling the page past a
|
|
527
632
|
// toast no longer throws it away on a bit of sideways drift.
|
|
528
|
-
if (dx > 50 && dx > dy)
|
|
529
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
530
|
-
removeWithAnimation(toastElement, () => {
|
|
531
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
532
|
-
});
|
|
533
|
-
}
|
|
633
|
+
if (dx > 50 && dx > dy) toastElement._dismiss();
|
|
534
634
|
});
|
|
535
635
|
}
|
|
536
636
|
|
|
537
637
|
// Let callers dismiss a toast they are holding, instead of only waiting out
|
|
538
638
|
// the duration or making the user click it.
|
|
539
639
|
toastElement.close = () => {
|
|
540
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
541
640
|
if (isConfirm) { resolveAndClose(false); return; }
|
|
542
|
-
|
|
543
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
544
|
-
});
|
|
641
|
+
toastElement._dismiss();
|
|
545
642
|
};
|
|
546
643
|
|
|
547
644
|
return toastElement;
|
|
@@ -570,10 +667,9 @@ const toast = function (message, options = {}) {
|
|
|
570
667
|
type = null,
|
|
571
668
|
icon = null,
|
|
572
669
|
showLoader = false,
|
|
573
|
-
duration = 3000, // Default duration (in ms)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
onDismiss = null // Custom onDismiss event listener
|
|
670
|
+
duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
|
|
671
|
+
allowHtml = true,
|
|
672
|
+
onDismiss = null // Replaces the callback the toast was created with
|
|
577
673
|
} = options;
|
|
578
674
|
|
|
579
675
|
// Remove old loader (if any)
|
|
@@ -591,7 +687,8 @@ const toast = function (message, options = {}) {
|
|
|
591
687
|
}
|
|
592
688
|
const toastBody = toastElement.querySelector('.ts-toast-body');
|
|
593
689
|
if (toastBody) {
|
|
594
|
-
toastBody.innerHTML = message;
|
|
690
|
+
if (allowHtml) toastBody.innerHTML = message;
|
|
691
|
+
else toastBody.textContent = message;
|
|
595
692
|
}
|
|
596
693
|
|
|
597
694
|
// Handle Icon update only if it's new or hasn't been set yet
|
|
@@ -619,9 +716,12 @@ const toast = function (message, options = {}) {
|
|
|
619
716
|
iconElement.appendChild(img);
|
|
620
717
|
}
|
|
621
718
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
(
|
|
719
|
+
// Only attach an icon we actually have. Without a type and without an explicit
|
|
720
|
+
// icon this used to append an empty <span><img></span>.
|
|
721
|
+
if (icon || iconElement.querySelector('img[src]')) {
|
|
722
|
+
const contentRow = toastElement.querySelector('.ts-toast-content');
|
|
723
|
+
(contentRow || toastElement).appendChild(iconElement);
|
|
724
|
+
}
|
|
625
725
|
|
|
626
726
|
// Handle loader if requested
|
|
627
727
|
if (showLoader) {
|
|
@@ -630,33 +730,19 @@ const toast = function (message, options = {}) {
|
|
|
630
730
|
toastElement.appendChild(loader);
|
|
631
731
|
setTimeout(() => {
|
|
632
732
|
loader.classList.add('done');
|
|
633
|
-
}, 2000
|
|
733
|
+
}, duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000);
|
|
634
734
|
}
|
|
635
735
|
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
736
|
+
// Rebind the dismiss callback rather than leaving the creation-time one in place,
|
|
737
|
+
// which meant an updated toast fired two different onDismiss handlers.
|
|
738
|
+
if (typeof onDismiss === 'function') toastElement._onDismiss = onDismiss;
|
|
640
739
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
setTimeout(() => {
|
|
648
|
-
el.classList.remove('ts-toast-slide-out');
|
|
649
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
650
|
-
if (typeof cb === 'function') cb();
|
|
651
|
-
}, 500);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
removeWithAnimation(toastElement, () => {
|
|
655
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
656
|
-
});
|
|
657
|
-
}, duration);
|
|
658
|
-
|
|
659
|
-
toastElement._autoRemove = autoRemove; // Re-set the auto-remove timer
|
|
740
|
+
// Re-arm the countdown through the toast's own timer, so duration: 0 keeps the
|
|
741
|
+
// toast on screen. This used to schedule setTimeout(..., 0) and remove it at once,
|
|
742
|
+
// which broke every `toast.loading(...).update(msg, { duration: 0 })`.
|
|
743
|
+
if (typeof toastElement._setDuration === 'function') {
|
|
744
|
+
toastElement._setDuration(duration);
|
|
745
|
+
}
|
|
660
746
|
};
|
|
661
747
|
|
|
662
748
|
toast.loading = function (message, options = {}) {
|
|
@@ -730,6 +816,34 @@ const toast = function (message, options = {}) {
|
|
|
730
816
|
});
|
|
731
817
|
};
|
|
732
818
|
|
|
819
|
+
// Wraps an async action: one loading toast that becomes the success or error
|
|
820
|
+
// message, instead of hand-rolling loading/update/catch at every call site.
|
|
821
|
+
toast.promise = function (promise, messages = {}, options = {}) {
|
|
822
|
+
const {
|
|
823
|
+
loading = 'Loading…',
|
|
824
|
+
success = 'Done',
|
|
825
|
+
error = 'Something went wrong'
|
|
826
|
+
} = messages;
|
|
827
|
+
|
|
828
|
+
const handle = toast.loading(loading, options);
|
|
829
|
+
// Messages may be functions so they can name what actually came back.
|
|
830
|
+
const text = (msg, value) => (typeof msg === 'function' ? msg(value) : msg);
|
|
831
|
+
|
|
832
|
+
return Promise.resolve(promise).then(
|
|
833
|
+
(value) => {
|
|
834
|
+
handle.update(text(success, value), { type: 'success', duration: options.duration });
|
|
835
|
+
return value;
|
|
836
|
+
},
|
|
837
|
+
(err) => {
|
|
838
|
+
handle.update(text(error, err), { type: 'error', duration: options.duration });
|
|
839
|
+
throw err; // the caller still owns the failure
|
|
840
|
+
}
|
|
841
|
+
);
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
// Options applied to every toast unless the call overrides them.
|
|
845
|
+
toast.defaults = {};
|
|
846
|
+
|
|
733
847
|
// Close every toast currently on screen. Confirm dialogs settle as a cancel.
|
|
734
848
|
toast.dismissAll = function () {
|
|
735
849
|
document.querySelectorAll('.ts-toast').forEach((el) => {
|
package/toast.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const TS_TOAST_VERSION="5.5.0",TS_TOAST_CDN="undefined"!=typeof window&&window.TS_TOAST_ASSET_BASE?String(window.TS_TOAST_ASSET_BASE).replace(/\/+$/,""):"https://cdn.jsdelivr.net/npm/@tsirosgeorge/toastnotification@5.5.0";let tsToastOpenModals=0,tsToastPrevOverflow="";const tsToastReducedMotion=()=>"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,tsToastFocusable=t=>Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter((t=>!t.disabled&&null!==t.offsetParent));let tsToastIdCounter=0;!function(){const t="ts-toast-stylesheet";if("undefined"!=typeof window&&window.TS_TOAST_NO_CSS)return;if(document.getElementById(t))return;const e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`${TS_TOAST_CDN}/assets/css/toast.min.css`,document.head.appendChild(e)}(),function(){const t="ts-toast-inline-extras";if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n /* Ensure center positions exist even if external CSS lacks them */\n .ts-toast-container.top-center { top: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }\n .ts-toast-overlay.center { align-items: center; justify-content: center; }\n .ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }\n .ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }\n .ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }\n .ts-toast-actions { display: flex; gap: 10px; justify-content: center; margin-top: 12px; }\n .ts-toast-btn { appearance: none; border: 0; padding: 8px 12px; border-radius: 8px; font-weight: 600; cursor: pointer; }\n .ts-toast-btn.cancel { background: #e9ecef; color: #1f2937; }\n .ts-toast-btn.confirm { background: #3b82f6; color: #fff; }\n .ts-toast.ts-toast-error .ts-toast-btn.confirm,\n .ts-toast.ts-toast-warning .ts-toast-btn.confirm { background: #ef4444; color: #fff; }\n .ts-toast.ts-toast-confirm .ts-toast-title { font-weight: 700; font-size: 1.05rem; margin-top: 4px; }\n .ts-toast.ts-toast-confirm .ts-toast-close { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 999px; border: 0; background: transparent; color: #6b7280; font-size: 20px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }\n .ts-toast.ts-toast-confirm .ts-toast-icon { width: 64px; height: 64px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-icon img { width: 36px; height: 36px; }\n .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }\n .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }\n .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }\n .ts-toast.ts-toast-confirm.ts-toast-error .ts-toast-icon { background: #fee2e2; }\n ",document.head.appendChild(e)}();const toast=function(t,e={}){const{position:o="top-right",animation:s="slide-right",type:n="info",duration:a=3e3,icon:i=null,showLoader:r=!1,mode:l="alert",title:c=null,confirmText:d="Yes",cancelText:u="No",input:m=!1,inputPlaceholder:f="",inputValue:p="",confirmButtonBg:y=null,confirmButtonColor:h=null,cancelButtonBg:g=null,cancelButtonColor:b=null,onConfirm:v=null,onCancel:x=null,onResult:w=null,useOverlay:T=!0,closeOnOverlayClick:C=!0,showClose:E=!1,closeOnEscape:S=!0,allowHtml:L=!0,dismissOnClick:_=!0,onClick:k=null,onShow:A=null,onDismiss:N=null}=e,O="confirm"===l||"swal"===l,M=tsToastReducedMotion(),$="string"==typeof e.animation&&e.animation.trim()?{"slide-top":"ts-toast-slide-top","slide-bottom":"ts-toast-slide-bottom","slide-left":"ts-toast-slide-left","slide-right":"ts-toast-slide-right","zoom-in":"ts-toast-zoom-in","zoom-out":"ts-toast-zoom-out",flip:"ts-toast-flip"}[B=e.animation.trim()]||B:O||"center"===o?"ts-toast-zoom-in":o.startsWith("top")?"ts-toast-slide-top":o.startsWith("bottom")?"ts-toast-slide-bottom":o.endsWith("left")?"ts-toast-slide-left":"ts-toast-slide-right";var B;const R=(t,e)=>{const o=t.dataset&&t.dataset.anim?t.dataset.anim:t.style.animation||"";let s="";o.includes("ts-toast-slide-top")?s="translateY(-100%)":o.includes("ts-toast-slide-bottom")?s="translateY(100%)":(o.includes("ts-toast-slide-left")||o.includes("ts-toast-slide-right"))&&(s="translateX(100%)"),t.classList.add("ts-toast-slide-out"),t.classList.remove("ts-toast-show"),t.style.animation="",s&&(t.style.transform=s),t.style.opacity="0",setTimeout((()=>{t.classList.remove("ts-toast-slide-out"),t.parentNode&&t.parentNode.removeChild(t),"function"==typeof e&&e()}),M?0:500)},D=document.createElement("div");D.className=`ts-toast ts-toast-${n}${O?" ts-toast-confirm":""}`,D.dataset.anim=$,M||(D.style.animation=`${$} 0.5s ease`);const q="ts-toast-"+ ++tsToastIdCounter;O?(D.setAttribute("role","dialog"),D.setAttribute("aria-modal","true"),D.tabIndex=-1):"error"!==n&&"warning"!==n||D.setAttribute("role","alert"),O||(D.style.flexDirection="row-reverse",D.style.justifyContent="flex-end");const j=document.createElement("span");if(j.className="ts-toast-icon",j.style.display="flex",i)j.textContent=i;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[n];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),j.appendChild(t)}const P=document.createElement("div");P.className="ts-toast-body",P.id=`${q}-body`,L?P.innerHTML=t:P.textContent=t;let z=null;if(O){if(z=document.createElement("div"),z.className="ts-toast-content",z.appendChild(j),c){const t=document.createElement("div");t.className="ts-toast-title",t.id=`${q}-title`,t.textContent=c,z.appendChild(t),D.setAttribute("aria-labelledby",t.id)}z.appendChild(P),D.setAttribute("aria-describedby",P.id),D.appendChild(z)}else D.appendChild(P);let I=null;O&&m&&("textarea"===m?(I=document.createElement("textarea"),I.rows=3):(I=document.createElement("input"),I.type="text"===m||"email"===m||"password"===m||"number"===m?m:"text"),I.className="ts-toast-input",I.placeholder=f,I.value=p,"textarea"!==m&&I.addEventListener("keydown",(t=>{"Enter"===t.key&&(t.preventDefault(),Y(!0))})),D.appendChild(I));let F=null,X=null,Y=null,H=()=>{};if(O){F=document.createElement("div"),F.className="ts-toast-actions";const t=document.createElement("button");t.className="ts-toast-btn cancel",t.textContent=u;const e=document.createElement("button");e.className="ts-toast-btn confirm",e.textContent=d,g&&(t.style.background=g),b&&(t.style.color=b),y&&(e.style.background=y),h&&(e.style.color=h),F.appendChild(t),F.appendChild(e),D.appendChild(F),D.result=new Promise((t=>{X=t}));let o=!1;Y=t=>{if(o)return;o=!0;const e=t?!I||I.value:!!I&&null;X&&X(e),t&&"function"==typeof v&&v(e,D),t||"function"!=typeof x||x(D),"function"==typeof w&&w(e,D),H(),R(D,(()=>{N&&"function"==typeof N&&N(D),W&&W.parentNode&&W.parentNode.removeChild(W)}))},t.addEventListener("click",(t=>{t.stopPropagation(),Y(!1)})),e.addEventListener("click",(t=>{t.stopPropagation(),Y(!0)}))}let K=null;r&&(K=document.createElement("div"),K.className="ts-toast-loader",D.appendChild(K));let W=null;if(O&&T){if(W=document.createElement("div"),W.className="ts-toast-overlay"+(e.position?` ${o}`:""),document.body.appendChild(W),W.appendChild(D),E){const t=document.createElement("button");t.className="ts-toast-close",t.setAttribute("aria-label","Close"),t.innerHTML="×",t.addEventListener("click",(t=>{t.stopPropagation(),Y(!1)})),D.appendChild(t)}if(C){let t=!1;W.addEventListener("pointerdown",(e=>{t=e.target===W})),W.addEventListener("click",(e=>{e.target===W&&t&&Y(!1),t=!1}))}}else{let t=document.querySelector(`.ts-toast-container.${o}`);t||(t=document.createElement("div"),t.className=`ts-toast-container ${o}`,document.body.appendChild(t)),t.hasAttribute("aria-live")||(t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-relevant","additions")),t.appendChild(D)}if(O){const t=document.activeElement;tsToastOpenModals+=1,1===tsToastOpenModals&&(tsToastPrevOverflow=document.body.style.overflow,document.body.style.overflow="hidden");const e=()=>{const t=document.querySelectorAll(".ts-toast.ts-toast-confirm");return 0===t.length||t[t.length-1]===D},o=t=>{if(!e())return;if("Escape"===t.key&&S)return t.preventDefault(),void Y(!1);if("Tab"!==t.key)return;const o=tsToastFocusable(D);if(!o.length)return void t.preventDefault();const s=o[0],n=o[o.length-1];D.contains(document.activeElement)?t.shiftKey&&document.activeElement===s?(t.preventDefault(),n.focus()):t.shiftKey||document.activeElement!==n||(t.preventDefault(),s.focus()):(t.preventDefault(),(t.shiftKey?n:s).focus())};document.addEventListener("keydown",o,!0),H=()=>{document.removeEventListener("keydown",o,!0),tsToastOpenModals=Math.max(0,tsToastOpenModals-1),0===tsToastOpenModals&&(document.body.style.overflow=tsToastPrevOverflow),t&&"function"==typeof t.focus&&document.contains(t)&&t.focus()},(I||D.querySelector(".ts-toast-btn.confirm")||D).focus()}if(A&&"function"==typeof A&&A(D),setTimeout((()=>{D.classList.add("ts-toast-show")}),100),r&&K&&setTimeout((()=>{D._managedByLoading||(K.classList.add("done"),K.remove(),D.contains(j)||(O&&z?z.appendChild(j):D.appendChild(j)))}),2e3),r||O||D.contains(j)||D.appendChild(j),!O&&a>0){const t=setTimeout((()=>{R(D,(()=>{N&&"function"==typeof N&&N(D)}))}),a);D._autoRemove=t}if(!O&&_&&D.addEventListener("click",(()=>{D._autoRemove&&clearTimeout(D._autoRemove),k&&"function"==typeof k&&k(D),R(D,(()=>{N&&"function"==typeof N&&N(D)}))})),!O){let t=0,e=0,o=0;D.addEventListener("touchstart",(o=>{t=o.changedTouches[0].screenX,e=o.changedTouches[0].screenY}),{passive:!0}),D.addEventListener("touchend",(s=>{o=s.changedTouches[0].screenX;const n=Math.abs(t-o),a=Math.abs(e-s.changedTouches[0].screenY);n>50&&n>a&&(D._autoRemove&&clearTimeout(D._autoRemove),R(D,(()=>{N&&"function"==typeof N&&N(D)})))}))}return D.close=()=>{D._autoRemove&&clearTimeout(D._autoRemove),O?Y(!1):R(D,(()=>{N&&"function"==typeof N&&N(D)}))},D};toast.success=function(t,e){return toast(t,{...e,type:"success"})},toast.error=function(t,e){return toast(t,{...e,type:"error"})},toast.warning=function(t,e){return toast(t,{...e,type:"warning"})},toast.info=function(t,e){return toast(t,{...e,type:"info"})},toast.update=function(t,e,o={}){const{type:s=null,icon:n=null,showLoader:a=!1,duration:i=3e3,onClick:r=null,onShow:l=null,onDismiss:c=null}=o,d=t.querySelector(".ts-toast-loader"),u=t.querySelector(".ts-toast-icon");d&&d.remove(),s&&(["success","error","info","warning"].forEach((e=>t.classList.remove(`ts-toast-${e}`))),t.classList.add("ts-toast",`ts-toast-${s}`,"ts-toast-show"));const m=t.querySelector(".ts-toast-body");m&&(m.innerHTML=e),u&&u.remove();const f=document.createElement("span");if(f.className="ts-toast-icon",f.style.display="flex",n)f.textContent=n;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[s];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),f.appendChild(t)}if((t.querySelector(".ts-toast-content")||t).appendChild(f),a){const e=document.createElement("div");e.className="ts-toast-loader",t.appendChild(e),setTimeout((()=>{e.classList.add("done")}),2e3)}t._autoRemove&&clearTimeout(t._autoRemove);const p=setTimeout((()=>{var e,o;o=()=>{c&&"function"==typeof c&&c(t)},(e=t).classList.add("ts-toast-slide-out"),e.classList.remove("ts-toast-show"),e.style.animation="",setTimeout((()=>{e.classList.remove("ts-toast-slide-out"),e.parentNode&&e.parentNode.removeChild(e),"function"==typeof o&&o()}),500)}),i);t._autoRemove=p},toast.loading=function(t,e={}){const o=toast(t,{...e,type:e.type||"info",duration:0,showLoader:!0,icon:null});requestAnimationFrame((()=>{o.classList.add("ts-toast-show")}));const s=o.querySelector(".ts-toast-loader");let n=o.querySelector(".ts-toast-icon");return n||(n=document.createElement("span"),n.className="ts-toast-icon",n.style.display="flex",o.appendChild(n)),o._managedByLoading=!0,s&&setTimeout((()=>{o._managedByLoading||s.classList.add("done")}),2e3),{update:(t,e={})=>{o._managedByLoading=!1,toast.update(o,t,{...e,showLoader:!1})},close:()=>{o._managedByLoading=!1,o.close()}}},toast.confirm=function(t,e={}){return new Promise((o=>{toast(t,{...e,mode:"confirm",duration:0,dismissOnClick:!1,onResult:t=>o(t)})}))},toast.dismissAll=function(){document.querySelectorAll(".ts-toast").forEach((t=>{"function"==typeof t.close&&t.close()}))},"undefined"!=typeof window&&(window.toast=toast),"undefined"!=typeof module&&module.exports&&(module.exports=toast);
|
|
1
|
+
"use strict";const TS_TOAST_VERSION="5.6.1",TS_TOAST_CDN="undefined"!=typeof window&&window.TS_TOAST_ASSET_BASE?String(window.TS_TOAST_ASSET_BASE).replace(/\/+$/,""):"https://cdn.jsdelivr.net/npm/@tsirosgeorge/toastnotification@5.6.1";let tsToastOpenModals=0,tsToastPrevOverflow="",tsToastPrevPaddingRight="";const tsToastReducedMotion=()=>"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,tsToastFocusable=t=>Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter((t=>!t.disabled&&null!==t.offsetParent));let tsToastIdCounter=0;!function(){const t="ts-toast-stylesheet";if("undefined"!=typeof window&&window.TS_TOAST_NO_CSS)return;if(document.getElementById(t))return;const e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`${TS_TOAST_CDN}/assets/css/toast.min.css`,document.head.appendChild(e)}(),function(){const t="ts-toast-inline-extras";if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n /* Ensure center positions exist even if external CSS lacks them */\n .ts-toast-container.top-center { top: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }\n .ts-toast-overlay.center { align-items: center; justify-content: center; }\n .ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }\n @keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }\n .ts-toast .ts-toast-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; transform-origin: left center; border-radius: 0 0 8px 8px; background: currentColor; opacity: 0.35; pointer-events: none; }\n .ts-toast .ts-toast-action { order: -1; flex: none; appearance: none; border: 0; background: transparent; color: #3b82f6; font: inherit; font-weight: 600; padding: 4px 8px; margin-left: 4px; border-radius: 6px; cursor: pointer; }\n .ts-toast .ts-toast-action:hover { background: rgba(59,130,246,0.12); }\n .ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }\n .ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }\n .ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }\n .ts-toast-actions { display: flex; gap: 10px; justify-content: center; margin-top: 12px; }\n .ts-toast-btn { appearance: none; border: 0; padding: 8px 12px; border-radius: 8px; font-weight: 600; cursor: pointer; }\n .ts-toast-btn.cancel { background: #e9ecef; color: #1f2937; }\n .ts-toast-btn.confirm { background: #3b82f6; color: #fff; }\n .ts-toast.ts-toast-error .ts-toast-btn.confirm,\n .ts-toast.ts-toast-warning .ts-toast-btn.confirm { background: #ef4444; color: #fff; }\n .ts-toast.ts-toast-confirm .ts-toast-title { font-weight: 700; font-size: 1.05rem; margin-top: 4px; }\n .ts-toast.ts-toast-confirm .ts-toast-close { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 999px; border: 0; background: transparent; color: #6b7280; font-size: 20px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }\n .ts-toast.ts-toast-confirm .ts-toast-icon { width: 64px; height: 64px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-icon img { width: 36px; height: 36px; }\n .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }\n .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }\n .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }\n .ts-toast.ts-toast-confirm.ts-toast-error .ts-toast-icon { background: #fee2e2; }\n ",document.head.appendChild(e)}();const toast=function(t,e={}){e={...toast.defaults||{},...e};const{position:o="top-right",animation:s="slide-right",type:n="info",duration:a=3e3,icon:i=null,showLoader:r=!1,mode:l="alert",title:c=null,confirmText:d="Yes",cancelText:u="No",input:m=!1,inputPlaceholder:p="",inputValue:f="",confirmButtonBg:g=null,confirmButtonColor:y=null,cancelButtonBg:h=null,cancelButtonColor:b=null,onConfirm:x=null,onCancel:v=null,onResult:w=null,useOverlay:T=!0,closeOnOverlayClick:C=!0,showClose:E=!1,pauseOnHover:S=!0,showProgress:k=!1,action:L=null,closeOnEscape:_=!0,allowHtml:A=!0,dismissOnClick:N=!0,onClick:O=null,onShow:D=null,onDismiss:P=null}=e,M="confirm"===l||"swal"===l,$=tsToastReducedMotion(),B="string"==typeof e.animation&&e.animation.trim()?{"slide-top":"ts-toast-slide-top","slide-bottom":"ts-toast-slide-bottom","slide-left":"ts-toast-slide-left","slide-right":"ts-toast-slide-right","zoom-in":"ts-toast-zoom-in","zoom-out":"ts-toast-zoom-out",flip:"ts-toast-flip"}[q=e.animation.trim()]||q:M||"center"===o?"ts-toast-zoom-in":o.startsWith("top")?"ts-toast-slide-top":o.startsWith("bottom")?"ts-toast-slide-bottom":o.endsWith("left")?"ts-toast-slide-left":"ts-toast-slide-right";var q;const R=(t,e)=>{const o=t.dataset&&t.dataset.anim?t.dataset.anim:t.style.animation||"";let s="";o.includes("ts-toast-slide-top")?s="translateY(-100%)":o.includes("ts-toast-slide-bottom")?s="translateY(100%)":(o.includes("ts-toast-slide-left")||o.includes("ts-toast-slide-right"))&&(s="translateX(100%)"),t.classList.add("ts-toast-slide-out"),t.classList.remove("ts-toast-show"),t.style.animation="",s&&(t.style.transform=s),t.style.opacity="0",setTimeout((()=>{t.classList.remove("ts-toast-slide-out"),t.parentNode&&t.parentNode.removeChild(t),"function"==typeof e&&e()}),$?0:500)},j=document.createElement("div");j.className=`ts-toast ts-toast-${n}${M?" ts-toast-confirm":""}`,j.dataset.anim=B,$||(j.style.animation=`${B} 0.5s ease`);const z="ts-toast-"+ ++tsToastIdCounter;M?(j.setAttribute("role","dialog"),j.setAttribute("aria-modal","true"),j.tabIndex=-1):"error"!==n&&"warning"!==n||j.setAttribute("role","alert"),M||(j.style.flexDirection="row-reverse",j.style.justifyContent="flex-end");const X=document.createElement("span");if(X.className="ts-toast-icon",X.style.display="flex",i)X.textContent=i;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[n];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),X.appendChild(t)}const F=document.createElement("div");F.className="ts-toast-body",F.id=`${z}-body`,A?F.innerHTML=t:F.textContent=t;let H=null;if(M){if(H=document.createElement("div"),H.className="ts-toast-content",H.appendChild(X),c){const t=document.createElement("div");t.className="ts-toast-title",t.id=`${z}-title`,t.textContent=c,H.appendChild(t),j.setAttribute("aria-labelledby",t.id)}H.appendChild(F),j.setAttribute("aria-describedby",F.id),j.appendChild(H)}else j.appendChild(F);let I=null;M&&m&&("textarea"===m?(I=document.createElement("textarea"),I.rows=3):(I=document.createElement("input"),I.type="text"===m||"email"===m||"password"===m||"number"===m?m:"text"),I.className="ts-toast-input",I.placeholder=p,I.value=f,"textarea"!==m&&I.addEventListener("keydown",(t=>{"Enter"===t.key&&(t.preventDefault(),K(!0))})),j.appendChild(I));let W=null,Y=null,K=null,V=()=>{};if(M){W=document.createElement("div"),W.className="ts-toast-actions";const t=document.createElement("button");t.className="ts-toast-btn cancel",t.textContent=u;const e=document.createElement("button");e.className="ts-toast-btn confirm",e.textContent=d,h&&(t.style.background=h),b&&(t.style.color=b),g&&(e.style.background=g),y&&(e.style.color=y),W.appendChild(t),W.appendChild(e),j.appendChild(W),j.result=new Promise((t=>{Y=t}));let o=!1;K=t=>{if(o)return;o=!0;const e=t?!I||I.value:!!I&&null;Y&&Y(e),t&&"function"==typeof x&&x(e,j),t||"function"!=typeof v||v(j),"function"==typeof w&&w(e,j),V(),R(j,(()=>{P&&"function"==typeof P&&P(j),Q&&Q.parentNode&&Q.parentNode.removeChild(Q)}))},t.addEventListener("click",(t=>{t.stopPropagation(),K(!1)})),e.addEventListener("click",(t=>{t.stopPropagation(),K(!0)}))}if(!M&&L&&"object"==typeof L&&L.text){const t=document.createElement("button");t.className="ts-toast-action",t.type="button",t.textContent=L.text,t.addEventListener("click",(t=>{t.stopPropagation(),"function"==typeof L.onClick&&L.onClick(j),j._dismiss()})),j.appendChild(t)}let G=null;!M&&k&&a>0&&!$&&(G=document.createElement("div"),G.className="ts-toast-progress",G.style.animation=`ts-toast-progress ${a}ms linear forwards`,G.style.animationPlayState="paused",j.appendChild(G));let J=null;r&&(J=document.createElement("div"),J.className="ts-toast-loader",j.appendChild(J));let Q=null,U=null;if(M&&T){Q=document.createElement("div");const t=tsToastOpenModals>0?" ts-toast-overlay-stacked":"";if(Q.className="ts-toast-overlay"+t+(e.position?` ${o}`:""),document.body.appendChild(Q),Q.appendChild(j),E){const t=document.createElement("button");t.className="ts-toast-close",t.setAttribute("aria-label","Close"),t.innerHTML="×",t.addEventListener("click",(t=>{t.stopPropagation(),K(!1)})),j.appendChild(t)}if(C){let t=!1;Q.addEventListener("pointerdown",(e=>{t=e.target===Q})),Q.addEventListener("click",(e=>{e.target===Q&&t&&K(!1),t=!1}))}}else{let t=document.querySelector(`.ts-toast-container.${o}`);t||(t=document.createElement("div"),t.className=`ts-toast-container ${o}`,document.body.appendChild(t)),t.hasAttribute("aria-live")||(t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-relevant","additions")),t.appendChild(j),U=t}if(M){const t=document.activeElement;if(tsToastOpenModals+=1,1===tsToastOpenModals){tsToastPrevOverflow=document.body.style.overflow,tsToastPrevPaddingRight=document.body.style.paddingRight;const t=window.innerWidth-document.documentElement.clientWidth;if(t>0){const e=parseFloat(window.getComputedStyle(document.body).paddingRight)||0;document.body.style.paddingRight=`${e+t}px`}document.body.style.overflow="hidden"}const e=()=>{const t=document.querySelectorAll(".ts-toast.ts-toast-confirm");return 0===t.length||t[t.length-1]===j},o=t=>{if(!e())return;if("Escape"===t.key&&_)return t.preventDefault(),void K(!1);if("Tab"!==t.key)return;const o=tsToastFocusable(j);if(!o.length)return void t.preventDefault();const s=o[0],n=o[o.length-1];j.contains(document.activeElement)?t.shiftKey&&document.activeElement===s?(t.preventDefault(),n.focus()):t.shiftKey||document.activeElement!==n||(t.preventDefault(),s.focus()):(t.preventDefault(),(t.shiftKey?n:s).focus())};document.addEventListener("keydown",o,!0),V=()=>{document.removeEventListener("keydown",o,!0),tsToastOpenModals=Math.max(0,tsToastOpenModals-1),0===tsToastOpenModals&&(document.body.style.overflow=tsToastPrevOverflow,document.body.style.paddingRight=tsToastPrevPaddingRight),t&&"function"==typeof t.focus&&document.contains(t)&&t.focus()},(I||j.querySelector(".ts-toast-btn.confirm")||j).focus()}D&&"function"==typeof D&&D(j),setTimeout((()=>{j.classList.add("ts-toast-show")}),100);const Z=a>0?Math.min(2e3,Math.max(0,a-500)):2e3;r&&J&&setTimeout((()=>{j._managedByLoading||(J.classList.add("done"),J.remove(),j.contains(X)||(M&&H?H.appendChild(X):j.appendChild(X)))}),Z),r||M||j.contains(X)||j.appendChild(X),j._onDismiss="function"==typeof P?P:null;let tt=a,et=null,ot=0;const st=()=>{et&&(clearTimeout(et),et=null)},nt=()=>{M||tt<=0||et||(ot=Date.now(),et=setTimeout((()=>{et=null,j._dismiss()}),tt),G&&(G.style.animationPlayState="running"))},at=()=>{et&&(clearTimeout(et),et=null,tt-=Date.now()-ot,G&&(G.style.animationPlayState="paused"))};if(j._dismiss=()=>{j._removing||(j._removing=!0,st(),R(j,(()=>{j._onDismiss&&j._onDismiss(j),U&&!U.children.length&&U.remove()})))},j._setDuration=t=>{st(),tt=t,nt()},nt(),!M&&S&&(j.addEventListener("mouseenter",at),j.addEventListener("mouseleave",nt),j.addEventListener("focusin",at),j.addEventListener("focusout",nt)),!M&&N&&j.addEventListener("click",(()=>{O&&"function"==typeof O&&O(j),j._dismiss()})),!M){let t=0,e=0,o=0;j.addEventListener("touchstart",(o=>{t=o.changedTouches[0].screenX,e=o.changedTouches[0].screenY}),{passive:!0}),j.addEventListener("touchend",(s=>{o=s.changedTouches[0].screenX;const n=Math.abs(t-o),a=Math.abs(e-s.changedTouches[0].screenY);n>50&&n>a&&j._dismiss()}))}return j.close=()=>{M?K(!1):j._dismiss()},j};toast.success=function(t,e){return toast(t,{...e,type:"success"})},toast.error=function(t,e){return toast(t,{...e,type:"error"})},toast.warning=function(t,e){return toast(t,{...e,type:"warning"})},toast.info=function(t,e){return toast(t,{...e,type:"info"})},toast.update=function(t,e,o={}){const{type:s=null,icon:n=null,showLoader:a=!1,duration:i=3e3,allowHtml:r=!0,onDismiss:l=null}=o,c=t.querySelector(".ts-toast-loader"),d=t.querySelector(".ts-toast-icon");c&&c.remove(),s&&(["success","error","info","warning"].forEach((e=>t.classList.remove(`ts-toast-${e}`))),t.classList.add("ts-toast",`ts-toast-${s}`,"ts-toast-show"));const u=t.querySelector(".ts-toast-body");u&&(r?u.innerHTML=e:u.textContent=e),d&&d.remove();const m=document.createElement("span");if(m.className="ts-toast-icon",m.style.display="flex",n)m.textContent=n;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[s];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),m.appendChild(t)}if(n||m.querySelector("img[src]")){(t.querySelector(".ts-toast-content")||t).appendChild(m)}if(a){const e=document.createElement("div");e.className="ts-toast-loader",t.appendChild(e),setTimeout((()=>{e.classList.add("done")}),i>0?Math.min(2e3,Math.max(0,i-500)):2e3)}"function"==typeof l&&(t._onDismiss=l),"function"==typeof t._setDuration&&t._setDuration(i)},toast.loading=function(t,e={}){const o=toast(t,{...e,type:e.type||"info",duration:0,showLoader:!0,icon:null});requestAnimationFrame((()=>{o.classList.add("ts-toast-show")}));const s=o.querySelector(".ts-toast-loader");let n=o.querySelector(".ts-toast-icon");return n||(n=document.createElement("span"),n.className="ts-toast-icon",n.style.display="flex",o.appendChild(n)),o._managedByLoading=!0,s&&setTimeout((()=>{o._managedByLoading||s.classList.add("done")}),2e3),{update:(t,e={})=>{o._managedByLoading=!1,toast.update(o,t,{...e,showLoader:!1})},close:()=>{o._managedByLoading=!1,o.close()}}},toast.confirm=function(t,e={}){return new Promise((o=>{toast(t,{...e,mode:"confirm",duration:0,dismissOnClick:!1,onResult:t=>o(t)})}))},toast.promise=function(t,e={},o={}){const{loading:s="Loading…",success:n="Done",error:a="Something went wrong"}=e,i=toast.loading(s,o),r=(t,e)=>"function"==typeof t?t(e):t;return Promise.resolve(t).then((t=>(i.update(r(n,t),{type:"success",duration:o.duration}),t)),(t=>{throw i.update(r(a,t),{type:"error",duration:o.duration}),t}))},toast.defaults={},toast.dismissAll=function(){document.querySelectorAll(".ts-toast").forEach((t=>{"function"==typeof t.close&&t.close()}))},"undefined"!=typeof window&&(window.toast=toast),"undefined"!=typeof module&&module.exports&&(module.exports=toast);
|
package/toast.module.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Single source of truth for the CDN this build points at.
|
|
4
4
|
// `npm run sync:version` rewrites it from package.json, so it can never go stale.
|
|
5
|
-
const TS_TOAST_VERSION = "5.
|
|
5
|
+
const TS_TOAST_VERSION = "5.6.1";
|
|
6
6
|
// Point this at your own copy of assets/ to self-host the CSS and icons
|
|
7
7
|
// (useful offline, behind a strict CSP, or when you don't want a CDN dependency):
|
|
8
8
|
// window.TS_TOAST_ASSET_BASE = '/vendor/toastnotification';
|
|
@@ -14,6 +14,7 @@ const TS_TOAST_CDN = (typeof window !== 'undefined' && window.TS_TOAST_ASSET_BAS
|
|
|
14
14
|
// group, so only the last dialog to close may release it.
|
|
15
15
|
let tsToastOpenModals = 0;
|
|
16
16
|
let tsToastPrevOverflow = '';
|
|
17
|
+
let tsToastPrevPaddingRight = '';
|
|
17
18
|
|
|
18
19
|
const tsToastReducedMotion = () =>
|
|
19
20
|
typeof window !== 'undefined' &&
|
|
@@ -52,6 +53,11 @@ let tsToastIdCounter = 0;
|
|
|
52
53
|
.ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
|
|
53
54
|
.ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }
|
|
54
55
|
.ts-toast-overlay.center { align-items: center; justify-content: center; }
|
|
56
|
+
.ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }
|
|
57
|
+
@keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }
|
|
58
|
+
.ts-toast .ts-toast-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; transform-origin: left center; border-radius: 0 0 8px 8px; background: currentColor; opacity: 0.35; pointer-events: none; }
|
|
59
|
+
.ts-toast .ts-toast-action { order: -1; flex: none; appearance: none; border: 0; background: transparent; color: #3b82f6; font: inherit; font-weight: 600; padding: 4px 8px; margin-left: 4px; border-radius: 6px; cursor: pointer; }
|
|
60
|
+
.ts-toast .ts-toast-action:hover { background: rgba(59,130,246,0.12); }
|
|
55
61
|
.ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }
|
|
56
62
|
.ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }
|
|
57
63
|
.ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
|
|
@@ -75,6 +81,9 @@ let tsToastIdCounter = 0;
|
|
|
75
81
|
})();
|
|
76
82
|
|
|
77
83
|
const toast = function (message, options = {}) {
|
|
84
|
+
// Site-wide defaults, so a page can turn on things like showProgress once
|
|
85
|
+
// instead of repeating them at every call site.
|
|
86
|
+
options = { ...(toast.defaults || {}), ...options };
|
|
78
87
|
const {
|
|
79
88
|
position = 'top-right',
|
|
80
89
|
animation = 'slide-right', // Default fallback animation
|
|
@@ -103,6 +112,12 @@ const toast = function (message, options = {}) {
|
|
|
103
112
|
useOverlay = true,
|
|
104
113
|
closeOnOverlayClick = true,
|
|
105
114
|
showClose = false,
|
|
115
|
+
// Freeze the countdown while the pointer or keyboard focus is on the toast
|
|
116
|
+
pauseOnHover = true,
|
|
117
|
+
// Thin bar counting the remaining time down
|
|
118
|
+
showProgress = false,
|
|
119
|
+
// { text, onClick } renders a button inside the toast, e.g. Undo
|
|
120
|
+
action = null,
|
|
106
121
|
// Escape cancels a confirm dialog
|
|
107
122
|
closeOnEscape = true,
|
|
108
123
|
// `message` is written as HTML for backwards compatibility. Pass false to
|
|
@@ -329,6 +344,31 @@ const toast = function (message, options = {}) {
|
|
|
329
344
|
confirmBtn.addEventListener('click', (e) => { e.stopPropagation(); resolveAndClose(true); });
|
|
330
345
|
}
|
|
331
346
|
|
|
347
|
+
// Action button (e.g. Undo). Clicking it runs the callback and closes the toast.
|
|
348
|
+
if (!isConfirm && action && typeof action === 'object' && action.text) {
|
|
349
|
+
const actionBtn = document.createElement('button');
|
|
350
|
+
actionBtn.className = 'ts-toast-action';
|
|
351
|
+
actionBtn.type = 'button';
|
|
352
|
+
actionBtn.textContent = action.text;
|
|
353
|
+
actionBtn.addEventListener('click', (e) => {
|
|
354
|
+
e.stopPropagation(); // do not also trigger dismissOnClick
|
|
355
|
+
if (typeof action.onClick === 'function') action.onClick(toastElement);
|
|
356
|
+
toastElement._dismiss();
|
|
357
|
+
});
|
|
358
|
+
toastElement.appendChild(actionBtn);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Progress bar. The width is driven by a CSS animation whose duration is the
|
|
362
|
+
// toast's own, so pausing it is one property and never drifts from the timer.
|
|
363
|
+
let progressBar = null;
|
|
364
|
+
if (!isConfirm && showProgress && duration > 0 && !reducedMotion) {
|
|
365
|
+
progressBar = document.createElement('div');
|
|
366
|
+
progressBar.className = 'ts-toast-progress';
|
|
367
|
+
progressBar.style.animation = `ts-toast-progress ${duration}ms linear forwards`;
|
|
368
|
+
progressBar.style.animationPlayState = 'paused';
|
|
369
|
+
toastElement.appendChild(progressBar);
|
|
370
|
+
}
|
|
371
|
+
|
|
332
372
|
// Loader Element
|
|
333
373
|
let loader = null;
|
|
334
374
|
if (showLoader) {
|
|
@@ -339,11 +379,15 @@ const toast = function (message, options = {}) {
|
|
|
339
379
|
|
|
340
380
|
// Container/Overlay
|
|
341
381
|
let overlay = null;
|
|
382
|
+
let containerEl = null;
|
|
342
383
|
if (isConfirm && useOverlay) {
|
|
343
384
|
overlay = document.createElement('div');
|
|
344
385
|
// A modal centres by default. The `position` default of 'top-right' is meant
|
|
345
386
|
// for toasts; applying it here parked the dialog in a corner of the backdrop.
|
|
346
|
-
|
|
387
|
+
// Each backdrop paints its own 50% black, so a second dialog opening over a
|
|
388
|
+
// first turned the page almost fully dark. Only the bottom one dims.
|
|
389
|
+
const stacked = tsToastOpenModals > 0 ? ' ts-toast-overlay-stacked' : '';
|
|
390
|
+
overlay.className = 'ts-toast-overlay' + stacked + (options.position ? ` ${position}` : '');
|
|
347
391
|
document.body.appendChild(overlay);
|
|
348
392
|
overlay.appendChild(toastElement);
|
|
349
393
|
if (showClose) {
|
|
@@ -387,6 +431,7 @@ const toast = function (message, options = {}) {
|
|
|
387
431
|
container.setAttribute('aria-relevant', 'additions');
|
|
388
432
|
}
|
|
389
433
|
container.appendChild(toastElement);
|
|
434
|
+
containerEl = container;
|
|
390
435
|
}
|
|
391
436
|
|
|
392
437
|
if (isConfirm) {
|
|
@@ -399,6 +444,15 @@ const toast = function (message, options = {}) {
|
|
|
399
444
|
tsToastOpenModals += 1;
|
|
400
445
|
if (tsToastOpenModals === 1) {
|
|
401
446
|
tsToastPrevOverflow = document.body.style.overflow;
|
|
447
|
+
tsToastPrevPaddingRight = document.body.style.paddingRight;
|
|
448
|
+
// Hiding the scrollbar makes the page wider, which shifts the whole
|
|
449
|
+
// layout sideways as the dialog opens. Pad by the scrollbar's width to
|
|
450
|
+
// hold it still. A no-op where scrollbars are overlays, as on macOS.
|
|
451
|
+
const scrollbar = window.innerWidth - document.documentElement.clientWidth;
|
|
452
|
+
if (scrollbar > 0) {
|
|
453
|
+
const current = parseFloat(window.getComputedStyle(document.body).paddingRight) || 0;
|
|
454
|
+
document.body.style.paddingRight = `${current + scrollbar}px`;
|
|
455
|
+
}
|
|
402
456
|
document.body.style.overflow = 'hidden';
|
|
403
457
|
}
|
|
404
458
|
|
|
@@ -441,7 +495,10 @@ const toast = function (message, options = {}) {
|
|
|
441
495
|
releaseModal = () => {
|
|
442
496
|
document.removeEventListener('keydown', onKeydown, true);
|
|
443
497
|
tsToastOpenModals = Math.max(0, tsToastOpenModals - 1);
|
|
444
|
-
if (tsToastOpenModals === 0)
|
|
498
|
+
if (tsToastOpenModals === 0) {
|
|
499
|
+
document.body.style.overflow = tsToastPrevOverflow;
|
|
500
|
+
document.body.style.paddingRight = tsToastPrevPaddingRight;
|
|
501
|
+
}
|
|
445
502
|
// Hand the keyboard back to whatever opened the dialog.
|
|
446
503
|
if (previouslyFocused && typeof previouslyFocused.focus === 'function' &&
|
|
447
504
|
document.contains(previouslyFocused)) {
|
|
@@ -463,6 +520,10 @@ const toast = function (message, options = {}) {
|
|
|
463
520
|
toastElement.classList.add('ts-toast-show');
|
|
464
521
|
}, 100);
|
|
465
522
|
|
|
523
|
+
// The loader always ran for 2s, so with a shorter duration the toast was gone
|
|
524
|
+
// before the icon it reveals ever appeared.
|
|
525
|
+
const loaderMs = duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000;
|
|
526
|
+
|
|
466
527
|
// Handle Loader and Icon
|
|
467
528
|
if (showLoader && loader) {
|
|
468
529
|
setTimeout(() => {
|
|
@@ -474,7 +535,7 @@ const toast = function (message, options = {}) {
|
|
|
474
535
|
if (isConfirm && contentRow) contentRow.appendChild(iconElement);
|
|
475
536
|
else toastElement.appendChild(iconElement); // Add icon only if not present
|
|
476
537
|
}
|
|
477
|
-
},
|
|
538
|
+
}, loaderMs);
|
|
478
539
|
}
|
|
479
540
|
if (!showLoader) {
|
|
480
541
|
// For confirm, icon already added above inside contentRow; avoid moving it
|
|
@@ -483,26 +544,70 @@ const toast = function (message, options = {}) {
|
|
|
483
544
|
}
|
|
484
545
|
}
|
|
485
546
|
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
547
|
+
// Dismissal state lives on the element so toast.update() can rebind the callback
|
|
548
|
+
// and reuse this exact removal path instead of keeping its own copy of it.
|
|
549
|
+
toastElement._onDismiss = typeof onDismiss === 'function' ? onDismiss : null;
|
|
550
|
+
|
|
551
|
+
// A plain setTimeout cannot be paused, so the countdown is tracked by hand:
|
|
552
|
+
// `remaining` is what is left, and hovering banks it.
|
|
553
|
+
let remaining = duration;
|
|
554
|
+
let timerId = null;
|
|
555
|
+
let startedAt = 0;
|
|
556
|
+
|
|
557
|
+
const stopTimer = () => {
|
|
558
|
+
if (timerId) { clearTimeout(timerId); timerId = null; }
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const startTimer = () => {
|
|
562
|
+
if (isConfirm || remaining <= 0 || timerId) return;
|
|
563
|
+
startedAt = Date.now();
|
|
564
|
+
timerId = setTimeout(() => { timerId = null; toastElement._dismiss(); }, remaining);
|
|
565
|
+
if (progressBar) progressBar.style.animationPlayState = 'running';
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const pauseTimer = () => {
|
|
569
|
+
if (!timerId) return;
|
|
570
|
+
clearTimeout(timerId);
|
|
571
|
+
timerId = null;
|
|
572
|
+
remaining -= Date.now() - startedAt;
|
|
573
|
+
if (progressBar) progressBar.style.animationPlayState = 'paused';
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
toastElement._dismiss = () => {
|
|
577
|
+
if (toastElement._removing) return; // never run the exit twice
|
|
578
|
+
toastElement._removing = true;
|
|
579
|
+
stopTimer();
|
|
580
|
+
removeWithAnimation(toastElement, () => {
|
|
581
|
+
if (toastElement._onDismiss) toastElement._onDismiss(toastElement);
|
|
582
|
+
// Containers used to pile up in the DOM, one per position, forever.
|
|
583
|
+
if (containerEl && !containerEl.children.length) containerEl.remove();
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// Lets toast.update() re-arm the countdown without reaching into internals.
|
|
588
|
+
toastElement._setDuration = (ms) => {
|
|
589
|
+
stopTimer();
|
|
590
|
+
remaining = ms;
|
|
591
|
+
startTimer();
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
startTimer();
|
|
595
|
+
|
|
596
|
+
if (!isConfirm && pauseOnHover) {
|
|
597
|
+
// Give the reader a chance to finish the sentence.
|
|
598
|
+
toastElement.addEventListener('mouseenter', pauseTimer);
|
|
599
|
+
toastElement.addEventListener('mouseleave', startTimer);
|
|
600
|
+
toastElement.addEventListener('focusin', pauseTimer);
|
|
601
|
+
toastElement.addEventListener('focusout', startTimer);
|
|
494
602
|
}
|
|
495
603
|
|
|
496
604
|
// Add event listener for closing the toast when clicked (disabled in confirm mode)
|
|
497
605
|
if (!isConfirm && dismissOnClick) {
|
|
498
606
|
toastElement.addEventListener('click', () => {
|
|
499
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove); // Clear the auto-remove timeout
|
|
500
607
|
// onClick belongs to the click, not to the end of the exit animation,
|
|
501
608
|
// which is where it used to fire half a second late.
|
|
502
609
|
if (onClick && typeof onClick === 'function') onClick(toastElement);
|
|
503
|
-
|
|
504
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
505
|
-
});
|
|
610
|
+
toastElement._dismiss();
|
|
506
611
|
});
|
|
507
612
|
}
|
|
508
613
|
|
|
@@ -525,23 +630,15 @@ const toast = function (message, options = {}) {
|
|
|
525
630
|
const dy = Math.abs(touchStartY - e.changedTouches[0].screenY);
|
|
526
631
|
// Only a mostly-horizontal swipe dismisses, so scrolling the page past a
|
|
527
632
|
// toast no longer throws it away on a bit of sideways drift.
|
|
528
|
-
if (dx > 50 && dx > dy)
|
|
529
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
530
|
-
removeWithAnimation(toastElement, () => {
|
|
531
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
532
|
-
});
|
|
533
|
-
}
|
|
633
|
+
if (dx > 50 && dx > dy) toastElement._dismiss();
|
|
534
634
|
});
|
|
535
635
|
}
|
|
536
636
|
|
|
537
637
|
// Let callers dismiss a toast they are holding, instead of only waiting out
|
|
538
638
|
// the duration or making the user click it.
|
|
539
639
|
toastElement.close = () => {
|
|
540
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
541
640
|
if (isConfirm) { resolveAndClose(false); return; }
|
|
542
|
-
|
|
543
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
544
|
-
});
|
|
641
|
+
toastElement._dismiss();
|
|
545
642
|
};
|
|
546
643
|
|
|
547
644
|
return toastElement;
|
|
@@ -570,10 +667,9 @@ const toast = function (message, options = {}) {
|
|
|
570
667
|
type = null,
|
|
571
668
|
icon = null,
|
|
572
669
|
showLoader = false,
|
|
573
|
-
duration = 3000, // Default duration (in ms)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
onDismiss = null // Custom onDismiss event listener
|
|
670
|
+
duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
|
|
671
|
+
allowHtml = true,
|
|
672
|
+
onDismiss = null // Replaces the callback the toast was created with
|
|
577
673
|
} = options;
|
|
578
674
|
|
|
579
675
|
// Remove old loader (if any)
|
|
@@ -591,7 +687,8 @@ const toast = function (message, options = {}) {
|
|
|
591
687
|
}
|
|
592
688
|
const toastBody = toastElement.querySelector('.ts-toast-body');
|
|
593
689
|
if (toastBody) {
|
|
594
|
-
toastBody.innerHTML = message;
|
|
690
|
+
if (allowHtml) toastBody.innerHTML = message;
|
|
691
|
+
else toastBody.textContent = message;
|
|
595
692
|
}
|
|
596
693
|
|
|
597
694
|
// Handle Icon update only if it's new or hasn't been set yet
|
|
@@ -619,9 +716,12 @@ const toast = function (message, options = {}) {
|
|
|
619
716
|
iconElement.appendChild(img);
|
|
620
717
|
}
|
|
621
718
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
(
|
|
719
|
+
// Only attach an icon we actually have. Without a type and without an explicit
|
|
720
|
+
// icon this used to append an empty <span><img></span>.
|
|
721
|
+
if (icon || iconElement.querySelector('img[src]')) {
|
|
722
|
+
const contentRow = toastElement.querySelector('.ts-toast-content');
|
|
723
|
+
(contentRow || toastElement).appendChild(iconElement);
|
|
724
|
+
}
|
|
625
725
|
|
|
626
726
|
// Handle loader if requested
|
|
627
727
|
if (showLoader) {
|
|
@@ -630,33 +730,19 @@ const toast = function (message, options = {}) {
|
|
|
630
730
|
toastElement.appendChild(loader);
|
|
631
731
|
setTimeout(() => {
|
|
632
732
|
loader.classList.add('done');
|
|
633
|
-
}, 2000
|
|
733
|
+
}, duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000);
|
|
634
734
|
}
|
|
635
735
|
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
736
|
+
// Rebind the dismiss callback rather than leaving the creation-time one in place,
|
|
737
|
+
// which meant an updated toast fired two different onDismiss handlers.
|
|
738
|
+
if (typeof onDismiss === 'function') toastElement._onDismiss = onDismiss;
|
|
640
739
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
setTimeout(() => {
|
|
648
|
-
el.classList.remove('ts-toast-slide-out');
|
|
649
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
650
|
-
if (typeof cb === 'function') cb();
|
|
651
|
-
}, 500);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
removeWithAnimation(toastElement, () => {
|
|
655
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
656
|
-
});
|
|
657
|
-
}, duration);
|
|
658
|
-
|
|
659
|
-
toastElement._autoRemove = autoRemove; // Re-set the auto-remove timer
|
|
740
|
+
// Re-arm the countdown through the toast's own timer, so duration: 0 keeps the
|
|
741
|
+
// toast on screen. This used to schedule setTimeout(..., 0) and remove it at once,
|
|
742
|
+
// which broke every `toast.loading(...).update(msg, { duration: 0 })`.
|
|
743
|
+
if (typeof toastElement._setDuration === 'function') {
|
|
744
|
+
toastElement._setDuration(duration);
|
|
745
|
+
}
|
|
660
746
|
};
|
|
661
747
|
|
|
662
748
|
toast.loading = function (message, options = {}) {
|
|
@@ -730,6 +816,34 @@ const toast = function (message, options = {}) {
|
|
|
730
816
|
});
|
|
731
817
|
};
|
|
732
818
|
|
|
819
|
+
// Wraps an async action: one loading toast that becomes the success or error
|
|
820
|
+
// message, instead of hand-rolling loading/update/catch at every call site.
|
|
821
|
+
toast.promise = function (promise, messages = {}, options = {}) {
|
|
822
|
+
const {
|
|
823
|
+
loading = 'Loading…',
|
|
824
|
+
success = 'Done',
|
|
825
|
+
error = 'Something went wrong'
|
|
826
|
+
} = messages;
|
|
827
|
+
|
|
828
|
+
const handle = toast.loading(loading, options);
|
|
829
|
+
// Messages may be functions so they can name what actually came back.
|
|
830
|
+
const text = (msg, value) => (typeof msg === 'function' ? msg(value) : msg);
|
|
831
|
+
|
|
832
|
+
return Promise.resolve(promise).then(
|
|
833
|
+
(value) => {
|
|
834
|
+
handle.update(text(success, value), { type: 'success', duration: options.duration });
|
|
835
|
+
return value;
|
|
836
|
+
},
|
|
837
|
+
(err) => {
|
|
838
|
+
handle.update(text(error, err), { type: 'error', duration: options.duration });
|
|
839
|
+
throw err; // the caller still owns the failure
|
|
840
|
+
}
|
|
841
|
+
);
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
// Options applied to every toast unless the call overrides them.
|
|
845
|
+
toast.defaults = {};
|
|
846
|
+
|
|
733
847
|
// Close every toast currently on screen. Confirm dialogs settle as a cancel.
|
|
734
848
|
toast.dismissAll = function () {
|
|
735
849
|
document.querySelectorAll('.ts-toast').forEach((el) => {
|